sprintify-ui 0.0.145 → 0.0.147

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,280 @@
1
+ <template>
2
+ <div class="base-number relative">
3
+ <Transition
4
+ enter-active-class="transition duration-200 ease-out"
5
+ enter-from-class="transform scale-90 opacity-0"
6
+ enter-to-class="transform scale-100 opacity-100"
7
+ leave-active-class="transition duration-75 ease-in"
8
+ leave-from-class="transform scale-100 opacity-100"
9
+ leave-to-class="transform scale-90 opacity-0"
10
+ >
11
+ <div v-if="invalidInput" class="absolute left-0 top-full">
12
+ <div
13
+ class="mt-1 ml-1 rounded bg-red-500 px-2 py-1 text-xs font-medium text-white"
14
+ >
15
+ <span v-if="tooBig"> Maximum {{ max }} </span>
16
+ <span v-else-if="tooSmall"> Minimum {{ min }} </span>
17
+ <span v-else>
18
+ {{ $t('sui.maximum_x_decimal_places', { count: precision }) }}
19
+ </span>
20
+ </div>
21
+ </div>
22
+ </Transition>
23
+
24
+ <div
25
+ class="relative flex"
26
+ :class="[borderColor, borderless ? '' : 'rounded border']"
27
+ >
28
+ <input
29
+ v-model="valueInternal"
30
+ :required="required"
31
+ :name="name"
32
+ :step="step"
33
+ :placeholder="placeholder"
34
+ :disabled="disabled"
35
+ :min="min"
36
+ :max="max"
37
+ :class="[
38
+ ['full', 'left'].includes(rounded) ? 'rounded-l' : '',
39
+ invalidInput ? 'focus:ring-red-400' : 'focus:ring-primary-500',
40
+ ]"
41
+ class="w-full border-none focus:z-[1] focus:border-none focus:border-transparent focus:shadow-none focus:outline-none focus:ring-2 focus:ring-offset-1 disabled:cursor-not-allowed disabled:text-slate-300"
42
+ type="text"
43
+ @input="onInput"
44
+ @blur="onBlur"
45
+ @focus="onFocus"
46
+ @keydown="onKeydown"
47
+ />
48
+ <div class="border-l border-slate-300">
49
+ <button
50
+ type="button"
51
+ :class="[['full', 'right'].includes(rounded) ? 'rounded-tr' : '']"
52
+ class="block h-1/2 border-b border-slate-300 bg-white enabled:hover:bg-slate-100 disabled:cursor-not-allowed disabled:text-slate-400"
53
+ :disabled="disabled"
54
+ @click="increment"
55
+ >
56
+ <BaseIcon icon="mdi:chevron-up"></BaseIcon>
57
+ </button>
58
+ <button
59
+ type="button"
60
+ :class="[['full', 'right'].includes(rounded) ? 'rounded-br' : '']"
61
+ class="block h-1/2 bg-white enabled:hover:bg-slate-100 disabled:cursor-not-allowed disabled:text-slate-400"
62
+ :disabled="disabled"
63
+ @click="decrement"
64
+ >
65
+ <BaseIcon icon="mdi:chevron-down"></BaseIcon>
66
+ </button>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </template>
71
+
72
+ <script lang="ts" setup>
73
+ import { useField } from '@/composables/field';
74
+ import { round } from 'lodash';
75
+ import { PropType } from 'vue';
76
+
77
+ const AUTO_CORRECT_TIMEOUT = 2000;
78
+
79
+ const props = defineProps({
80
+ modelValue: {
81
+ default: undefined,
82
+ type: [Number, null] as PropType<number | null>,
83
+ },
84
+ /**
85
+ * The step of the input. Can be a number or null.
86
+ */
87
+ step: {
88
+ default: 1,
89
+ type: Number,
90
+ },
91
+ name: {
92
+ default: undefined,
93
+ type: String,
94
+ },
95
+ placeholder: {
96
+ default: '',
97
+ type: String,
98
+ },
99
+ disabled: {
100
+ default: false,
101
+ type: Boolean,
102
+ },
103
+ required: {
104
+ default: false,
105
+ type: Boolean,
106
+ },
107
+ min: {
108
+ default: undefined,
109
+ type: Number,
110
+ },
111
+ max: {
112
+ default: undefined,
113
+ type: Number,
114
+ },
115
+ hasError: {
116
+ default: false,
117
+ type: Boolean,
118
+ },
119
+ borderless: {
120
+ default: false,
121
+ type: Boolean,
122
+ },
123
+ rounded: {
124
+ default: 'full',
125
+ type: String as PropType<'full' | 'left' | 'right' | 'none'>,
126
+ },
127
+ });
128
+
129
+ const emit = defineEmits(['update:modelValue', 'focus', 'blur', 'keydown']);
130
+
131
+ const { hasErrorInternal, emitUpdate } = useField({
132
+ name: computed(() => props.name),
133
+ required: computed(() => props.required),
134
+ hasError: computed(() => props.hasError),
135
+ emit: emit,
136
+ });
137
+
138
+ const precision = computed(() => {
139
+ if (props.step === undefined) return 0;
140
+ if (props.step === 0) return 0;
141
+ const parts = props.step.toString().split('.');
142
+ if (parts.length === 1) return 0;
143
+ return parts[1].length;
144
+ });
145
+
146
+ const hasMax = computed(() => props.max !== undefined && props.max !== null);
147
+ const hasMin = computed(() => props.min !== undefined && props.min !== null);
148
+
149
+ function convertToNumber(
150
+ value: string | number | null | undefined
151
+ ): number | null {
152
+ if (value === null) return null;
153
+ let number = parseFloat(value + '');
154
+ if (Number.isNaN(number)) {
155
+ return null;
156
+ }
157
+ if (hasMax.value) {
158
+ number = Math.min(number, props.max as number);
159
+ }
160
+ if (hasMin.value) {
161
+ number = Math.max(number, props.min as number);
162
+ }
163
+ return round(number, precision.value);
164
+ }
165
+
166
+ const valueInternal = ref<null | string | number>(null);
167
+ const realValueInternal = computed<number | null>(() => {
168
+ return convertToNumber(valueInternal.value);
169
+ });
170
+ const invalidInput = computed(() => {
171
+ if (realValueInternal.value == null && valueInternal.value == '') {
172
+ return false;
173
+ }
174
+
175
+ return realValueInternal.value != valueInternal.value;
176
+ });
177
+ const tooBig = computed(() => {
178
+ if (valueInternal.value === null) return false;
179
+ return hasMax.value && valueInternal.value > (props.max as number);
180
+ });
181
+ const tooSmall = computed(() => {
182
+ if (valueInternal.value === null) return false;
183
+ return hasMin.value && valueInternal.value < (props.min as number);
184
+ });
185
+
186
+ valueInternal.value = convertToNumber(props.modelValue);
187
+
188
+ if (valueInternal.value != props.modelValue) {
189
+ emitUpdate(realValueInternal.value);
190
+ }
191
+
192
+ let timeoutId = undefined as undefined | number;
193
+
194
+ function onInput(event: any) {
195
+ clearTimeout(timeoutId);
196
+
197
+ let value = (event.target.value + '' ?? '').replace(/[^\d.-]/g, '');
198
+
199
+ // remove all n + 1 dots
200
+ const parts = value.split('.');
201
+ value = parts.shift() + '';
202
+
203
+ if (parts.length > 0) {
204
+ value += '.' + parts.join('');
205
+ }
206
+
207
+ valueInternal.value = value;
208
+
209
+ emitUpdate(realValueInternal.value);
210
+
211
+ timeoutId = setTimeout(() => {
212
+ updateInternalValueToRealValue();
213
+ }, AUTO_CORRECT_TIMEOUT);
214
+ }
215
+
216
+ function onBlur(e: Event) {
217
+ emit('blur', e);
218
+ updateInternalValueToRealValue();
219
+ }
220
+
221
+ function onFocus(e: Event) {
222
+ emit('focus', e);
223
+ }
224
+
225
+ function onKeydown(e: KeyboardEvent) {
226
+ if (e.key === 'ArrowUp') {
227
+ increment();
228
+ e.preventDefault();
229
+ } else if (e.key === 'ArrowDown') {
230
+ decrement();
231
+ e.preventDefault();
232
+ }
233
+ emit('keydown', e);
234
+ }
235
+
236
+ function updateInternalValueToRealValue() {
237
+ if (realValueInternal.value === null) {
238
+ valueInternal.value = '';
239
+ return;
240
+ }
241
+ valueInternal.value = round(realValueInternal.value ?? 0, precision.value);
242
+ }
243
+
244
+ function increment() {
245
+ if (props.disabled) return;
246
+ if (realValueInternal.value === null) {
247
+ valueInternal.value = 0;
248
+ } else {
249
+ const newValue = round(
250
+ realValueInternal.value + props.step,
251
+ precision.value
252
+ );
253
+ if (!hasMax.value || newValue <= (props.max as number)) {
254
+ valueInternal.value = newValue;
255
+ }
256
+ }
257
+ emitUpdate(realValueInternal.value);
258
+ }
259
+
260
+ function decrement() {
261
+ if (props.disabled) return;
262
+ if (realValueInternal.value === null) {
263
+ valueInternal.value = 0;
264
+ } else {
265
+ const newValue = round(
266
+ realValueInternal.value - props.step,
267
+ precision.value
268
+ );
269
+ if (!hasMin.value || newValue >= (props.min as number)) {
270
+ valueInternal.value = newValue;
271
+ }
272
+ }
273
+ emitUpdate(realValueInternal.value);
274
+ }
275
+
276
+ const borderColor = computed(() => {
277
+ if (hasErrorInternal.value) return 'border-red-500';
278
+ return 'border-slate-300';
279
+ });
280
+ </script>
@@ -39,6 +39,7 @@ import BaseFileUploader from './BaseFileUploader.vue';
39
39
  import BaseForm from './BaseForm.vue';
