@antify/ui 4.1.36 → 4.1.38

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.
Files changed (31) hide show
  1. package/dist/components/index.d.ts +3 -1
  2. package/dist/components/index.js +14 -0
  3. package/dist/components/index.mjs +4 -0
  4. package/dist/components/inputs/AntCheckbox.vue +37 -37
  5. package/dist/components/inputs/AntCountryInput.vue +366 -0
  6. package/dist/components/inputs/AntPhoneNumberInput.vue +347 -0
  7. package/dist/components/inputs/Elements/AntSelectMenu.vue +2 -0
  8. package/dist/components/inputs/__stories/AntCheckbox.stories.js +1 -1
  9. package/dist/components/inputs/__stories/AntCheckbox.stories.mjs +1 -1
  10. package/dist/components/inputs/__stories/AntCheckboxGroup.stories.d.ts +1 -0
  11. package/dist/components/inputs/__stories/AntCheckboxGroup.stories.js +35 -2
  12. package/dist/components/inputs/__stories/AntCheckboxGroup.stories.mjs +39 -1
  13. package/dist/components/inputs/__stories/AntCountryInput.stories.d.ts +16 -0
  14. package/dist/components/inputs/__stories/AntCountryInput.stories.js +286 -0
  15. package/dist/components/inputs/__stories/AntCountryInput.stories.mjs +289 -0
  16. package/dist/components/inputs/__stories/AntPhoneNumberInput.stories.d.ts +7 -0
  17. package/dist/components/inputs/__stories/AntPhoneNumberInput.stories.js +303 -0
  18. package/dist/components/inputs/__stories/AntPhoneNumberInput.stories.mjs +305 -0
  19. package/dist/components/inputs/__types/AntCountryInput.types.d.ts +11 -0
  20. package/dist/components/inputs/__types/AntCountryInput.types.js +1 -0
  21. package/dist/components/inputs/__types/AntCountryInput.types.mjs +0 -0
  22. package/dist/components/inputs/__types/index.d.ts +1 -0
  23. package/dist/components/inputs/__types/index.js +11 -0
  24. package/dist/components/inputs/__types/index.mjs +1 -0
  25. package/dist/constants/countries.d.ts +22 -0
  26. package/dist/constants/countries.js +2009 -0
  27. package/dist/constants/countries.mjs +2185 -0
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +11 -0
  30. package/dist/index.mjs +1 -0
  31. package/package.json +1 -1
