feat: telephone input for every country

This commit is contained in:
Sebastian Pravda 2023-02-02 18:03:19 +01:00 committed by EETagent
parent 3f5e16fa7e
commit bde8e8da99
2 changed files with 129 additions and 52 deletions

View file

@ -1,39 +1,100 @@
<script lang="ts">
import Telephone from '../icons/Telephone.svelte';
import TextField from './TextField.svelte';
import { tippy } from 'svelte-tippy';
import 'tippy.js/dist/tippy.css';
let helperText: string = 'Zadejte platný telefon s předvolbou. Například +420 123 456 789';
export let placeholder: string = '';
const helperText: string = 'Zadejte platný telefon s předvolbou. Například +420 123 456 789';
export let placeholder: string = ''; // TODO
export let value: string = '';
export let error: string = '';
import TelInput, { normalizedCountries } from 'svelte-tel-input';
import type { NormalizedTelNumber, CountryCode, E164Number } from 'svelte-tel-input/types';
// Phone Number formatting
$: {
let x = value.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,3})(\d{0,3})/)!;
value =
(x[1] ? '+' + x[1] : '') +
(x[2] ? ' ' + x[2] : '') +
(x[3] ? ' ' + x[3] : '') +
(x[4] ? ' ' + x[4] : '');
}
// Any Country Code Alpha-2 (ISO 3166)
let country: CountryCode | null = 'CZ';
// You must use E164 number format. It's guarantee the parsing and storing consistency.
export let value: E164Number | null = '+36301234567';
// Validity
let valid = true;
export let invalid: boolean = false;
$: invalid = !valid;
// Optional - Extended details about the parsed phone number
let parsedTelInput: NormalizedTelNumber | null = null;
let selectedCountry: string | null = country;
const countrySelect = (e: any) => {
selectedCountry = e.target.value;
// @ts-ignore
country = selectedCountry;
value = null;
};
const isTooltip = helperText ? tippy : () => {};
$: tooltipDelay = invalid ? 0 : 1000;
</script>
<TextField
bind:error
bind:value
on:keydown
on:keyup
on:change
type="tel"
{placeholder}
{helperText}
icon
<div class="wrapper w-full h-full flex"
use:isTooltip={{
content: helperText,
placement: 'top',
showOnCreate: false,
delay: tooltipDelay
}}
>
<div slot="icon" class="flex items-center justify-center">
<Telephone />
<select
class="countrySelect {!valid && 'invalid'}"
aria-label="Default select example"
name="Country"
bind:value={selectedCountry}
on:input={countrySelect}
>
<option value={null} hidden={selectedCountry !== null}>Země</option>
{#each normalizedCountries as country (country.id)}
<option
value={country.iso2}
selected={country.iso2 === selectedCountry}
aria-selected={country.iso2 === selectedCountry}
>
{country.iso2} (+{country.dialCode})
</option>
{/each}
</select>
<div class="ml-2 inputWrapper">
<TelInput bind:country bind:value bind:valid bind:parsedTelInput class="basic-tel-input {!valid ? 'invalid' : '' }" />
<span>
<Telephone />
</span>
</div>
</TextField>
</div>
<style lang="postcss">
</style>
select {
@apply h-full pl-3 pr-3 border-1 w-2/5 rounded;
@apply hover:border-sspsBlue rounded-lg border border-2 bg-[#f8fafb] p-3 text-xl shadow-lg outline-none transition-colors duration-300;
}
.inputWrapper {
@apply w-full relative;
}
.inputWrapper span {
@apply absolute right-0 top-1 bottom-0 my-auto flex bg-transparent p-3;
}
.wrapper :global(.basic-tel-input) {
/* height: 32px;
padding-left: 12px;
padding-right: 12px;
border-radius: 6px;
border: 1px solid;
outline: none;
width: 100%; */
/* @apply h-full pl-3 pr-3 border-1 w-full rounded; */
@apply hover:border-sspsBlue w-full rounded-lg border border-2 bg-[#f8fafb] p-3 text-xl shadow-lg outline-none transition-colors duration-300;
}
.wrapper :global(.invalid) {
/* border-color: red; */
@apply border-red-700;
}
</style>

View file

@ -18,7 +18,7 @@
import { SvelteToast, toast } from '@zerodevx/svelte-toast';
import { createForm } from 'svelte-forms-lib';
import type { Writable } from 'svelte/store';
import { writable, type Writable } from 'svelte/store';
import * as yup from 'yup';
import type { CandidateData } from '$lib/stores/candidate';
import AccountLinkCheckBox from '$lib/components/checkbox/AccountLinkCheckBox.svelte';
@ -46,7 +46,19 @@
export let data: PageData;
let details = data.candidate;
let baseCandidateDetails = data.whoami;
let componentErrors = writable( {
candidate: {
telephone: false,
},
parents: [
{
telephone: false,
},
{
telephone: false,
}
]
});
let personalIdBirthdateMatch = true;
const formInitialValues = {
gdpr: false,
@ -106,8 +118,7 @@
email: yup.string().email().required(),
telephone: yup
.string()
.required()
.matches(/^\+\d{1,3} \d{3} \d{3} \d{3}$/),
.required(), // already validated by the 'TelephoneField' component
birthplace: yup.string().required(),
birthdate: yup
.string()
@ -286,7 +297,7 @@
onSubmit: async (values: CandidateData) => onSubmit(values)
});
$: console.log($componentErrors['candidate']['telephone'])
const isPageInvalid = (index: number): boolean => {
switch (index) {
case 0:
@ -311,7 +322,8 @@
$typedErrors['candidate']['name'] ||
$typedErrors['candidate']['surname'] ||
$typedErrors['candidate']['email'] ||
$typedErrors['candidate']['telephone'] ||
// $typedErrors['candidate']['telephone'] ||
$componentErrors['candidate']['telephone'] ||
$typedErrors['candidate']['city'] ||
$typedErrors['candidate']['street'] ||
$typedErrors['candidate']['houseNumber'] ||
@ -341,7 +353,8 @@
$typedErrors['parents'][0]['name'] ||
$typedErrors['parents'][0]['surname'] ||
$typedErrors['parents'][0]['email'] ||
$typedErrors['parents'][0]['telephone']
// $typedErrors['parents'][0]['telephone']
$componentErrors['parents'][0]['telephone']
) {
return true;
}
@ -351,7 +364,8 @@
$typedErrors['parents'][1]['name'] ||
$typedErrors['parents'][1]['surname'] ||
$typedErrors['parents'][1]['email'] ||
$typedErrors['parents'][1]['telephone']
// $typedErrors['parents'][1]['telephone']
$componentErrors['parents'][1]['telephone']
) {
return true;
}
@ -511,6 +525,14 @@
/>
</span>
</div>
<span class="field ml-2">
<TelephoneField
bind:invalid={$componentErrors['candidate']['telephone']}
bind:value={$form.candidate.telephone}
placeholder={$LL.input.telephone()}
/>
</span>
<div>
<div class="field flex">
<span class="w-[50%]">
<EmailField
@ -519,23 +541,17 @@
placeholder={$LL.input.email()}
/>
</span>
<span class="ml-2 w-[50%]">
<TelephoneField
error={$typedErrors['candidate']['telephone']}
bind:value={$form.candidate.telephone}
placeholder={$LL.input.telephone()}
<span class="w-[50%]">
<TextField
error={$typedErrors['candidate']['city']}
bind:value={$form.candidate.city}
type="text"
placeholder={$LL.input.city()}
helperText="Uveďte poštovní směrovací číslo. (např. 602 00)"
/>
</span>
</div>
<span class="field">
<TextField
error={$typedErrors['candidate']['city']}
bind:value={$form.candidate.city}
type="text"
placeholder={$LL.input.city()}
helperText="Uveďte poštovní směrovací číslo. (např. 602 00)"
/>
</span>
</div>
</div>
<div class="field flex">
<span class="w-[66%]">
@ -675,7 +691,7 @@
</span>
<span class="field">
<TelephoneField
error={$typedErrors['parents'][0]['telephone']}
bind:invalid={$componentErrors['parents'][0]['telephone']}
bind:value={$form.parents[0].telephone}
placeholder={$LL.input.parent.telephone()}
/>
@ -704,7 +720,7 @@
</span>
<span class="field">
<TelephoneField
error={$typedErrors['parents'][1]['telephone']}
bind:invalid={$componentErrors['parents'][1]['telephone']}
bind:value={$form.parents[1].telephone}
placeholder={`${$LL.input.parent.telephone()} (${$LL.input.optional()})`}
/>