@jacksonavila/phone-lib 2.0.0

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,204 @@
1
+ import React, { useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
2
+ import PhoneLib from './phone-lib.js';
3
+
4
+ // Nota: Importa './phone-lib.css' en tu aplicación principal o aquí:
5
+ // import './phone-lib.css';
6
+
7
+ /**
8
+ * Componente React para PhoneLib
9
+ *
10
+ * @example
11
+ * import PhoneLibReact from './phone-lib-react';
12
+ *
13
+ * function App() {
14
+ * return (
15
+ * <PhoneLibReact
16
+ * initialCountry="CO"
17
+ * onCountryChange={(country, dialCode) => console.log(country)}
18
+ * onPhoneChange={(phone, isValid) => console.log(phone)}
19
+ * />
20
+ * );
21
+ * }
22
+ */
23
+ const PhoneLibReact = forwardRef((props, ref) => {
24
+ const containerRef = useRef(null);
25
+ const phoneLibRef = useRef(null);
26
+
27
+ const {
28
+ containerId,
29
+ containerClassName,
30
+ initialCountry = 'US',
31
+ preferredCountries = [],
32
+ showHint = true,
33
+ layout = 'integrated',
34
+ showDialCode = true,
35
+ customClasses = {},
36
+ customStyles = {},
37
+ autoDetectCountry = false,
38
+ validateOnInput = false,
39
+ disabledCountries = [],
40
+ onlyCountries = [],
41
+ excludeCountries = [],
42
+ readonly = false,
43
+ disabled = false,
44
+ placeholder = null,
45
+ countryLabel = 'País',
46
+ dialCodeLabel = 'Código',
47
+ phoneLabel = 'Número de teléfono',
48
+ messages = {},
49
+ ariaLabels = {},
50
+ onCountryChange,
51
+ onPhoneChange,
52
+ onValidationChange,
53
+ onFocus,
54
+ onBlur,
55
+ ...restProps
56
+ } = props;
57
+
58
+ // Inicializar PhoneLib cuando el componente se monta
59
+ useEffect(() => {
60
+ if (!containerRef.current) return;
61
+
62
+ // Crear contenedor si no existe
63
+ if (!containerRef.current.id && containerId) {
64
+ containerRef.current.id = containerId;
65
+ }
66
+
67
+ // Inicializar PhoneLib
68
+ phoneLibRef.current = new PhoneLib(containerRef.current, {
69
+ initialCountry,
70
+ preferredCountries,
71
+ showHint,
72
+ layout,
73
+ showDialCode,
74
+ customClasses,
75
+ customStyles,
76
+ autoDetectCountry,
77
+ validateOnInput,
78
+ disabledCountries,
79
+ onlyCountries,
80
+ excludeCountries,
81
+ readonly,
82
+ disabled,
83
+ placeholder,
84
+ countryLabel,
85
+ dialCodeLabel,
86
+ phoneLabel,
87
+ messages,
88
+ ariaLabels,
89
+ onCountryChange,
90
+ onPhoneChange,
91
+ onValidationChange,
92
+ onFocus,
93
+ onBlur
94
+ });
95
+
96
+ // Cleanup al desmontar
97
+ return () => {
98
+ if (phoneLibRef.current) {
99
+ phoneLibRef.current.destroy();
100
+ phoneLibRef.current = null;
101
+ }
102
+ };
103
+ }, []); // Solo ejecutar una vez al montar
104
+
105
+ // Actualizar opciones cuando cambian las props
106
+ useEffect(() => {
107
+ if (!phoneLibRef.current) return;
108
+
109
+ phoneLibRef.current.updateOptions({
110
+ initialCountry,
111
+ preferredCountries,
112
+ showHint,
113
+ layout,
114
+ showDialCode,
115
+ customClasses,
116
+ customStyles,
117
+ autoDetectCountry,
118
+ validateOnInput,
119
+ disabledCountries,
120
+ onlyCountries,
121
+ excludeCountries,
122
+ readonly,
123
+ disabled,
124
+ placeholder,
125
+ countryLabel,
126
+ dialCodeLabel,
127
+ phoneLabel,
128
+ messages,
129
+ ariaLabels
130
+ });
131
+ }, [
132
+ initialCountry,
133
+ preferredCountries,
134
+ showHint,
135
+ layout,
136
+ showDialCode,
137
+ customClasses,
138
+ customStyles,
139
+ autoDetectCountry,
140
+ validateOnInput,
141
+ disabledCountries,
142
+ onlyCountries,
143
+ excludeCountries,
144
+ readonly,
145
+ disabled,
146
+ placeholder,
147
+ countryLabel,
148
+ dialCodeLabel,
149
+ phoneLabel,
150
+ messages,
151
+ ariaLabels
152
+ ]);
153
+
154
+ // Actualizar callbacks cuando cambian
155
+ useEffect(() => {
156
+ if (!phoneLibRef.current) return;
157
+ phoneLibRef.current.options.onCountryChange = onCountryChange;
158
+ phoneLibRef.current.options.onPhoneChange = onPhoneChange;
159
+ phoneLibRef.current.options.onValidationChange = onValidationChange;
160
+ phoneLibRef.current.options.onFocus = onFocus;
161
+ phoneLibRef.current.options.onBlur = onBlur;
162
+ }, [onCountryChange, onPhoneChange, onValidationChange, onFocus, onBlur]);
163
+
164
+ // Exponer métodos de PhoneLib a través del ref
165
+ useImperativeHandle(ref, () => ({
166
+ // Métodos de lectura
167
+ getCountry: () => phoneLibRef.current?.getCountry(),
168
+ getDialCode: () => phoneLibRef.current?.getDialCode(),
169
+ getRaw: () => phoneLibRef.current?.getRaw(),
170
+ getE164: () => phoneLibRef.current?.getE164(),
171
+ isValid: () => phoneLibRef.current?.isValid(),
172
+ formatInternational: () => phoneLibRef.current?.formatInternational(),
173
+ formatNational: () => phoneLibRef.current?.formatNational(),
174
+ formatRFC3966: () => phoneLibRef.current?.formatRFC3966(),
175
+ getNumberType: () => phoneLibRef.current?.getNumberType(),
176
+ getInfo: () => phoneLibRef.current?.getInfo(),
177
+ getCountryMetadata: () => phoneLibRef.current?.getCountryMetadata(),
178
+
179
+ // Métodos de control
180
+ setCountry: (iso2) => phoneLibRef.current?.setCountry(iso2),
181
+ setPhoneNumber: (number) => phoneLibRef.current?.setPhoneNumber(number),
182
+ setValue: (country, number) => phoneLibRef.current?.setValue(country, number),
183
+ enable: () => phoneLibRef.current?.enable(),
184
+ disable: () => phoneLibRef.current?.disable(),
185
+ reset: () => phoneLibRef.current?.reset(),
186
+ destroy: () => phoneLibRef.current?.destroy(),
187
+ updateOptions: (options) => phoneLibRef.current?.updateOptions(options),
188
+
189
+ // Acceso directo a la instancia
190
+ instance: phoneLibRef.current
191
+ }), []);
192
+
193
+ return (
194
+ <div
195
+ ref={containerRef}
196
+ className={containerClassName}
197
+ {...restProps}
198
+ />
199
+ );
200
+ });
201
+
202
+ PhoneLibReact.displayName = 'PhoneLibReact';
203
+
204
+ export default PhoneLibReact;
package/phone-lib.css ADDED
@@ -0,0 +1,377 @@
1
+ /* PhoneLib - Estilos básicos */
2
+
3
+ .phone-lib-wrapper {
4
+ display: flex;
5
+ align-items: stretch;
6
+ width: 100%;
7
+ max-width: 400px;
8
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
9
+ "Helvetica Neue", Arial, sans-serif;
10
+ font-size: 16px;
11
+ line-height: 1.5;
12
+ }
13
+
14
+ .phone-lib-dropdown-container {
15
+ position: relative;
16
+ flex-shrink: 0;
17
+ }
18
+
19
+ .phone-lib-dropdown-button {
20
+ display: flex;
21
+ align-items: center;
22
+ gap: 8px;
23
+ padding: 12px 16px;
24
+ background: #ffffff;
25
+ border: 2px solid #e0e0e0;
26
+ border-right: none;
27
+ border-radius: 8px 0 0 8px;
28
+ cursor: pointer;
29
+ transition: all 0.2s ease;
30
+ font-size: 16px;
31
+ min-width: 120px;
32
+ height: 100%;
33
+ }
34
+
35
+ .phone-lib-dropdown-button:hover {
36
+ background: #f5f5f5;
37
+ border-color: #b0b0b0;
38
+ }
39
+
40
+ .phone-lib-dropdown-button.active {
41
+ border-color: #4a90e2;
42
+ background: #f0f7ff;
43
+ }
44
+
45
+ .phone-lib-flag {
46
+ display: inline-flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ line-height: 1;
50
+ vertical-align: middle;
51
+ }
52
+
53
+ .phone-lib-flag-img {
54
+ width: 20px;
55
+ height: 15px;
56
+ object-fit: cover;
57
+ border-radius: 2px;
58
+ display: inline-block;
59
+ vertical-align: middle;
60
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
61
+ }
62
+
63
+ .phone-lib-dial-code {
64
+ font-weight: 500;
65
+ color: #333;
66
+ }
67
+
68
+ .phone-lib-arrow {
69
+ font-size: 10px;
70
+ color: #666;
71
+ margin-left: auto;
72
+ transition: transform 0.2s ease;
73
+ }
74
+
75
+ .phone-lib-dropdown-button.active .phone-lib-arrow {
76
+ transform: rotate(180deg);
77
+ }
78
+
79
+ .phone-lib-dropdown-menu {
80
+ position: absolute;
81
+ top: calc(100% + 4px);
82
+ left: 0;
83
+ right: 0;
84
+ background: #ffffff;
85
+ border: 2px solid #e0e0e0;
86
+ border-radius: 8px;
87
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
88
+ z-index: 1000;
89
+ max-height: 400px;
90
+ overflow: hidden;
91
+ display: flex;
92
+ flex-direction: column;
93
+ }
94
+
95
+ .phone-lib-countries-list {
96
+ overflow-y: auto;
97
+ max-height: 400px;
98
+ }
99
+
100
+ .phone-lib-country-item {
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 12px;
104
+ padding: 12px 16px;
105
+ cursor: pointer;
106
+ transition: background-color 0.15s ease;
107
+ border-bottom: 1px solid #f0f0f0;
108
+ }
109
+
110
+ .phone-lib-country-item:last-child {
111
+ border-bottom: none;
112
+ }
113
+
114
+ .phone-lib-country-item:hover {
115
+ background: #f5f5f5;
116
+ }
117
+
118
+ .phone-lib-country-item.selected {
119
+ background: #e8f4fd;
120
+ font-weight: 500;
121
+ }
122
+
123
+ .phone-lib-country-item .phone-lib-flag {
124
+ flex-shrink: 0;
125
+ display: inline-flex;
126
+ align-items: center;
127
+ justify-content: center;
128
+ width: 24px;
129
+ height: 18px;
130
+ }
131
+
132
+ .phone-lib-country-item .phone-lib-flag-img {
133
+ width: 24px;
134
+ height: 18px;
135
+ object-fit: cover;
136
+ border-radius: 2px;
137
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1);
138
+ }
139
+
140
+ .phone-lib-country-name {
141
+ flex: 1;
142
+ color: #333;
143
+ }
144
+
145
+ .phone-lib-country-dial-code {
146
+ color: #666;
147
+ font-size: 14px;
148
+ font-weight: 500;
149
+ }
150
+
151
+ .phone-lib-input-container {
152
+ flex: 1;
153
+ position: relative;
154
+ }
155
+
156
+ .phone-lib-input {
157
+ width: 100%;
158
+ padding: 12px 16px;
159
+ border: 2px solid #e0e0e0;
160
+ border-left: none;
161
+ border-radius: 0 8px 8px 0;
162
+ font-size: 16px;
163
+ outline: none;
164
+ transition: all 0.2s ease;
165
+ box-sizing: border-box;
166
+ }
167
+
168
+ .phone-lib-input:focus {
169
+ border-color: #4a90e2;
170
+ border-left: 2px solid #4a90e2;
171
+ }
172
+
173
+ .phone-lib-input-invalid {
174
+ border-color: #e74c3c !important;
175
+ }
176
+
177
+ .phone-lib-input-valid {
178
+ border-color: #27ae60 !important;
179
+ }
180
+
181
+ .phone-lib-hint {
182
+ position: absolute;
183
+ top: calc(100% + 4px);
184
+ left: 0;
185
+ font-size: 12px;
186
+ padding: 4px 8px;
187
+ border-radius: 4px;
188
+ margin-top: 4px;
189
+ }
190
+
191
+ .phone-lib-hint.valid {
192
+ color: #27ae60;
193
+ background: #e8f5e9;
194
+ }
195
+
196
+ .phone-lib-hint.invalid {
197
+ color: #e74c3c;
198
+ background: #ffeaea;
199
+ }
200
+
201
+ /* Estados disabled y readonly */
202
+ .phone-lib-disabled .phone-lib-dropdown-button,
203
+ .phone-lib-disabled .phone-lib-input,
204
+ .phone-lib-disabled .phone-lib-input-separated {
205
+ opacity: 0.6;
206
+ cursor: not-allowed;
207
+ background: #f5f5f5;
208
+ }
209
+
210
+ .phone-lib-disabled .phone-lib-dropdown-button:hover {
211
+ background: #f5f5f5;
212
+ border-color: #e0e0e0;
213
+ }
214
+
215
+ .phone-lib-readonly .phone-lib-input,
216
+ .phone-lib-readonly .phone-lib-input-separated {
217
+ background: #f9f9f9;
218
+ cursor: default;
219
+ }
220
+
221
+ .phone-lib-country-item.disabled {
222
+ opacity: 0.5;
223
+ cursor: not-allowed;
224
+ pointer-events: none;
225
+ }
226
+
227
+ .phone-lib-country-item.disabled:hover {
228
+ background: transparent;
229
+ }
230
+
231
+ /* Layout Separado - Campos separados */
232
+ .phone-lib-layout-separated {
233
+ flex-direction: column;
234
+ max-width: 100%;
235
+ }
236
+
237
+ .phone-lib-separated-row {
238
+ display: grid;
239
+ grid-template-columns: 2fr 1fr 2fr;
240
+ gap: 12px;
241
+ width: 100%;
242
+ }
243
+
244
+ .phone-lib-field-group {
245
+ display: flex;
246
+ flex-direction: column;
247
+ gap: 6px;
248
+ }
249
+
250
+ .phone-lib-field-group-phone {
251
+ grid-column: span 1;
252
+ }
253
+
254
+ .phone-lib-label {
255
+ font-size: 14px;
256
+ font-weight: 500;
257
+ color: #333;
258
+ margin-bottom: 4px;
259
+ }
260
+
261
+ .phone-lib-dropdown-button-separated {
262
+ display: flex;
263
+ align-items: center;
264
+ gap: 8px;
265
+ padding: 12px 16px;
266
+ background: #ffffff;
267
+ border: 2px solid #e0e0e0;
268
+ border-radius: 8px;
269
+ cursor: pointer;
270
+ transition: all 0.2s ease;
271
+ font-size: 16px;
272
+ width: 100%;
273
+ text-align: left;
274
+ }
275
+
276
+ .phone-lib-dropdown-button-separated:hover {
277
+ background: #f5f5f5;
278
+ border-color: #b0b0b0;
279
+ }
280
+
281
+ .phone-lib-dropdown-button-separated.active {
282
+ border-color: #4a90e2;
283
+ background: #f0f7ff;
284
+ }
285
+
286
+ .phone-lib-country-name-display {
287
+ flex: 1;
288
+ color: #333;
289
+ font-weight: 500;
290
+ }
291
+
292
+ .phone-lib-dial-code-input {
293
+ width: 100%;
294
+ padding: 12px 16px;
295
+ border: 2px solid #e0e0e0;
296
+ border-radius: 8px;
297
+ font-size: 16px;
298
+ outline: none;
299
+ transition: all 0.2s ease;
300
+ box-sizing: border-box;
301
+ background: #f5f5f5;
302
+ color: #666;
303
+ cursor: not-allowed;
304
+ }
305
+
306
+ .phone-lib-input-separated {
307
+ width: 100%;
308
+ padding: 12px 16px;
309
+ border: 2px solid #e0e0e0;
310
+ border-radius: 8px;
311
+ font-size: 16px;
312
+ outline: none;
313
+ transition: all 0.2s ease;
314
+ box-sizing: border-box;
315
+ }
316
+
317
+ .phone-lib-input-separated:focus {
318
+ border-color: #4a90e2;
319
+ }
320
+
321
+ .phone-lib-input-separated.phone-lib-input-invalid {
322
+ border-color: #e74c3c !important;
323
+ }
324
+
325
+ .phone-lib-input-separated.phone-lib-input-valid {
326
+ border-color: #27ae60 !important;
327
+ }
328
+
329
+ /* Responsive para layout separado */
330
+ @media (max-width: 768px) {
331
+ .phone-lib-separated-row {
332
+ grid-template-columns: 1fr;
333
+ }
334
+
335
+ .phone-lib-field-group-phone {
336
+ grid-column: span 1;
337
+ }
338
+ }
339
+
340
+ /* Responsive */
341
+ @media (max-width: 480px) {
342
+ .phone-lib-wrapper {
343
+ flex-direction: column;
344
+ max-width: 100%;
345
+ }
346
+
347
+ .phone-lib-dropdown-button {
348
+ border-right: 2px solid #e0e0e0;
349
+ border-radius: 8px 8px 0 0;
350
+ width: 100%;
351
+ }
352
+
353
+ .phone-lib-dropdown-button.active {
354
+ border-radius: 8px 8px 0 0;
355
+ }
356
+
357
+ .phone-lib-input {
358
+ border-left: 2px solid #e0e0e0;
359
+ border-top: none;
360
+ border-radius: 0 0 8px 8px;
361
+ }
362
+
363
+ .phone-lib-input:focus {
364
+ border-top: 2px solid #4a90e2;
365
+ border-left: 2px solid #4a90e2;
366
+ }
367
+
368
+ .phone-lib-dropdown-menu {
369
+ position: fixed;
370
+ top: auto;
371
+ bottom: 0;
372
+ left: 0;
373
+ right: 0;
374
+ max-height: 50vh;
375
+ border-radius: 16px 16px 0 0;
376
+ }
377
+ }