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"> <script lang="ts">
import Telephone from '../icons/Telephone.svelte'; 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'; const helperText: string = 'Zadejte platný telefon s předvolbou. Například +420 123 456 789';
export let placeholder: string = ''; export let placeholder: string = ''; // TODO
export let value: string = ''; import TelInput, { normalizedCountries } from 'svelte-tel-input';
export let error: string = ''; import type { NormalizedTelNumber, CountryCode, E164Number } from 'svelte-tel-input/types';
// Phone Number formatting // Any Country Code Alpha-2 (ISO 3166)
$: { let country: CountryCode | null = 'CZ';
let x = value.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,3})(\d{0,3})/)!;
value = // You must use E164 number format. It's guarantee the parsing and storing consistency.
(x[1] ? '+' + x[1] : '') + export let value: E164Number | null = '+36301234567';
(x[2] ? ' ' + x[2] : '') +
(x[3] ? ' ' + x[3] : '') + // Validity
(x[4] ? ' ' + x[4] : ''); 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> </script>
<TextField <div class="wrapper w-full h-full flex"
bind:error use:isTooltip={{
bind:value content: helperText,
on:keydown placement: 'top',
on:keyup showOnCreate: false,
on:change delay: tooltipDelay
type="tel" }}
{placeholder}
{helperText}
icon
> >
<div slot="icon" class="flex items-center justify-center"> <select
<Telephone /> 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> </div>
</TextField> </div>
<style lang="postcss"> <style lang="postcss">
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> </style>

View file

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