Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/paulopariz/vue-input-phone-shadcn

🏳️ The Vue Input Phone Shadcn library is an open source project, based on Shadcn. It offers an excellent component for entering phone numbers, supporting 194 countries, with automatic formatting and advanced validation to detect invalid formats. Plus, it's highly customizable.
https://github.com/paulopariz/vue-input-phone-shadcn

componenet flag nuxt phone-number shadcn shadcn-ui tailwindcss typescript vue

Last synced: about 1 month ago
JSON representation

🏳️ The Vue Input Phone Shadcn library is an open source project, based on Shadcn. It offers an excellent component for entering phone numbers, supporting 194 countries, with automatic formatting and advanced validation to detect invalid formats. Plus, it's highly customizable.

Awesome Lists containing this project

README

        

# Vue Input Phone Shadch - Component Documentation

[VueInputPhoneShadcn.webm](https://github.com/user-attachments/assets/2038fbcd-c0dc-4a6b-a4fe-c0a655ff1945)

## 1. Configure components

### 1.1 Install components

To get started, install the necessary Shadcn components by running the following commands in the terminal:

```bash
npx shadcn-vue@latest add button
npx shadcn-vue@latest add input
npx shadcn-vue@latest add command
npx shadcn-vue@latest add popover
```

### 1.2 Install vMaska

To format the number, install the vMaska library:

```bash
npm install maska
#or
yarn add maska
#or
pnpm install maska
#or
bun add maska
```

## 2. Component `PhoneInput`

In your `components` folder, create a new subfolder called `phone`. Then add a file called `PhoneInput.vue` and insert the following code:

```ts

import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandItem,
CommandList,
CommandSeparator,
CommandInput,
} from "@/components/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import FlagCountry from "./FlagCountry.vue";

import { CaretSortIcon, CheckIcon } from "@radix-icons/vue";

import { countriesByContinent, type ICountry } from "./countries";
import { vMaska } from "maska";

const props = defineProps<{
modelValue?: string;
disabled?: boolean;
placeholder?: string;
defaultCountry?: string;
}>();

const emit = defineEmits<{
(e: "update:modelValue", value: string): void;
}>();

const allCountries = computed(() => countriesByContinent);
const open = ref(false);
const phoneNumber = ref(props.modelValue || "");

const languageToCountry: Record<string, string> = {
"pt-BR": "BR",
pt: "BR",
"fr-FR": "FR",
fr: "FR",
"it-IT": "IT",
it: "IT",
"de-DE": "DE",
de: "DE",
"es-ES": "ES",
es: "ES",
"ja-JP": "JP",
ja: "JP",
"en-US": "US",
en: "US",
};

function findCountry(countryCode: string) {
const allContinents = Object.values(allCountries.value);
for (const continent of allContinents) {
const foundByCode = continent.find((country) => country.code === countryCode);
if (foundByCode) return foundByCode;
}
return null;
}

const countryCode = getCountryCodeFromLanguage(navigator?.language);
const countrySelected = ref(findCountry(countryCode));

function getCountryCodeFromLanguage(language: string): string {
return props.defaultCountry || languageToCountry[language] || "US";
}

function selectedCountry(data: ICountry) {
if (props.disabled) return;
phoneNumber.value = "";
countrySelected.value = data;
open.value = false;
}

watch(phoneNumber, (value) => {
if (props.disabled) return;
emit(
"update:modelValue",
value.length === countrySelected.value?.mask.length ? value : value + "invalid"
);
});












No country found.






{{ country.name }}










```

## 3. Component `FlagCountry`

Now, create a file called `FlagCountry.vue` in the `phone` folder and add the following content:

```ts

import { computed } from 'vue';

interface Props {
country: string;
countryName?: string;
}

const props = defineProps<Props>();

const flagUrl = computed(() => {
return `https://flagcdn.com/w40/${props.country.toLowerCase()}.png`;
});





```

## 4. Countries

Now let's add the JSON that contains the country information and corresponding phone formatting. To do this, create a file called `countries.ts`:
- [`Full code available here`](https://github.com/paulopariz/vue-input-phone-shadcn/blob/master/components/ui/phone/countries.ts)

```ts
type Continent = "Africa" | "Asia" | "Europe" | "Oceania" | "South America" | "North America";

export interface ICountry {
code: string;
name: string;
ddd: string;
mask: string;
}

const countries: Record = {
Africa: [
{ name: "Algeria", ddd: "+213", code: "DZ", mask: "+213 ### ### ###" },
{ name: "Angola", ddd: "+244", code: "AO", mask: "+244 ### ### ###" },
// Other countries...
],
// Other continents...
};

function sortCountriesByName(countries: ICountry[]): ICountry[] {
return countries.slice().sort((a, b) => a.name.localeCompare(b.name));
}

const countriesByContinent = Object.keys(countries).reduce(
(sorted, continent) => {
const continentKey = continent as Continent;
sorted[continentKey] = sortCountriesByName(countries[continentKey]);
return sorted;
},
{} as Record
);

export { countriesByContinent };
```

## 5. Export

Finally, to export the `PhoneInput.vue` component, create a file called `index.ts` inside the `phone` folder:

```ts
export { default as PhoneInput } from './PhoneInput.vue';
```