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.
@@ -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>
@@ -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
+ }