mirror of
https://github.com/danbulant/Portfolio
synced 2026-05-22 22:08:59 +00:00
feat: telephone input for every country
This commit is contained in:
parent
3f5e16fa7e
commit
bde8e8da99
2 changed files with 129 additions and 52 deletions
|
|
@ -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>
|
||||
|
|
@ -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()})`}
|
||||
/>
|
||||
|
|
|
|||
Loading…
Reference in a new issue