@@ -0,0 +1,347 @@
1
+ <script lang="ts" setup>
2
+ import {
3
+ computed, watch,
4
+ } from 'vue';
5
+ import {
6
+ useVModel,
7
+ } from '@vueuse/core';
8
+ import AntField from '../forms/AntField.vue';
9
+ import AntCountryInput from './AntCountryInput.vue';
10
+ import AntBaseInput from './Elements/AntBaseInput.vue';
11
+ import {
12
+ Size, InputState, Grouped,
13
+ } from '../../enums';
14
+ import {
15
+ BaseInputType,
16
+ } from './Elements/__types';
17
+ import {
18
+ COUNTRIES, CountryValueKey, Locale,
19
+ } from '../../constants/countries';
20
+ import type {
21
+ Country,
22
+ } from '../../types';
23
+ import {
24
+ ref, nextTick,
25
+ } from 'vue';
26
+
27
+ const phoneInputNativeRef = ref<HTMLInputElement | null>(null);
28
+
29
+ defineOptions({
30
+ inheritAttrs: false,
31
+ });
32
+
33
+ const props = withDefaults(defineProps<{
34
+ modelValue: string | null;
35
+ countryValue: string | number | null;
36
+ countries?: Country[];
37
+
38
+ //Common Props
39
+ size?: Size;
40
+ state?: InputState;
41
+ disabled?: boolean;
42
+ readonly?: boolean;
43
+ skeleton?: boolean;
44
+
45
+ //AntField Props
46
+ label?: string;
47
+ description?: string;
48
+ messages?: string[];
49
+
50
+ //AntCountryInput Props
51
+ countryPlaceholder?: string;
52
+ searchPlaceholder?: string;
53
+ searchable?: boolean;
54
+ countryMaxHeight?: string;
55
+ countryValueKey?: CountryValueKey;
56
+ countryErrorMessage?: string;
57
+ countrySortable?: boolean;
58
+
59
+ //AntBaseInput Props
60
+ placeholder?: string;
61
+ nullable?: boolean;
62
+ locale?: Locale;
63
+ }>(), {
64
+ size: Size.md,
65
+ state: InputState.base,
66
+ searchable: true,
67
+ searchPlaceholder: 'Search country...',
68
+ countryPlaceholder: 'Select country',
69
+ placeholder: 'Enter phone number',
70
+ countryValueKey: CountryValueKey.dialCode,
71
+ countryErrorMessage: 'Please select a country code or start with "+"',
72
+ countrySortable: true,
73
+ messages: () => [],
74
+ nullable: true,
75
+ countries: () => COUNTRIES,
76
+ locale: Locale.en,
77
+ });
78
+
79
+ const emit = defineEmits([
80
+ 'update:modelValue',
81
+ 'update:countryValue',
82
+ 'select-country',
83
+ 'validate',
84
+ 'blur',
85
+ ]);
86
+
87
+ const _countryValue = useVModel(props, 'countryValue', emit);
88
+ const _phoneNumber = useVModel(props, 'modelValue', emit);
89
+
90
+ const updateFullValue = (countryId: string | number | null, rawPhone: string | null) => {
91
+ if (!rawPhone) {
92
+ _phoneNumber.value = null;
93
+
94
+ return;
95
+ }
96
+
97
+ const country = props.countries.find(c => String(c[props.countryValueKey]) === String(countryId));
98
+
99
+ if (country && !rawPhone.startsWith('+')) {
100
+ const digitsOnly = rawPhone.replace(/\D/g, '');
101
+ _phoneNumber.value = `${country.dialCode}${digitsOnly}`;
102
+ } else {
103
+ _phoneNumber.value = rawPhone;
104
+ }
105
+ };
106
+
107
+ const showCountryError = computed(() => {
108
+ const val = props.modelValue || '';
109
+
110
+ return props.countryValue == null && val.length > 0 && !val.startsWith('+');
111
+ });
112
+
113
+ const allMessages = computed(() => {
114
+ const msgs = [
115
+ ...(props.messages || []),
116
+ ];
117
+
118
+ if (showCountryError.value) {
119
+ msgs.push(props.countryErrorMessage);
120
+ }
121
+
122
+ return msgs;
123
+ });
124
+
125
+ const currentCountry = computed(() => {
126
+ return props.countries.find(c => String(c[props.countryValueKey]) === String(props.countryValue));
127
+ });
128
+
129
+ const sortedCountriesByDialCode = computed(() => {
130
+ return [
131
+ ...props.countries,
132
+ ].sort((a, b) => b.dialCode.length - a.dialCode.length);
133
+ });
134
+
135
+ const findCountryByPhone = (phone: string): Country | undefined => {
136
+ if (!phone.startsWith('+')) {
137
+ return undefined;
138
+ }
139
+
140
+ return sortedCountriesByDialCode.value.find(country => phone.startsWith(country.dialCode));
141
+ };
142
+
143
+ const formattedNumber = computed({
144
+ get: () => {
145
+ const fullVal = props.modelValue || '';
146
+ const country = currentCountry.value;
147
+
148
+ if (country && fullVal.startsWith(country.dialCode)) {
149
+ const shortNumber = fullVal.slice(country.dialCode.length);
150
+
151
+ return country.mask ? formatByMask(shortNumber, country.mask) : shortNumber;
152
+ }
153
+
154
+ return fullVal;
155
+ },
156
+ set: (val: string | null) => {
157
+ if (!val) {
158
+ _phoneNumber.value = null;
159
+
160
+ return;
161
+ }
162
+
163
+ if (val.startsWith('+')) {
164
+ const country = findCountryByPhone(val);
165
+
166
+ if (country) {
167
+ _countryValue.value = country[props.countryValueKey] as string | number;
168
+ }
169
+
170
+ _phoneNumber.value = val;
171
+
172
+ return;
173
+ }
174
+
175
+ updateFullValue(_countryValue.value, val);
176
+ },
177
+ });
178
+
179
+ const formatByMask = (value: string | null, mask: string): string | null => {
180
+ if (!value) {
181
+ return null;
182
+ }
183
+
184
+ const digits = value.replace(/\D/g, '');
185
+ if (digits.length === 0) {
186
+ return null;
187
+ }
188
+
189
+ let result = '';
190
+ let digitIndex = 0;
191
+
192
+ for (let i = 0; i < mask.length; i++) {
193
+ if (mask[i] === '#') {
194
+ if (digitIndex < digits.length) {
195
+ result += digits[digitIndex];
196
+ digitIndex++;
197
+ } else {
198
+ break;
199
+ }
200
+ } else if (digitIndex < digits.length) {
201
+ result += mask[i];
202
+ }
203
+ }
204
+
205
+ if (digitIndex < digits.length) {
206
+ result += digits.substring(digitIndex);
207
+ }
208
+
209
+ return result || null;
210
+ };
211
+
212
+ function onCountrySelect(country: Country) {
213
+ emit('select-country', country);
214
+
215
+ nextTick(() => {
216
+ phoneInputNativeRef.value?.focus();
217
+ });
218
+ }
219
+
220
+ function onKeyPress(event: KeyboardEvent) {
221
+ const charStr = event.key;
222
+ const target = event.target as HTMLInputElement;
223
+ const currentRawValue = target.value;
224
+
225
+ if (event.ctrlKey || event.metaKey || charStr.length > 1) {
226
+ return;
227
+ }
228
+
229
+ if (!/[\d+]/.test(charStr)) {
230
+ event.preventDefault();
231
+
232
+ return;
233
+ }
234
+
235
+ if (props.countryValue && charStr === '+') {
236
+ event.preventDefault();
237
+
238
+ return;
239
+ }
240
+
241
+ if (!props.countryValue && charStr === '+' && currentRawValue.length > 0) {
242
+ event.preventDefault();
243
+ }
244
+
245
+ if (charStr === '+' && currentRawValue.includes('+')) {
246
+ event.preventDefault();
247
+ }
248
+ }
249
+
250
+ function onPaste(event: ClipboardEvent) {
251
+ event.preventDefault();
252
+ const pasteData = event.clipboardData?.getData('text') || '';
253
+
254
+ if (!pasteData) {
255
+ return;
256
+ }
257
+
258
+ const cleanInput = pasteData.replace(/[^\d+]/g, '');
259
+
260
+ if (cleanInput.startsWith('+')) {
261
+ const country = findCountryByPhone(cleanInput);
262
+
263
+ if (country) {
264
+ _countryValue.value = country[props.countryValueKey] as string | number;
265
+ }
266
+ _phoneNumber.value = cleanInput;
267
+ } else {
268
+ updateFullValue(_countryValue.value, cleanInput);
269
+ }
270
+ }
271
+
272
+ watch(_countryValue, (newCountryId, oldCountryId) => {
273
+ if (newCountryId === oldCountryId) {
274
+ return;
275
+ }
276
+
277
+ const fullVal = props.modelValue || '';
278
+ const oldCountry = props.countries.find(c => String(c[props.countryValueKey]) === String(oldCountryId));
279
+ let body = fullVal;
280
+
281
+ if (oldCountry && fullVal.startsWith(oldCountry.dialCode)) {
282
+ body = fullVal.slice(oldCountry.dialCode.length);
283
+ }
284
+
285
+ updateFullValue(newCountryId, body);
286
+ });
287
+ </script>
288
+
289
+ <template>
290
+ <AntField
291
+ :label="label"
292
+ :messages="allMessages"
293
+ :state="showCountryError ? InputState.danger : state"
294
+ :size="size"
295
+ :skeleton="skeleton"
296
+ :description="description"
297
+ data-e2e="phone-input"
298
+ >
299
+ <div
300
+ class="flex relative w-full"
301
+ @click.prevent
302
+ >
303
+ <AntCountryInput
304
+ v-model="_countryValue"
305
+ :countries="countries"
306
+ :size="size"
307
+ :locale="locale"
308
+ :state="showCountryError ? InputState.danger : state"
309
+ :disabled="disabled"
310
+ :readonly="readonly"
311
+ :skeleton="skeleton"
312
+ :searchable="searchable"
313
+ :placeholder="countryPlaceholder"
314
+ :search-placeholder="searchPlaceholder"
315
+ :max-height="countryMaxHeight"
316
+ :is-grouped="true"
317
+ :grouped="Grouped.left"
318
+ class="w-fit flex-shrink-0"
319
+ :show-dial-code-in-menu="true"
320
+ :option-value-key="countryValueKey"
321
+ :sortable="countrySortable"
322
+ @select="onCountrySelect"
323
+ />
324
+
325
+ <AntBaseInput
326
+ v-model="formattedNumber"
327
+ v-model:input-ref="phoneInputNativeRef"
328
+ :nullable="nullable"
329
+ :type="BaseInputType.text"
330
+ :state="showCountryError ? InputState.danger : state"
331
+ :size="size"
332
+ :skeleton="skeleton"
333
+ v-bind="$attrs"
334
+ :disabled="disabled"
335
+ :readonly="readonly"
336
+ :placeholder="placeholder"
337
+ :grouped="Grouped.right"
338
+ wrapper-class="flex-grow"
339
+ class="-ml-px"
340
+ @validate="val => $emit('validate', val)"
341
+ @blur="e => $emit('blur', e)"
342
+ @keydown="onKeyPress"
343
+ @paste="onPaste"
344
+ />
345
+ </div>
346
+ </AntField>
347
+ </template>
@@ -30,6 +30,7 @@ const emit = defineEmits([
30
30
  'update:modelValue',
31
31
  'update:focused',
32
32
  'selectElement',
33
+ 'clickOutside',
33
34
  ]);
