ENH: Phone Input Component (#189)
* #170-ENH: Created custom dropdown phone input * #170-ENH: Added phone_number rules * #170-ENH: Added phone_number rules * #170-ENH: Added separate Rule for phone number input, starting 0 phone number is ignored, added regex to ignore non digit phone input * #170-ENH: Removed global registration of CountryFlag * #170-ENH: Using VSelect component for country selection, added prop for dropdown styling * #170-ENH: Updated phone number rule * #170-ENH: Added margins to country selector --------- Co-authored-by: Sutirtha <sdas@republicfinance.com> Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
parent
f775ab84c7
commit
a53677d2ed
|
@ -11,6 +11,7 @@ use Illuminate\Support\Str;
|
|||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Rules\ValidHCaptcha;
|
||||
use App\Rules\ValidPhoneInputRule;
|
||||
|
||||
class AnswerFormRequest extends FormRequest
|
||||
{
|
||||
|
@ -150,7 +151,6 @@ class AnswerFormRequest extends FormRequest
|
|||
{
|
||||
switch ($property['type']) {
|
||||
case 'text':
|
||||
case 'phone_number':
|
||||
case 'signature':
|
||||
return ['string'];
|
||||
case 'number':
|
||||
|
@ -189,6 +189,8 @@ class AnswerFormRequest extends FormRequest
|
|||
return ['array', 'min:2'];
|
||||
}
|
||||
return $this->getRulesForDate($property);
|
||||
case 'phone_number':
|
||||
return [new ValidPhoneInputRule];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ValidPhoneInputRule implements Rule
|
||||
{
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
if (!Str::startsWith($value, '+')) {
|
||||
return false;
|
||||
}
|
||||
$parts = explode(' ', $value);
|
||||
if (count($parts) < 2) {
|
||||
return false;
|
||||
}
|
||||
return strlen($parts[1]) >= 5;
|
||||
}
|
||||
|
||||
public function message()
|
||||
{
|
||||
return 'The :attribute must be a string that starts with a "+" character and must be at least 5 digits long.';
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,101 @@
|
|||
<template>
|
||||
<div :class="wrapperClass" :style="inputStyle">
|
||||
<slot name="label">
|
||||
<label v-if="label" :for="id ? id : name"
|
||||
:class="[theme.default.label, { 'uppercase text-xs': uppercaseLabels, 'text-sm': !uppercaseLabels }]">
|
||||
{{ label }}
|
||||
<span v-if="required" class="text-red-500 required-dot">*</span>
|
||||
</label>
|
||||
</slot>
|
||||
<div v-if="help && helpPosition == 'above_input'" class="flex mb-1">
|
||||
<small :class="theme.default.help" class="grow">
|
||||
<slot name="help"><span class="field-help" v-html="help" /></slot>
|
||||
</small>
|
||||
</div>
|
||||
<div :id="id ? id : name" :disabled="disabled" :name="name" :style="inputStyle" class="flex items-center">
|
||||
<v-select class="w-1/4 mt-1" :data="countries" :value="selectedCountryCode" :inner-style="{ width: '474px' }"
|
||||
:searchable="true" :search-keys="['name']" :option-key="'code'" :color="'#3B82F6'"
|
||||
:placeholder="'Select a country'" :uppercase-labels="true" :theme="theme" @input="onCountryChange">
|
||||
<template #option="props">
|
||||
<div class="flex items-center space-x-2">
|
||||
<country-flag :country="props.option.code" />
|
||||
<span>{{ props.option.name }}</span>
|
||||
<span>{{ props.option.code }}</span>
|
||||
<span>{{ props.option.dial_code }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #selected="props">
|
||||
<div class="flex items-center space-x-2 justify-start">
|
||||
<country-flag :country="props.option.code" :style="{
|
||||
'margin-top': '-0.7em',
|
||||
'margin-left': '-0.9em',
|
||||
'margin-right': '-0.6em',
|
||||
'margin-bottom': '-0.7em'
|
||||
}" />
|
||||
<span>{{ props.option.dial_code }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</v-select>
|
||||
<input v-model="inputVal" type="text" class="inline-flex-grow ml-5"
|
||||
:class="[theme.default.input, { '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200': disabled }]"
|
||||
:placeholder="placeholder" :style="inputStyle" @input="onInput">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { directive as onClickaway } from 'vue-clickaway'
|
||||
import inputMixin from '~/mixins/forms/input.js'
|
||||
import countryCodes from '../../../data/country_codes.json'
|
||||
import CountryFlag from 'vue-country-flag'
|
||||
import VSelect from './components/VSelect.vue'
|
||||
|
||||
export default {
|
||||
phone: 'PhoneInput',
|
||||
components: {
|
||||
CountryFlag, VSelect
|
||||
},
|
||||
directives: {
|
||||
onClickaway: onClickaway
|
||||
},
|
||||
mixins: [inputMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
selectedCountryCode: countryCodes[234],
|
||||
countries: countryCodes,
|
||||
isOpen: false,
|
||||
inputVal: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
inputVal(newVal, oldVal) {
|
||||
if (newVal.startsWith('0')) {
|
||||
newVal = newVal.replace(/^0+/, '')
|
||||
}
|
||||
this.compVal = this.selectedCountryCode.dial_code + ' ' + newVal
|
||||
},
|
||||
selectedCountryCode(newVal, oldVal) {
|
||||
if (this.compVal) {
|
||||
this.compVal = this.compVal.replace(oldVal.dial_code, newVal.dial_code)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCountryChange(country) {
|
||||
this.selectedCountryCode = country
|
||||
this.closeDropdown()
|
||||
},
|
||||
closeDropdown() {
|
||||
this.isOpen = false
|
||||
},
|
||||
onInput(event) {
|
||||
const input = event.target.value
|
||||
const digitsOnly = input.replace(/[^0-9]/g, '')
|
||||
this.inputVal = digitsOnly
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
|
@ -41,8 +41,9 @@
|
|||
</span>
|
||||
</button></span>
|
||||
<!-- Select popover, show/hide based on select state. -->
|
||||
<div v-show="isOpen" :dusk="dusk+'_dropdown'"
|
||||
<div v-show="isOpen" :dusk="dusk+'_dropdown' "
|
||||
class="absolute mt-1 w-full rounded-md bg-white dark:bg-notion-dark-light shadow-lg z-10"
|
||||
:style="innerStyle"
|
||||
>
|
||||
<ul tabindex="-1" role="listbox" aria-labelled by="listbox-label" aria-activedescendant="listbox-item-3"
|
||||
class="rounded-md text-base leading-6 shadow-xs overflow-auto focus:outline-none sm:text-sm sm:leading-5 relative"
|
||||
|
@ -99,6 +100,7 @@ export default {
|
|||
props: {
|
||||
data: Array,
|
||||
value: { default: null },
|
||||
innerStyle: { type: Object, default: null },
|
||||
label: { type: String, default: null },
|
||||
dusk: { type: String, default: null },
|
||||
loading: { type: Boolean, default: false },
|
||||
|
|
|
@ -41,3 +41,4 @@ import ToggleSwitchInput from './ToggleSwitchInput.vue'
|
|||
Vue.component('SignatureInput', () => import('./SignatureInput.vue'))
|
||||
Vue.component('RichTextAreaInput', () => import('./RichTextAreaInput.vue'))
|
||||
Vue.component('DateInput', () => import('./DateInput.vue'))
|
||||
Vue.component('PhoneInput', () => import('./PhoneInput.vue'))
|
||||
|
|
|
@ -69,11 +69,14 @@
|
|||
<script>
|
||||
import FormLogicPropertyResolver from '../../../forms/FormLogicPropertyResolver.js'
|
||||
import FormPendingSubmissionKey from '../../../mixins/forms/form-pending-submission-key.js'
|
||||
import PhoneInput from '../../forms/PhoneInput.vue'
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'OpenFormField',
|
||||
components: { },
|
||||
components: {
|
||||
PhoneInput
|
||||
},
|
||||
mixins: [FormPendingSubmissionKey],
|
||||
props: {
|
||||
form: {
|
||||
|
@ -122,7 +125,7 @@ export default {
|
|||
checkbox: 'CheckboxInput',
|
||||
url: 'TextInput',
|
||||
email: 'TextInput',
|
||||
phone_number: 'TextInput',
|
||||
phone_number: 'PhoneInput'
|
||||
}
|
||||
},
|
||||
/**
|
||||
|
@ -246,7 +249,7 @@ export default {
|
|||
placeholder: field.placeholder,
|
||||
help: field.help,
|
||||
helpPosition: (field.help_position) ? field.help_position : 'below_input',
|
||||
uppercaseLabels: this.form.uppercase_labels,
|
||||
uppercaseLabels: this.form.uppercase_labels == 1 || this.form.uppercase_labels == true,
|
||||
theme: this.theme,
|
||||
maxCharLimit: (field.max_char_limit) ? parseInt(field.max_char_limit) : 2000,
|
||||
showCharLimit: field.show_char_limit || false
|
||||
|
|
|
@ -43,7 +43,7 @@ export default {
|
|||
checkbox: 'CheckboxInput',
|
||||
url: 'TextInput',
|
||||
email: 'TextInput',
|
||||
phone_number: 'TextInput',
|
||||
phone_number: 'PhoneInput'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -44,7 +44,7 @@ class FormSubmissionDataFactory
|
|||
$value = $this->faker->url();
|
||||
break;
|
||||
case 'phone_number':
|
||||
$value = $this->faker->phoneNumber();
|
||||
$value = '+1 ' .$this->faker->phoneNumber();
|
||||
break;
|
||||
case 'date':
|
||||
$value = $this->faker->date();
|
||||
|
|
Loading…
Reference in New Issue