@volverjs/ui-vue 0.0.1-beta.5 → 0.0.1-beta.8
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/README.md +61 -2
- package/dist/components/VvButton/vv-button.es.js +56 -58
- package/dist/components/VvButton/vv-button.umd.js +1 -1
- package/dist/components/VvCheckGroup/vv-check-group.es.js +221 -203
- package/dist/components/VvCheckGroup/vv-check-group.umd.js +2 -2
- package/dist/components/VvInputText/VvInputText.d.ts +14 -0
- package/dist/components/VvInputText/VvInputText.vue.d.ts +36 -1
- package/dist/components/VvInputText/VvInputTextActions.d.ts +3 -0
- package/dist/components/VvInputText/vv-input-text.es.js +509 -380
- package/dist/components/VvInputText/vv-input-text.umd.js +2 -2
- package/dist/components/VvNativeSelect/vv-native-select.es.js +180 -161
- package/dist/components/VvNativeSelect/vv-native-select.umd.js +2 -2
- package/dist/components/VvRadioGroup/vv-radio-group.es.js +211 -193
- package/dist/components/VvRadioGroup/vv-radio-group.umd.js +2 -2
- package/dist/components/VvSelect/vv-select.es.js +189 -171
- package/dist/components/VvSelect/vv-select.umd.js +2 -2
- package/dist/components/VvTextarea/VvTextarea.d.ts +43 -22
- package/dist/components/VvTextarea/VvTextarea.vue.d.ts +140 -85
- package/dist/components/VvTextarea/vv-textarea.es.js +364 -288
- package/dist/components/VvTextarea/vv-textarea.umd.js +2 -2
- package/dist/composables/debouncedInput/useDebouncedInput.d.ts +2 -0
- package/dist/composables/icons/useComponentIcons.d.ts +6 -0
- package/dist/composables/textLimit/useTextLimit.d.ts +14 -0
- package/dist/composables/useModifiers.d.ts +3 -2
- package/dist/icons.es.js +3 -3
- package/dist/icons.umd.js +1 -1
- package/dist/props/index.d.ts +42 -0
- package/dist/stories/utils.d.ts +5 -0
- package/dist/ui-vue.es.js +417 -401
- package/dist/ui-vue.umd.js +2 -2
- package/package.json +3 -1
- package/src/assets/icons/detailed.json +1 -1
- package/src/assets/icons/normal.json +1 -1
- package/src/assets/icons/simple.json +1 -1
- package/src/components/VvButton/VvButton.vue +1 -2
- package/src/components/VvInputText/VvInputText.ts +19 -2
- package/src/components/VvInputText/VvInputText.vue +123 -149
- package/src/components/VvInputText/VvInputTextActions.ts +151 -0
- package/src/components/VvTextarea/VvTextarea.ts +25 -16
- package/src/components/VvTextarea/VvTextarea.vue +89 -93
- package/src/components/common/HintSlot.ts +31 -13
- package/src/composables/debouncedInput/useDebouncedInput.ts +19 -0
- package/src/composables/icons/useComponentIcons.ts +35 -0
- package/src/composables/textLimit/useTextLimit.ts +44 -0
- package/src/composables/useModifiers.ts +47 -1
- package/src/props/index.ts +39 -0
- package/src/stories/InputText/InputTextMaxLength.stories.mdx +21 -0
- package/src/stories/Textarea/Textarea.stories.mdx +33 -51
- package/src/stories/Textarea/TextareaAutoclear.stories.mdx +23 -0
- package/src/stories/Textarea/TextareaAutocomplete.stories.mdx +10 -2
- package/src/stories/Textarea/TextareaAutofocus.stories.mdx +5 -1
- package/src/stories/Textarea/TextareaDebounce.stories.mdx +23 -0
- package/src/stories/Textarea/TextareaDisabled.stories.mdx +5 -1
- package/src/stories/Textarea/TextareaError.stories.mdx +6 -3
- package/src/stories/Textarea/TextareaErrorLabel.stories.mdx +37 -0
- package/src/stories/Textarea/TextareaFloating.stories.mdx +7 -2
- package/src/stories/Textarea/TextareaHintLabel.stories.mdx +5 -1
- package/src/stories/Textarea/TextareaIcon.stories.mdx +5 -1
- package/src/stories/Textarea/TextareaIconPosition.stories.mdx +9 -1
- package/src/stories/Textarea/TextareaId.stories.mdx +19 -0
- package/src/stories/Textarea/TextareaLabel.stories.mdx +5 -1
- package/src/stories/Textarea/TextareaLimit.stories.mdx +50 -0
- package/src/stories/Textarea/TextareaLoading.stories.mdx +6 -3
- package/src/stories/Textarea/TextareaLoadingLabel.stories.mdx +23 -0
- package/src/stories/Textarea/TextareaMaxLength.stories.mdx +6 -2
- package/src/stories/Textarea/TextareaMinLength.stories.mdx +5 -1
- package/src/stories/Textarea/TextareaModifiers.stories.mdx +24 -0
- package/src/stories/Textarea/TextareaName.stories.mdx +23 -0
- package/src/stories/Textarea/TextareaPlaceholder.stories.mdx +5 -1
- package/src/stories/Textarea/TextareaReadonly.stories.mdx +5 -1
- package/src/stories/Textarea/TextareaRequired.stories.mdx +22 -0
- package/src/stories/Textarea/TextareaResizable.stories.mdx +22 -0
- package/src/stories/Textarea/TextareaRowsCols.stories.mdx +9 -1
- package/src/stories/Textarea/TextareaValid.stories.mdx +7 -4
- package/src/stories/Textarea/TextareaValidLabel.stories.mdx +35 -0
- package/src/stories/stories.scss +11 -0
- package/src/stories/utils.ts +12 -0
- package/src/stories/volver-ui-vue.stories.mdx +7 -1
- package/dist/components/VvInputText/useInputNumber.d.ts +0 -16
- package/dist/components/VvInputText/useInputPassword.d.ts +0 -16
- package/src/components/VvInputText/useInputNumber.ts +0 -40
- package/src/components/VvInputText/useInputPassword.ts +0 -38
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div v-bind="
|
|
3
|
-
<label v-if="label" :for="
|
|
2
|
+
<div v-bind="textAreaProps" :class="textAreaClass">
|
|
3
|
+
<label v-if="label" :id="textAreaLabeledBy" :for="textAreaId">{{
|
|
4
|
+
label
|
|
5
|
+
}}</label>
|
|
4
6
|
<div class="vv-textarea__wrapper">
|
|
5
7
|
<!-- @slot icon-left to replace icon left -->
|
|
6
8
|
<slot v-if="hasIconLeft" name="icon-left" v-bind="iconSlotProps">
|
|
@@ -8,18 +10,25 @@
|
|
|
8
10
|
</slot>
|
|
9
11
|
<textarea
|
|
10
12
|
ref="input"
|
|
11
|
-
v-bind="innerTextareaProps"
|
|
12
13
|
v-model="inputTextData"
|
|
14
|
+
v-bind="htmlTextareaProps"
|
|
13
15
|
@input="emit('input', $event)" />
|
|
16
|
+
<!-- autoclear text button -->
|
|
17
|
+
<button
|
|
18
|
+
v-if="autoclear && textLength > 0"
|
|
19
|
+
class="vv-button vv-button--ghost"
|
|
20
|
+
@click="clearTextarea">
|
|
21
|
+
<vv-icon name="clear-field" />
|
|
22
|
+
</button>
|
|
14
23
|
<!-- @slot icon-right to replace icon right -->
|
|
15
24
|
<slot v-if="hasIconRight" name="icon-right" v-bind="iconSlotProps">
|
|
16
|
-
<!-- default password icon -->
|
|
17
25
|
<vv-icon :name="icon" />
|
|
18
26
|
</slot>
|
|
27
|
+
<span v-if="limit" class="vv-textarea__limit">
|
|
28
|
+
<slot name="limit"> {{ formattedTextLimitLength }} </slot>
|
|
29
|
+
</span>
|
|
19
30
|
</div>
|
|
20
|
-
<HintSlot
|
|
21
|
-
:id="inputAriaAttrs['aria-describedby']"
|
|
22
|
-
class="vv-textarea__hint" />
|
|
31
|
+
<HintSlot :id="textAreaDescribedBy" class="vv-textarea__hint" />
|
|
23
32
|
</div>
|
|
24
33
|
</template>
|
|
25
34
|
|
|
@@ -31,7 +40,6 @@ import {
|
|
|
31
40
|
ref,
|
|
32
41
|
toRefs,
|
|
33
42
|
onMounted,
|
|
34
|
-
watch,
|
|
35
43
|
type HTMLAttributes,
|
|
36
44
|
type TextareaHTMLAttributes
|
|
37
45
|
} from 'vue'
|
|
@@ -43,10 +51,11 @@ import VvIcon from '../../components/VvIcon/VvIcon.vue'
|
|
|
43
51
|
import HintSlotFactory from '../common/HintSlot'
|
|
44
52
|
|
|
45
53
|
//Composables
|
|
46
|
-
import {
|
|
47
|
-
import { useComponentIcons } from '../../composables/icons/useComponentIcons'
|
|
54
|
+
import { useComponentIcon } from '../../composables/icons/useComponentIcons'
|
|
48
55
|
import { useComponentFocus } from '../../composables/focus/useComponentFocus'
|
|
49
|
-
import {
|
|
56
|
+
import { useDebouncedInput } from '../../composables/debouncedInput/useDebouncedInput'
|
|
57
|
+
import { useTextLimit } from '../../composables/textLimit/useTextLimit'
|
|
58
|
+
import { toBem } from '@/composables/useModifiers'
|
|
50
59
|
|
|
51
60
|
//Props, Emits, Slots e Attrs
|
|
52
61
|
const props = defineProps(VvTextareaProps)
|
|
@@ -58,124 +67,111 @@ const attrs = useAttrs()
|
|
|
58
67
|
const input = ref()
|
|
59
68
|
|
|
60
69
|
//Data
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const debouncedInputTextData = refDebounced(inputTextData, props.debounce || 0)
|
|
76
|
-
watch(debouncedInputTextData, (v) => emit('update:modelValue', v))
|
|
70
|
+
const { icon, iconPosition, label, modelValue, autoclear, limit } =
|
|
71
|
+
toRefs(props)
|
|
72
|
+
const textAreaId = props.id || props.name
|
|
73
|
+
const textAreaLabeledBy = `${props.name}-label`
|
|
74
|
+
const textAreaDescribedBy = `${props.name}-hint`
|
|
75
|
+
//BUG - https://www.samanthaming.com/tidbits/88-css-placeholder-shown/
|
|
76
|
+
const textAreaPlaceholder = computed(() =>
|
|
77
|
+
props.floating && ObjectUtilities.isEmpty(props.placeholder)
|
|
78
|
+
? ' '
|
|
79
|
+
: props.placeholder
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
//Debounce input
|
|
83
|
+
const inputTextData = useDebouncedInput(modelValue, props.debounce, emit)
|
|
77
84
|
|
|
78
85
|
//Gestione ICONE
|
|
79
|
-
const
|
|
80
|
-
const iconSlots = {
|
|
86
|
+
const { hasIconLeft, hasIconRight } = useComponentIcon(icon, iconPosition, {
|
|
81
87
|
iconLeft: slots['icon-left'],
|
|
82
88
|
iconRight: slots['icon-right']
|
|
83
|
-
}
|
|
84
|
-
const { hasIconLeft, hasIconRight } = useComponentIcons(iconProps, iconSlots)
|
|
89
|
+
})
|
|
85
90
|
|
|
86
91
|
//Input FOCUS
|
|
87
92
|
const { focused } = useComponentFocus(input, emit)
|
|
88
93
|
|
|
89
|
-
//
|
|
90
|
-
const {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
invalid: error,
|
|
94
|
-
loading,
|
|
95
|
-
iconLeft: hasIconLeft,
|
|
96
|
-
iconRight: hasIconRight,
|
|
97
|
-
floating: computed(
|
|
98
|
-
() => floating.value && ObjectUtilities.isNotEmpty(label?.value)
|
|
99
|
-
),
|
|
100
|
-
dirty: computed(() => ObjectUtilities.isNotEmpty(modelValue))
|
|
94
|
+
//Conteggio battute
|
|
95
|
+
const { textLength, formattedTextLimitLength } = useTextLimit(inputTextData, {
|
|
96
|
+
mode: props.limit,
|
|
97
|
+
upperLimit: props.maxlength || 0
|
|
101
98
|
})
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
99
|
+
|
|
100
|
+
//Styles & Bindings
|
|
101
|
+
const textAreaClass = computed(() => {
|
|
102
|
+
return [
|
|
103
|
+
toBem('vv-textarea', {
|
|
104
|
+
modifiers: props.modifiers,
|
|
105
|
+
readonly: props.readonly,
|
|
106
|
+
valid: props.valid,
|
|
107
|
+
invalid: props.error,
|
|
108
|
+
loading: props.loading,
|
|
109
|
+
iconLeft: hasIconLeft,
|
|
110
|
+
iconRight: hasIconRight,
|
|
111
|
+
floating: props.floating && ObjectUtilities.isNotEmpty(props.label),
|
|
112
|
+
dirty: ObjectUtilities.isNotEmpty(modelValue?.value),
|
|
113
|
+
resizable: props.resizable
|
|
114
|
+
}),
|
|
115
|
+
attrs.class
|
|
116
|
+
]
|
|
108
117
|
})
|
|
109
|
-
const
|
|
110
|
-
const { style } = attrs
|
|
118
|
+
const textAreaProps = computed(() => {
|
|
111
119
|
const dataAttrs = ObjectUtilities.pickBy(attrs, (k: string) =>
|
|
112
120
|
k.startsWith('data-')
|
|
113
121
|
)
|
|
114
122
|
return {
|
|
115
|
-
style,
|
|
123
|
+
style: attrs.style,
|
|
116
124
|
...dataAttrs
|
|
117
125
|
} as HTMLAttributes
|
|
118
126
|
})
|
|
119
|
-
const
|
|
120
|
-
const {
|
|
121
|
-
id,
|
|
122
|
-
name,
|
|
123
|
-
autocomplete,
|
|
124
|
-
minlength,
|
|
125
|
-
maxlength,
|
|
126
|
-
disabled,
|
|
127
|
-
readonly,
|
|
128
|
-
floating,
|
|
129
|
-
placeholder,
|
|
130
|
-
cols,
|
|
131
|
-
rows
|
|
132
|
-
} = props
|
|
133
|
-
|
|
134
|
-
const _id = id || name
|
|
135
|
-
//BUG - https://www.samanthaming.com/tidbits/88-css-placeholder-shown/
|
|
136
|
-
const _placeholder =
|
|
137
|
-
floating && ObjectUtilities.isEmpty(placeholder) ? ' ' : placeholder
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
id: _id,
|
|
141
|
-
placeholder: _placeholder,
|
|
142
|
-
name,
|
|
143
|
-
autocomplete,
|
|
144
|
-
disabled,
|
|
145
|
-
readonly,
|
|
146
|
-
minlength,
|
|
147
|
-
maxlength,
|
|
148
|
-
cols,
|
|
149
|
-
rows,
|
|
150
|
-
...inputAriaAttrs.value
|
|
151
|
-
} as TextareaHTMLAttributes
|
|
152
|
-
})
|
|
153
|
-
const inputAriaAttrs = computed(() => {
|
|
154
|
-
const { name } = attrs
|
|
127
|
+
const htmlTextareaProps = computed(() => {
|
|
155
128
|
const ariaAttrs = ObjectUtilities.pickBy(attrs, (k: string) =>
|
|
156
129
|
k.startsWith('aria-')
|
|
157
130
|
)
|
|
131
|
+
|
|
158
132
|
return {
|
|
159
|
-
|
|
160
|
-
|
|
133
|
+
id: textAreaId,
|
|
134
|
+
placeholder: textAreaPlaceholder.value,
|
|
135
|
+
name: props.name,
|
|
136
|
+
autocomplete: props.autocomplete,
|
|
137
|
+
disabled: props.disabled,
|
|
138
|
+
readonly: props.readonly,
|
|
139
|
+
minlength: props.minlength,
|
|
140
|
+
maxlength: props.maxlength,
|
|
141
|
+
cols: props.cols,
|
|
142
|
+
rows: props.rows,
|
|
143
|
+
required: props.required,
|
|
144
|
+
tabindex: attrs.tabindex,
|
|
161
145
|
'aria-invalid': props.error,
|
|
146
|
+
'aria-valid': !props.valid,
|
|
147
|
+
'aria-labeledby': textAreaLabeledBy,
|
|
148
|
+
'aria-describedby': textAreaDescribedBy,
|
|
149
|
+
'aria-errormessage': textAreaDescribedBy,
|
|
162
150
|
...ariaAttrs
|
|
163
|
-
}
|
|
151
|
+
} as TextareaHTMLAttributes
|
|
164
152
|
})
|
|
165
153
|
|
|
166
154
|
//Slot props
|
|
167
155
|
const iconSlotProps = computed(() => {
|
|
168
|
-
const { modelValue, valid, error } = props
|
|
156
|
+
const { modelValue, valid, error, maxlength, hintLabel } = props
|
|
169
157
|
return {
|
|
170
158
|
valid,
|
|
171
159
|
error,
|
|
172
|
-
modelValue
|
|
160
|
+
modelValue,
|
|
161
|
+
hintLabel,
|
|
162
|
+
maxlength,
|
|
163
|
+
textLength
|
|
173
164
|
}
|
|
174
165
|
})
|
|
175
166
|
|
|
176
167
|
//Hint
|
|
177
168
|
const HintSlot = HintSlotFactory(props, slots)
|
|
178
169
|
|
|
170
|
+
//methods
|
|
171
|
+
function clearTextarea() {
|
|
172
|
+
inputTextData.value = ''
|
|
173
|
+
}
|
|
174
|
+
|
|
179
175
|
onMounted(() => {
|
|
180
176
|
if (props.autofocus) focused.value = true
|
|
181
177
|
console.log('Focused', focused.value)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toReactive } from '@vueuse/core'
|
|
1
2
|
import { h, type Component, type ExtractPropTypes, type Slots } from 'vue'
|
|
2
3
|
import { computed, toRefs } from 'vue'
|
|
3
4
|
import ObjectUtilities from '../../utils/ObjectUtilities'
|
|
@@ -7,7 +8,7 @@ import ObjectUtilities from '../../utils/ObjectUtilities'
|
|
|
7
8
|
* @param {Array<string> | string} errors
|
|
8
9
|
* @returns {string}
|
|
9
10
|
*/
|
|
10
|
-
function
|
|
11
|
+
function joinLines(errors: Array<string> | string | unknown[] | undefined) {
|
|
11
12
|
if (Array.isArray(errors))
|
|
12
13
|
return errors
|
|
13
14
|
.filter((e) => ObjectUtilities.isString(e))
|
|
@@ -50,7 +51,10 @@ export function HintSlotFactory(
|
|
|
50
51
|
): Component {
|
|
51
52
|
return {
|
|
52
53
|
name: 'HintSlot',
|
|
53
|
-
|
|
54
|
+
props: {
|
|
55
|
+
params: { type: Object, default: () => {} }
|
|
56
|
+
},
|
|
57
|
+
setup(hProps) {
|
|
54
58
|
const props = toRefs(pProps)
|
|
55
59
|
|
|
56
60
|
//Slots
|
|
@@ -110,19 +114,29 @@ export function HintSlotFactory(
|
|
|
110
114
|
)
|
|
111
115
|
})
|
|
112
116
|
|
|
113
|
-
const errorMessage = computed(() => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
})
|
|
117
|
+
// const errorMessage = computed(() => {
|
|
118
|
+
// if (Array.isArray(errorLabel?.value))
|
|
119
|
+
// return joinLines(errorLabel?.value || '')
|
|
120
|
+
// else return errorLabel?.value
|
|
121
|
+
// })
|
|
118
122
|
|
|
119
123
|
const hintContent = computed(() => {
|
|
120
|
-
const slotProps = {
|
|
124
|
+
const slotProps = toReactive({
|
|
125
|
+
hintLabel,
|
|
126
|
+
modelValue,
|
|
127
|
+
valid,
|
|
128
|
+
validLabel,
|
|
129
|
+
error,
|
|
130
|
+
errorLabel,
|
|
131
|
+
loading,
|
|
132
|
+
loadingLabel,
|
|
133
|
+
...hProps.params
|
|
134
|
+
})
|
|
121
135
|
|
|
122
136
|
if (error?.value) {
|
|
123
137
|
return (
|
|
124
138
|
errorSlot?.(slotProps) ||
|
|
125
|
-
|
|
139
|
+
joinLines(errorLabel?.value) ||
|
|
126
140
|
hintLabel?.value
|
|
127
141
|
)
|
|
128
142
|
}
|
|
@@ -130,20 +144,20 @@ export function HintSlotFactory(
|
|
|
130
144
|
if (valid?.value)
|
|
131
145
|
return (
|
|
132
146
|
validSlot?.(slotProps) ||
|
|
133
|
-
validLabel?.value ||
|
|
147
|
+
joinLines(validLabel?.value) ||
|
|
134
148
|
hintLabel?.value
|
|
135
149
|
)
|
|
136
150
|
|
|
137
151
|
if (loading?.value)
|
|
138
152
|
return (
|
|
139
153
|
loadingSlot?.(slotProps) ||
|
|
140
|
-
loadingLabel?.value ||
|
|
154
|
+
joinLines(loadingLabel?.value) ||
|
|
141
155
|
hintLabel?.value
|
|
142
156
|
)
|
|
143
157
|
|
|
144
158
|
return (
|
|
145
159
|
hintSlot?.(slotProps) ||
|
|
146
|
-
hintLabel?.value ||
|
|
160
|
+
joinLines(hintLabel?.value) ||
|
|
147
161
|
hintLabel?.value
|
|
148
162
|
)
|
|
149
163
|
})
|
|
@@ -155,7 +169,11 @@ export function HintSlotFactory(
|
|
|
155
169
|
},
|
|
156
170
|
render() {
|
|
157
171
|
if (this.hasHint) {
|
|
158
|
-
return h(
|
|
172
|
+
return h(
|
|
173
|
+
'pre',
|
|
174
|
+
{ style: { 'white-space': 'pre' } },
|
|
175
|
+
this.hintContent
|
|
176
|
+
)
|
|
159
177
|
}
|
|
160
178
|
}
|
|
161
179
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
import { refDebounced } from '@vueuse/core'
|
|
4
|
+
|
|
5
|
+
import { watch, ref } from 'vue'
|
|
6
|
+
|
|
7
|
+
export function useDebouncedInput(
|
|
8
|
+
inputText: Ref<any> | undefined,
|
|
9
|
+
debounced: number | undefined,
|
|
10
|
+
emit: (event: any, ...args: any[]) => void
|
|
11
|
+
): Ref<any> {
|
|
12
|
+
const _text = ref(inputText?.value)
|
|
13
|
+
const debouncedInputTextData = refDebounced(
|
|
14
|
+
_text as Ref<any>,
|
|
15
|
+
debounced || 0
|
|
16
|
+
)
|
|
17
|
+
watch(debouncedInputTextData, (v) => emit('update:modelValue', v))
|
|
18
|
+
return _text
|
|
19
|
+
}
|
|
@@ -50,3 +50,38 @@ export function useComponentIcons(
|
|
|
50
50
|
hasIconBottom
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
|
|
54
|
+
export function useComponentIcon(
|
|
55
|
+
icon: Ref<string>,
|
|
56
|
+
iconPosition: Ref<string>,
|
|
57
|
+
slots: ComponentIconSlots
|
|
58
|
+
) {
|
|
59
|
+
const hasIconLeft = computed(
|
|
60
|
+
() =>
|
|
61
|
+
!!((icon.value && iconPosition.value === 'left') || slots.iconLeft)
|
|
62
|
+
)
|
|
63
|
+
const hasIconRight = computed(
|
|
64
|
+
() =>
|
|
65
|
+
!!(
|
|
66
|
+
(icon.value && iconPosition.value === 'right') ||
|
|
67
|
+
slots.iconRight
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
const hasIconTop = computed(
|
|
71
|
+
() => !!((icon.value && iconPosition.value === 'top') || slots.iconTop)
|
|
72
|
+
)
|
|
73
|
+
const hasIconBottom = computed(
|
|
74
|
+
() =>
|
|
75
|
+
!!(
|
|
76
|
+
(icon.value && iconPosition.value === 'bottom') ||
|
|
77
|
+
slots.iconBottom
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
hasIconLeft,
|
|
83
|
+
hasIconRight,
|
|
84
|
+
hasIconTop,
|
|
85
|
+
hasIconBottom
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isString } from '@vueuse/core'
|
|
2
|
+
import { computed, unref, type Ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
interface useTextOptions {
|
|
5
|
+
mode: string | boolean
|
|
6
|
+
upperLimit: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
export function useTextLimit(inputText: Ref<string>, options: useTextOptions) {
|
|
13
|
+
const textLength = computed(() => {
|
|
14
|
+
const _text = unref(inputText)
|
|
15
|
+
return isString(_text) ? _text.length : 0
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const textLimitLength = computed(() => {
|
|
19
|
+
const _text = unref(inputText) || ''
|
|
20
|
+
|
|
21
|
+
if (!isString(_text) || options.mode === false) return 0
|
|
22
|
+
|
|
23
|
+
if (options.mode === true) return _text.length
|
|
24
|
+
else return unref(options.upperLimit) - _text.length
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const formattedTextLimitLength = computed(() => {
|
|
28
|
+
if (options.mode === false) return ''
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
options.mode === true &&
|
|
32
|
+
options.upperLimit &&
|
|
33
|
+
options.upperLimit > 0
|
|
34
|
+
)
|
|
35
|
+
return `${textLimitLength.value}/${unref(options.upperLimit)}`
|
|
36
|
+
else return textLimitLength.value
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
textLength,
|
|
41
|
+
textLimitLength,
|
|
42
|
+
formattedTextLimitLength
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -5,8 +5,17 @@ interface IBemModifiers {
|
|
|
5
5
|
[key: string]:
|
|
6
6
|
| Ref<boolean>
|
|
7
7
|
| Ref<string | unknown[] | undefined>
|
|
8
|
+
| boolean
|
|
9
|
+
| string
|
|
10
|
+
| string[]
|
|
8
11
|
| undefined
|
|
9
|
-
|
|
12
|
+
| unknown[]
|
|
13
|
+
modifiers?:
|
|
14
|
+
| Ref<string | unknown[] | undefined>
|
|
15
|
+
| undefined
|
|
16
|
+
| string[]
|
|
17
|
+
| string
|
|
18
|
+
| unknown[]
|
|
10
19
|
}
|
|
11
20
|
|
|
12
21
|
export function useBemModifiers(prefix: string, modifiers: IBemModifiers) {
|
|
@@ -52,3 +61,40 @@ export function useBemModifiers(prefix: string, modifiers: IBemModifiers) {
|
|
|
52
61
|
bemCssClasses
|
|
53
62
|
}
|
|
54
63
|
}
|
|
64
|
+
|
|
65
|
+
export function toBem(prefix: string, modifiers: IBemModifiers) {
|
|
66
|
+
const baseCssClass: object = { [`${prefix}`]: true }
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
Object.keys(modifiers).reduce((acc, k) => {
|
|
70
|
+
const _modifier = unref(modifiers[k] as Ref<any>) || false
|
|
71
|
+
|
|
72
|
+
if (!_modifier) return acc
|
|
73
|
+
|
|
74
|
+
if (k === 'modifiers') {
|
|
75
|
+
const _reduceModifiers = Array.isArray(_modifier)
|
|
76
|
+
? _modifier
|
|
77
|
+
: [_modifier]
|
|
78
|
+
return {
|
|
79
|
+
...acc,
|
|
80
|
+
..._reduceModifiers.reduce(
|
|
81
|
+
(accVariant: object, currentVariant: string) => {
|
|
82
|
+
return {
|
|
83
|
+
...accVariant,
|
|
84
|
+
[`${prefix}--${ObjectUtilities.kebabCase(
|
|
85
|
+
currentVariant
|
|
86
|
+
)}`]: true
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{}
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
return {
|
|
94
|
+
...acc,
|
|
95
|
+
[`${prefix}--${ObjectUtilities.kebabCase(k)}`]: _modifier
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}, baseCssClass) || {}
|
|
99
|
+
)
|
|
100
|
+
}
|
package/src/props/index.ts
CHANGED
|
@@ -43,3 +43,42 @@ export const OptionsProps = {
|
|
|
43
43
|
*/
|
|
44
44
|
optionValue: { type: [String, Function], default: () => 'value' }
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
export const LimitProps = {
|
|
48
|
+
/**
|
|
49
|
+
* Conteggio caratteri
|
|
50
|
+
* @description
|
|
51
|
+
* Se false, non mostrare il conteggio caratteri
|
|
52
|
+
* Se true, mostra quanti caratteri ho digitato sin ora e qualì'è la maxlength.
|
|
53
|
+
* Se true e maxlength > 0, mostra quanti caratteri ho digitato sin ora e qualì'è la maxlength.
|
|
54
|
+
* Se "countdown", mostra quanti caratteri mancano per raggiungere la maxlength.
|
|
55
|
+
*/
|
|
56
|
+
limit: {
|
|
57
|
+
type: [Boolean, String],
|
|
58
|
+
default: false,
|
|
59
|
+
validator: (value: string) => [true, false, 'countdown'].includes(value)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const InputProps = {
|
|
64
|
+
id: String,
|
|
65
|
+
name: { type: String, required: true },
|
|
66
|
+
autocomplete: { type: String, default: 'off' },
|
|
67
|
+
autofocus: Boolean,
|
|
68
|
+
minlength: Number,
|
|
69
|
+
maxlength: Number,
|
|
70
|
+
label: String,
|
|
71
|
+
placeholder: String,
|
|
72
|
+
required: Boolean,
|
|
73
|
+
disabled: Boolean,
|
|
74
|
+
readonly: Boolean
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const DebounceProps = {
|
|
78
|
+
/**
|
|
79
|
+
* Debounce time (millisecondi)
|
|
80
|
+
* @descrition
|
|
81
|
+
* Tempo che deve passare dall'ultima battuta prima che modelValue venga aggiornato.
|
|
82
|
+
*/
|
|
83
|
+
debounce: Number
|
|
84
|
+
}
|
|
@@ -13,3 +13,24 @@ import { Template } from './InputText.stories.mdx'
|
|
|
13
13
|
{Template.bind({})}
|
|
14
14
|
</Story>
|
|
15
15
|
</Canvas>
|
|
16
|
+
|
|
17
|
+
<Canvas>
|
|
18
|
+
<Story
|
|
19
|
+
name="MaxLength - limit"
|
|
20
|
+
args={{ name: 'input-text-limit', maxlength: 5, showLimit: true }}>
|
|
21
|
+
{Template.bind({})}
|
|
22
|
+
</Story>
|
|
23
|
+
</Canvas>
|
|
24
|
+
|
|
25
|
+
<Canvas>
|
|
26
|
+
<Story
|
|
27
|
+
name="MaxLength - countdown"
|
|
28
|
+
args={{
|
|
29
|
+
name: 'input-text-limit-countdown',
|
|
30
|
+
maxlength: 5,
|
|
31
|
+
showLimit: true,
|
|
32
|
+
showLimitMode: 'countdown'
|
|
33
|
+
}}>
|
|
34
|
+
{Template.bind({})}
|
|
35
|
+
</Story>
|
|
36
|
+
</Canvas>
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { Canvas, Meta, Story, Source, ArgsTable } from '@storybook/addon-docs'
|
|
2
2
|
import { action } from '@storybook/addon-actions'
|
|
3
3
|
|
|
4
|
+
import { toGroup } from '../utils'
|
|
5
|
+
import ObjectUtilities from '../../utils/ObjectUtilities'
|
|
4
6
|
import VvTextarea from '../../components/VvTextarea/VvTextarea.vue'
|
|
7
|
+
import { VvTextareaProps } from '../../components/VvTextarea/VvTextarea'
|
|
5
8
|
|
|
6
9
|
<Meta title="Components/Textarea" component={VvTextarea} />
|
|
7
10
|
|
|
@@ -20,65 +23,44 @@ export const Template = (args, { argTypes }) => ({
|
|
|
20
23
|
`
|
|
21
24
|
})
|
|
22
25
|
|
|
26
|
+
# VvTextarea
|
|
27
|
+
|
|
28
|
+
`VvTextarea` is a multi-line text input element.
|
|
29
|
+
|
|
23
30
|
<Canvas>
|
|
24
31
|
<Story
|
|
25
32
|
name="vv-textarea"
|
|
26
33
|
args={{
|
|
27
|
-
|
|
28
|
-
id: 'vv-textarea',
|
|
29
|
-
name: 'vv-textarea',
|
|
30
|
-
placeholder: 'Prova input',
|
|
31
|
-
label: 'Campo di prova',
|
|
32
|
-
hintLabel: 'Hint label text',
|
|
33
|
-
autocomplete: 'off',
|
|
34
|
-
disabled: false,
|
|
35
|
-
readonly: false,
|
|
36
|
-
valid: false,
|
|
37
|
-
error: false,
|
|
38
|
-
loading: false,
|
|
39
|
-
loadingLabel: 'Ciao sono il loading',
|
|
40
|
-
icon: '',
|
|
41
|
-
iconPosition: null,
|
|
42
|
-
floating: false,
|
|
43
|
-
minlength: null,
|
|
34
|
+
...ObjectUtilities.propsToObject(VvTextareaProps),
|
|
44
35
|
maxlength: null
|
|
45
36
|
}}
|
|
46
37
|
argTypes={{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
},
|
|
71
|
-
table: { category: 'Attributes' }
|
|
72
|
-
},
|
|
73
|
-
minlength: {
|
|
74
|
-
description: 'Lunghezza minima testo',
|
|
75
|
-
table: { category: 'Attributes' }
|
|
76
|
-
},
|
|
77
|
-
maxlength: {
|
|
78
|
-
description: 'Lunghezza massima testo',
|
|
79
|
-
table: { category: 'Attributes' }
|
|
80
|
-
}
|
|
38
|
+
...toGroup(
|
|
39
|
+
['required', 'minlength', 'maxlength'],
|
|
40
|
+
'Data validation'
|
|
41
|
+
),
|
|
42
|
+
...toGroup(
|
|
43
|
+
[
|
|
44
|
+
'disabled',
|
|
45
|
+
'readonly',
|
|
46
|
+
'valid',
|
|
47
|
+
'validLabel',
|
|
48
|
+
'error',
|
|
49
|
+
'errorLabel',
|
|
50
|
+
'loading',
|
|
51
|
+
'loadingLabel'
|
|
52
|
+
],
|
|
53
|
+
'State'
|
|
54
|
+
),
|
|
55
|
+
...toGroup(['floating'], 'Floating'),
|
|
56
|
+
...toGroup(['modifiers'], 'Modifiers'),
|
|
57
|
+
...toGroup(['icon', 'iconPosition'], 'Icon'),
|
|
58
|
+
...toGroup(['hintLabel'], 'Hint text'),
|
|
59
|
+
...toGroup(['limit'], 'Text limit count'),
|
|
60
|
+
...toGroup(['autoclear'], 'Experimental feature')
|
|
81
61
|
}}>
|
|
82
62
|
{Template.bind({})}
|
|
83
63
|
</Story>
|
|
84
64
|
</Canvas>
|
|
65
|
+
|
|
66
|
+
<ArgsTable story="vv-textarea" />
|