40
40
  import BaseHasMany from './BaseHasMany.vue';
41
41
  import { Icon as BaseIcon } from '@iconify/vue';
42
+ import BaseIconPicker from './BaseIconPicker.vue';
42
43
  import BaseInput from './BaseInput.vue';
43
44
  import BaseInputLabel from './BaseInputLabel.vue';
44
45
  import BaseInputPercent from './BaseInputPercent.vue';
@@ -53,6 +54,7 @@ import BaseModalSide from './BaseModalSide.vue';
53
54
  import BaseNavbar from './BaseNavbar.vue';
54
55
  import BaseNavbarItem from './BaseNavbarItem.vue';
55
56
  import BaseNavbarItemContent from './BaseNavbarItemContent.vue';
57
+ import BaseNumber from './BaseNumber.vue';
56
58
  import BasePagination from './BasePagination.vue';
57
59
  import BasePanel from './BasePanel.vue';
58
60
  import BasePassword from './BasePassword.vue';
@@ -126,6 +128,7 @@ export {
126
128
  BaseForm,
127
129
  BaseHasMany,
128
130
  BaseIcon,
131
+ BaseIconPicker,
129
132
  BaseInput,
130
133
  BaseInputLabel,
131
134
  BaseInputPercent,
@@ -140,6 +143,7 @@ export {
140
143
  BaseNavbar,
141
144
  BaseNavbarItem,
142
145
  BaseNavbarItemContent,
146
+ BaseNumber,
143
147
  BasePagination,
144
148
  BasePanel,
145
149
  BasePassword,
package/src/lang/en.json CHANGED
@@ -20,6 +20,9 @@
20
20
  "file_must_be_of_type": "The file must be of type",
21
21
  "filters": "Filters",
22
22
  "go_to_page": "Go to ",
23
+ "invalid_value": "Invalid value",
24
+ "just_now": "Just now",
25
+ "maximum_x_decimal_places": "Maximum 1 decimal place|Maximum {count} decimal places",
23
26
  "min_x_characters": "{x} characters minimum",
24
27
  "month": "Month",
25
28
  "next": "Next",
@@ -59,7 +62,6 @@
59
62
  "year": "Year",
60
63
  "yes_delete": "Yes, delete",
61
64
  "you_can_upload_up_to_n_files": "You can upload one file at most|You can upload up to {count} files",
62
- "you_cannot_select_more_than_x_items": "You can't select more than one item|You can't select more than {count} items",
63
- "just_now": "Just now"
65
+ "you_cannot_select_more_than_x_items": "You can't select more than one item|You can't select more than {count} items"
64
66
  }
65
67
  }
package/src/lang/fr.json CHANGED
@@ -20,6 +20,9 @@
20
20
  "file_must_be_of_type": "Le fichier doit être de type",
21
21
  "filters": "Filtres",
22
22
  "go_to_page": "Page",
23
+ "invalid_value": "Valeur invalide",
24
+ "just_now": "à l’instant",
25
+ "maximum_x_decimal_places": "Maximum 1 décimale|Maximum {count} décimales",
23
26
  "min_x_characters": "{x} caractères minimum",
24
27
  "month": "Mois",
25
28
  "next": "Suivant",
@@ -59,7 +62,6 @@
59
62
  "year": "Année",
60
63
  "yes_delete": "Oui, supprimer",
61
64
  "you_can_upload_up_to_n_files": "Vous pouvez télécharger un fichier au maximum|Vous pouvez télécharger jusqu'à {count} fichiers",
62
- "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",
63
- "just_now": "à l’instant"
65
+ "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"
64
66
  }
65
67
  }