34
35
  const props = withDefaults(defineProps<{
35
36
  modelValue: string | string[] | number | number[] | null;
@@ -76,6 +77,7 @@ onClickOutside(floating, () => {
76
77
  }
77
78
 
78
79
  emit('update:open', false);
80
+ emit('clickOutside');
79
81
  });
80
82
 
81
83
  const _modelValue = useVModel(props, 'modelValue', emit);
@@ -55,7 +55,7 @@ const Docs = exports.Docs = {
55
55
  },
56
56
  template: `
57
57
  <div class="m-2">
58
- <AntCheckbox v-bind="args" v-model="value"/>
58
+ <AntCheckbox v-bind="args" v-model="value">Value</AntCheckbox>
59
59
  <span class="text-sm text-gray-500">Reactive value: {{ value }}</span>
60
60
  </div>
61
61
  `
@@ -54,7 +54,7 @@ export const Docs = {
54
54
  },
55
55
  template: `
56
56
  <div class="m-2">
57
- <AntCheckbox v-bind="args" v-model="value"/>
57
+ <AntCheckbox v-bind="args" v-model="value">Value</AntCheckbox>
58
58
  <span class="text-sm text-gray-500">Reactive value: {{ value }}</span>
59
59
  </div>
60
60
  `
@@ -4,3 +4,4 @@ declare const meta: Meta<typeof AntCheckboxGroup>;
4
4
  export default meta;
5
5
  type Story = StoryObj<typeof AntCheckboxGroup>;
6
6
  export declare const Docs: Story;
7
+ export declare const WithLongContent: Story;
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- module.exports = exports.Docs = void 0;
6
+ module.exports = exports.WithLongContent = exports.Docs = void 0;
7
7
  var _AntCheckboxGroup = _interopRequireDefault(require("../AntCheckboxGroup.vue"));
8
8
  var _enums = require("../../../enums");
9
9
  var _Direction = require("../../../enums/Direction.enum");
@@ -55,7 +55,7 @@ const Docs = exports.Docs = {
55
55
  };
56
56
  },
