Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Phone Input Component #189

Merged
merged 12 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/Http/Requests/AnswerFormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ private function getPropertyRules($property): array
{
switch ($property['type']) {
case 'text':
case 'phone_number':
case 'signature':
return ['string'];
case 'number':
Expand Down Expand Up @@ -189,6 +188,8 @@ private function getPropertyRules($property): array
return ['array', 'min:2'];
}
return $this->getRulesForDate($property);
case 'phone_number':
return ['string', 'min:10', 'starts_with:+'];
default:
return [];
}
Expand Down
2 changes: 2 additions & 0 deletions resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import '~/plugins'
import '~/components'

import '../sass/app.scss'
import CountryFlag from 'vue-country-flag'
Ahtritus marked this conversation as resolved.
Show resolved Hide resolved

Vue.component('country-flag', CountryFlag)
Ahtritus marked this conversation as resolved.
Show resolved Hide resolved
Vue.config.productionTip = false

Vue.mixin(Base)
Expand Down
119 changes: 119 additions & 0 deletions resources/js/components/forms/PhoneInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<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 v-on-clickaway="closeDropdown" class="relative">
Ahtritus marked this conversation as resolved.
Show resolved Hide resolved
<div :id="id ? id : name" :disabled="disabled" :name="name" class="flex">
<div class="dropdown" :style="inputStyle">
<div class="selected-option flex items-center justify-center space-x-2" @click="isOpen = !isOpen"
:class="[theme.default.input, { '!ring-red-500 !ring-2': hasValidation && form.errors.has(name), '!cursor-not-allowed !bg-gray-200': disabled }]"
:style="inputStyle">
<country-flag :country="selectedCountryCode.code" />
<span>{{ selectedCountryCode.dial_code }} </span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 transform" :class="{ 'rotate-180': isOpen }"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
<div class="options rounded-md bg-white dark:bg-notion-dark-light shadow-lg z-10" v-if="isOpen">
<div
class="option bg-white dark:bg-notion-dark-light z-10 flex items-center space-x-2 text-gray-900 cursor-default select-none relative py-2 pl-3 pr-9 cursor-pointer group hover:text-white hover:bg-nt-blue focus:outline-none focus:text-white focus:bg-nt-blue"
v-for="country in countries" :key="country.code" @click="onCountryChange(country)" :style="inputStyle">
<span><country-flag :country="country.code" /></span>
<span>{{ country.name }}</span>
<span>{{ country.code }}</span>
<span>{{ country.dial_code }}</span>
</div>
</div>
</div>
<input type="number" class="phone" v-model="inputVal"
: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">
</div>

</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'
Ahtritus marked this conversation as resolved.
Show resolved Hide resolved

export default {
phone: 'PhoneInput',
components: {},
directives: {
onClickaway: onClickaway
},
mixins: [inputMixin],

data() {
return {
selectedCountryCode: countryCodes[234],
countries: countryCodes,
isOpen: false,
inputVal: ''
}
},
watch: {
inputVal(newVal, oldVal) {
this.compVal = this.selectedCountryCode.dial_code + newVal;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure to read all requirements

It should handle clashes for instance, for French number you don't need +33 and start with a 0, so if user selects +33 for France, and enters 0123456789, the resulting value should not be +330123456789 but +330123456789

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I'm reading it right but do you mean that the result should be +33123456789 instead of +330123456789, with the extra 0 ignored?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added the new logic to handle this scenario, please take a look

},
selectedCountryCode(newVal, oldVal) {
this.compVal = this.compVal.replace(oldVal.dial_code, newVal.dial_code);
}
},
methods: {
onCountryChange(country) {
this.selectedCountryCode = country
this.closeDropdown()
},
closeDropdown() {
this.isOpen = false
},
}
}
</script>

<style scoped>
.dropdown {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please stick to tailwind. We avoid css as much as possible, and usually don't use actual classes. Ex:

  • Remove the class dropdown and replace it with relative w-1/4

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, using tailwind classes. Also, added a prop to the VSelect component as I wanted the opened dropdown options to have greater width than the selected option when dropdown closed. Do take a look at that

position: relative;
width: 25%;
}

.options {
z-index: 10;
position: absolute;
width: 474px;
max-height: 200px;
overflow-y: auto;
}

.option {
padding: 10px;
cursor: pointer;
}

.selected-option {
padding: 10px;
cursor: pointer;
}

.phone {
flex-grow: 1;
margin-left: 10px;
padding: 10px;
}

</style>
1 change: 1 addition & 0 deletions resources/js/components/forms/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'))
Ahtritus marked this conversation as resolved.
Show resolved Hide resolved
9 changes: 6 additions & 3 deletions resources/js/components/open/forms/OpenFormField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,13 @@
<script>
import FormLogicPropertyResolver from '../../../forms/FormLogicPropertyResolver.js'
import FormPendingSubmissionKey from '../../../mixins/forms/form-pending-submission-key.js'
import PhoneInput from '../../forms/PhoneInput.vue'

export default {
name: 'OpenFormField',
components: { },
components: {
PhoneInput
},
mixins: [FormPendingSubmissionKey],
props: {
form: {
Expand Down Expand Up @@ -117,7 +120,7 @@ export default {
checkbox: 'CheckboxInput',
url: 'TextInput',
email: 'TextInput',
phone_number: 'TextInput',
phone_number: 'PhoneInput'
}
},
/**
Expand Down Expand Up @@ -232,7 +235,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,
Ahtritus marked this conversation as resolved.
Show resolved Hide resolved
theme: this.theme,
maxCharLimit: (field.max_char_limit) ? parseInt(field.max_char_limit) : 2000,
showCharLimit: field.show_char_limit || false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default {
checkbox: 'CheckboxInput',
url: 'TextInput',
email: 'TextInput',
phone_number: 'TextInput',
phone_number: 'PhoneInput'
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion tests/Helpers/FormSubmissionDataFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function createSubmissionData($mergeData = [])
$value = $this->faker->url();
break;
case 'phone_number':
$value = $this->faker->phoneNumber();
$value = '+1' .$this->faker->phoneNumber();
break;
case 'date':
$value = $this->faker->date();
Expand Down