sprintify-ui 0.2.14 → 0.2.16
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/dist/sprintify-ui.es.js +7381 -7222
- package/dist/types/src/components/BaseGantt.vue.d.ts +2 -1
- package/dist/types/src/components/BaseInput.vue.d.ts +6 -1
- package/dist/types/src/components/BaseInputPercent.vue.d.ts +6 -1
- package/dist/types/src/components/BasePassword.vue.d.ts +6 -1
- package/dist/types/src/components/BaseTextarea.vue.d.ts +6 -1
- package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +6 -1
- package/dist/types/src/components/BaseUniqueCode.vue.d.ts +33 -0
- package/dist/types/src/components/index.d.ts +2 -1
- package/dist/types/src/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/BaseGantt.stories.js +6 -2
- package/src/components/BaseGantt.vue +2 -0
- package/src/components/BaseInput.stories.js +17 -0
- package/src/components/BaseInput.vue +15 -0
- package/src/components/BaseInputPercent.stories.js +16 -0
- package/src/components/BaseInputPercent.vue +16 -0
- package/src/components/BasePassword.stories.js +17 -0
- package/src/components/BasePassword.vue +15 -0
- package/src/components/BaseTextarea.stories.js +16 -0
- package/src/components/BaseTextarea.vue +16 -0
- package/src/components/BaseTextareaAutoresize.stories.js +16 -0
- package/src/components/BaseTextareaAutoresize.vue +16 -0
- package/src/components/BaseUniqueCode.stories.js +36 -0
- package/src/components/BaseUniqueCode.vue +209 -0
- package/src/components/index.ts +2 -0
- package/src/lang/en.json +2 -1
- package/src/lang/fr.json +2 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="unique-code">
|
|
3
|
+
<input
|
|
4
|
+
v-for="index in props.numberOfCharacters "
|
|
5
|
+
:key="index"
|
|
6
|
+
ref="codeInputs"
|
|
7
|
+
class="border-t-0 border-x-0 border-b-2 border-gray-300 w-[70px] h-[90px]
|
|
8
|
+
text-4xl font-normal text-gray-700 text-center p-0 m-0 mx-2 outline-none
|
|
9
|
+
duration-200 focus:outline-none focus:shadow-none focus:border-b-primary-500 focus:ring-0"
|
|
10
|
+
|
|
11
|
+
type="text"
|
|
12
|
+
maxlength="1"
|
|
13
|
+
:pattern="pattern"
|
|
14
|
+
:value="uniqueCode[index] || ''"
|
|
15
|
+
@input="handleCodeInput(index, $event)"
|
|
16
|
+
@paste="handleCodePaste(index, $event)"
|
|
17
|
+
@keydown="handleCodeKeyDown(index, $event)"
|
|
18
|
+
>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { ref, onMounted, nextTick, PropType } from 'vue';
|
|
24
|
+
|
|
25
|
+
const props = defineProps({
|
|
26
|
+
modelValue: {
|
|
27
|
+
required: true,
|
|
28
|
+
type: [String, Number, null] as PropType<string | number | null>,
|
|
29
|
+
},
|
|
30
|
+
numberOfCharacters: {
|
|
31
|
+
required: true,
|
|
32
|
+
type: Number,
|
|
33
|
+
},
|
|
34
|
+
type: {
|
|
35
|
+
default: "numeric",
|
|
36
|
+
type: String as PropType<'numeric' | 'alphanumeric'>,
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const emit = defineEmits(['update:modelValue']);
|
|
41
|
+
|
|
42
|
+
const pattern = computed(() => {
|
|
43
|
+
if (props.type === 'alphanumeric') {
|
|
44
|
+
return "[A-Za-z0-9]+";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return "[0-9]*";
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const form = reactive({
|
|
51
|
+
code: '',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const codeInputs = ref<(HTMLInputElement | null)[]>([]);
|
|
55
|
+
const uniqueCode = ref<string[]>(Array(props.numberOfCharacters).fill(''));
|
|
56
|
+
const prvIndex = ref(0);
|
|
57
|
+
|
|
58
|
+
watch(
|
|
59
|
+
() => form.code,
|
|
60
|
+
() => {
|
|
61
|
+
emit('update:modelValue', form.code);
|
|
62
|
+
},
|
|
63
|
+
{ immediate: true }
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Max length check for each input
|
|
67
|
+
const maxLengthCheck = (input: HTMLInputElement) => {
|
|
68
|
+
if (input.value.length > input.maxLength) {
|
|
69
|
+
input.value = input.value.slice(0, input.maxLength);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Handle code input
|
|
74
|
+
const handleCodeInput = (index: number, event: Event) => {
|
|
75
|
+
const input = event.target as HTMLInputElement;
|
|
76
|
+
maxLengthCheck(input);
|
|
77
|
+
|
|
78
|
+
const nextIndex = index < props.numberOfCharacters ? index + 1 : index;
|
|
79
|
+
|
|
80
|
+
if (index >= 0 && index <= props.numberOfCharacters) {
|
|
81
|
+
if (props.type === 'numeric') {
|
|
82
|
+
const inputValue = parseInt(input.value);
|
|
83
|
+
if (isNaN(inputValue) || inputValue < 0 || inputValue > 9) {
|
|
84
|
+
input.value = '';
|
|
85
|
+
uniqueCode.value[index] = '';
|
|
86
|
+
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (input.value !== '') {
|
|
92
|
+
uniqueCode.value[index] = input.value;
|
|
93
|
+
codeInputs.value[nextIndex - 1]?.focus();
|
|
94
|
+
}
|
|
95
|
+
console.log('ccc');
|
|
96
|
+
|
|
97
|
+
form.code += input.value;
|
|
98
|
+
uniqueCode.value[index] = input.value;
|
|
99
|
+
if (form.code.length > props.numberOfCharacters) {
|
|
100
|
+
form.code = form.code.slice(0, props.numberOfCharacters);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Handle code keydown events
|
|
106
|
+
function handleCodeKeyDown(index: number, event: KeyboardEvent) {
|
|
107
|
+
const input = event.target as HTMLInputElement;
|
|
108
|
+
|
|
109
|
+
if (index === 0 && event.key === 'Backspace') {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (index === props.numberOfCharacters + 1) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
prvIndex.value = index - 1;
|
|
118
|
+
|
|
119
|
+
if (event.key === 'Backspace') {
|
|
120
|
+
if (index > 0 && index <= props.numberOfCharacters) {
|
|
121
|
+
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
codeInputs.value[prvIndex.value - 1]?.focus();
|
|
124
|
+
}, 10);
|
|
125
|
+
|
|
126
|
+
input.value = ''
|
|
127
|
+
form.code = form.code.slice(0, prvIndex.value);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (event.key === 'ArrowRight') {
|
|
132
|
+
codeInputs.value[index]?.focus();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (event.key === 'ArrowLeft') {
|
|
136
|
+
codeInputs.value[prvIndex.value - 1]?.focus();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Handle code paste
|
|
141
|
+
const handleCodePaste = (index: number, event: ClipboardEvent) => {
|
|
142
|
+
const pastedData = event.clipboardData?.getData('text/plain') || '';
|
|
143
|
+
|
|
144
|
+
let data = [] as string[];
|
|
145
|
+
|
|
146
|
+
if (props.type === 'alphanumeric') {
|
|
147
|
+
data = pastedData.split('').filter((char) => /^[A-Za-z0-9]+$/.test(char));
|
|
148
|
+
} else {
|
|
149
|
+
data = pastedData.split('').filter((char) => !isNaN(Number(char)));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Reset code
|
|
153
|
+
form.code = '';
|
|
154
|
+
|
|
155
|
+
for (let i = 0; i < data.length && i < props.numberOfCharacters; i++) {
|
|
156
|
+
uniqueCode.value[index + i] = data[i];
|
|
157
|
+
form.code += data[i];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Focus the next input after paste
|
|
161
|
+
codeInputs.value[index + data.length]?.focus();
|
|
162
|
+
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Focus the first input on component mount
|
|
167
|
+
onMounted(async () => {
|
|
168
|
+
await nextTick();
|
|
169
|
+
codeInputs.value = Array.from(
|
|
170
|
+
{ length: props.numberOfCharacters },
|
|
171
|
+
(_, index) => codeInputs.value[index]
|
|
172
|
+
);
|
|
173
|
+
const firstInput = codeInputs.value[0];
|
|
174
|
+
if (firstInput !== null) {
|
|
175
|
+
firstInput.focus();
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
</script>
|
|
180
|
+
|
|
181
|
+
<style scoped>
|
|
182
|
+
/* .unique-code input {
|
|
183
|
+
appearance: none;
|
|
184
|
+
-moz-appearance: textfield;
|
|
185
|
+
-webkit-appearance: textfield;
|
|
186
|
+
border: none;
|
|
187
|
+
border-bottom: 2px solid #e2e8f0;
|
|
188
|
+
width: 70px;
|
|
189
|
+
height: 90px;
|
|
190
|
+
font-size: 4rem;
|
|
191
|
+
font-weight: normal;
|
|
192
|
+
color: #484747;
|
|
193
|
+
text-align: center;
|
|
194
|
+
padding: 0;
|
|
195
|
+
margin: 0 .5rem;
|
|
196
|
+
outline: none;
|
|
197
|
+
transition: border-color 0.2s;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.unique-code input:focus {
|
|
201
|
+
box-shadow: none;
|
|
202
|
+
border-bottom-color: #667eea;
|
|
203
|
+
}
|
|
204
|
+
.unique-code input::-webkit-inner-spin-button,
|
|
205
|
+
.unique-code input::-webkit-outer-spin-button {
|
|
206
|
+
-webkit-appearance: none;
|
|
207
|
+
margin: 0;
|
|
208
|
+
} */
|
|
209
|
+
</style>
|
package/src/components/index.ts
CHANGED
|
@@ -88,6 +88,7 @@ import BaseTextarea from './BaseTextarea.vue';
|
|
|
88
88
|
import BaseTextareaAutoresize from './BaseTextareaAutoresize.vue';
|
|
89
89
|
import BaseTimeline from './BaseTimeline.vue';
|
|
90
90
|
import BaseTimelineItem from './BaseTimelineItem.vue';
|
|
91
|
+
import BaseUniqueCode from './BaseUniqueCode.vue';
|
|
91
92
|
|
|
92
93
|
import BaseLayoutStacked from './BaseLayoutStacked.vue';
|
|
93
94
|
import BaseLayoutStackedConfigurable from './BaseLayoutStackedConfigurable.vue';
|
|
@@ -185,6 +186,7 @@ export {
|
|
|
185
186
|
BaseTextareaAutoresize,
|
|
186
187
|
BaseTimeline,
|
|
187
188
|
BaseTimelineItem,
|
|
189
|
+
BaseUniqueCode,
|
|
188
190
|
BaseLayoutStacked,
|
|
189
191
|
BaseLayoutStackedConfigurable,
|
|
190
192
|
BaseLayoutSidebar,
|
package/src/lang/en.json
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"and": "and",
|
|
11
11
|
"apply": "Apply",
|
|
12
12
|
"apply_filters": "Apply filters",
|
|
13
|
+
"authentication_code": "Authentication code",
|
|
13
14
|
"autocomplete_placeholder": "Type to start your search",
|
|
14
15
|
"cancel": "Cancel",
|
|
15
16
|
"city": "City",
|
|
@@ -84,4 +85,4 @@
|
|
|
84
85
|
"you_can_upload_up_to_n_files": "You can upload one file at most|You can upload up to {count} files",
|
|
85
86
|
"you_cannot_select_more_than_x_items": "You can't select more than one item|You can't select more than {count} items"
|
|
86
87
|
}
|
|
87
|
-
}
|
|
88
|
+
}
|
package/src/lang/fr.json
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"and": "et",
|
|
11
11
|
"apply": "Appliquer",
|
|
12
12
|
"apply_filters": "Appliquer les filtres",
|
|
13
|
+
"authentication_code": "Code d'authentification",
|
|
13
14
|
"autocomplete_placeholder": "Taper pour lancer votre recherche",
|
|
14
15
|
"cancel": "Annuler",
|
|
15
16
|
"city": "Ville",
|
|
@@ -84,4 +85,4 @@
|
|
|
84
85
|
"you_can_upload_up_to_n_files": "Vous pouvez télécharger un fichier au maximum|Vous pouvez télécharger jusqu'à {count} fichiers",
|
|
85
86
|
"you_cannot_select_more_than_x_items": "Vous ne pouvez pas sélectionner plus de un élément|Vous ne pouvez pas sélectionner plus de {count} éléments"
|
|
86
87
|
}
|
|
87
|
-
}
|
|
88
|
+
}
|