57
57
  template: `
58
- <AntCheckboxGroup v-bind="args" v-model="args.modelValue"/>
58
+ <AntCheckboxGroup v-bind="args" v-model="args.modelValue"/>
59
59
  `
60
60
  }),
61
61
  args: {
@@ -74,4 +74,37 @@ const Docs = exports.Docs = {
74
74
  value: "checkbox-4"
75
75
  }]
76
76
  }
77
+ };
78
+ const WithLongContent = exports.WithLongContent = {
79
+ render: args => ({
80
+ components: {
81
+ AntCheckboxGroup: _AntCheckboxGroup.default
82
+ },
83
+ setup() {
84
+ return {
85
+ args
86
+ };
87
+ },
88
+ template: `
89
+ <div class="w-[150px]">
90
+ <AntCheckboxGroup v-bind="args" v-model="args.modelValue"/>
91
+ </div>
92
+ `
93
+ }),
94
+ args: {
95
+ modelValue: [],
96
+ checkboxes: [{
97
+ label: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam",
98
+ value: "checkbox-1"
99
+ }, {
100
+ label: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam",
101
+ value: "checkbox-2"
102
+ }, {
103
+ label: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam",
104
+ value: "checkbox-3"
105
+ }, {
106
+ label: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam",
107
+ value: "checkbox-4"
108
+ }]
109
+ }
77
110
  };
@@ -53,7 +53,7 @@ export const Docs = {
53
53
  };
54
54
  },
55
55
  template: `
56
- <AntCheckboxGroup v-bind="args" v-model="args.modelValue"/>
56
+ <AntCheckboxGroup v-bind="args" v-model="args.modelValue"/>
57
57
  `
58
58
  }),
59
59
  args: {
@@ -78,3 +78,41 @@ export const Docs = {
78
78
  ]
79
79
  }
80
80
  };
81
+ export const WithLongContent = {
82
+ render: (args) => ({
83
+ components: {
84
+ AntCheckboxGroup
85
+ },
86
+ setup() {
87
+ return {
88
+ args
89
+ };
90
+ },
91
+ template: `
92
+ <div class="w-[150px]">
93
+ <AntCheckboxGroup v-bind="args" v-model="args.modelValue"/>
94
+ </div>
95
+ `
96
+ }),
97
+ args: {
98
+ modelValue: [],
99
+ checkboxes: [
100
+ {
101
+ label: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam",
102
+ value: "checkbox-1"
103
+ },
104
+ {
105
+ label: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam",
106
+ value: "checkbox-2"
107
+ },
108
+ {
109
+ label: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam",
110
+ value: "checkbox-3"
111
+ },
112
+ {
113
+ label: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam",
114
+ value: "checkbox-4"
115
+ }
116
+ ]
117
+ }
118
+ };
@@ -0,0 +1,16 @@
1
+ import { type Meta, type StoryObj } from '@storybook/vue3';
2
+ import AntCountryInput from '../AntCountryInput.vue';
3
+ declare const meta: Meta<typeof AntCountryInput>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof AntCountryInput>;
6
+ export declare const Docs: Story;
7
+ export declare const ValueKeyNumericCode: Story;
8
+ export declare const DefaultCountry: Story;
9
+ export declare const DefaultByNumericCode: Story;
10
+ export declare const Localization: Story;
11
+ export declare const WithoutSearch: Story;
12
+ export declare const WithoutFlags: Story;
13
+ export declare const GroupedMode: Story;
14
+ export declare const GermanEmptyState: Story;
15
+ export declare const Skeleton: Story;
16
+ export declare const summary: Story;