feat: Flag icon component (#10564)
This commit is contained in:
@@ -6,6 +6,7 @@ import CardLayout from 'dashboard/components-next/CardLayout.vue';
|
||||
import ContactsForm from 'dashboard/components-next/Contacts/ContactsForm/ContactsForm.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import Avatar from 'dashboard/components-next/avatar/Avatar.vue';
|
||||
import Flag from 'dashboard/components-next/flag/Flag.vue';
|
||||
import countries from 'shared/constants/countries';
|
||||
|
||||
const props = defineProps({
|
||||
@@ -56,13 +57,19 @@ const countryDetails = computed(() => {
|
||||
|
||||
if (!activeCountry) return null;
|
||||
|
||||
const parts = [
|
||||
activeCountry.emoji,
|
||||
city ? `${city},` : null,
|
||||
activeCountry.name,
|
||||
].filter(Boolean);
|
||||
return {
|
||||
countryCode: activeCountry.id,
|
||||
city: city ? `${city},` : null,
|
||||
name: activeCountry.name,
|
||||
};
|
||||
});
|
||||
|
||||
return parts.length ? parts.join(' ') : null;
|
||||
const formattedLocation = computed(() => {
|
||||
if (!countryDetails.value) return '';
|
||||
|
||||
return [countryDetails.value.city, countryDetails.value.name]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
});
|
||||
|
||||
const handleFormUpdate = updatedData => {
|
||||
@@ -114,8 +121,12 @@ const onClickViewDetails = () => emit('showContact', props.id);
|
||||
{{ phoneNumber }}
|
||||
</span>
|
||||
<div v-if="phoneNumber" class="w-px h-3 truncate bg-n-slate-6" />
|
||||
<span v-if="countryDetails" class="text-sm truncate text-n-slate-11">
|
||||
{{ countryDetails }}
|
||||
<span
|
||||
v-if="countryDetails"
|
||||
class="inline-flex items-center gap-2 text-sm truncate text-n-slate-11"
|
||||
>
|
||||
<Flag :country="countryDetails.countryCode" class="size-3.5" />
|
||||
{{ formattedLocation }}
|
||||
</span>
|
||||
<div v-if="countryDetails" class="w-px h-3 truncate bg-n-slate-6" />
|
||||
<Button
|
||||
|
||||
24
app/javascript/dashboard/components-next/flag/Flag.vue
Normal file
24
app/javascript/dashboard/components-next/flag/Flag.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup>
|
||||
import { h, defineProps } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
country: { type: String, required: true },
|
||||
squared: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
const renderFlag = () => {
|
||||
const classes = ['fi', `fi-${props.country.toLowerCase()}`, 'flex-shrink-0'];
|
||||
if (props.squared) {
|
||||
classes.push('fis');
|
||||
}
|
||||
return h('span', { class: classes });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="renderFlag" />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import 'flag-icons/css/flag-icons.min.css';
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import Flag from '../Flag.vue';
|
||||
import countries from 'shared/constants/countries';
|
||||
|
||||
const BasicTemplate = {
|
||||
components: { Flag },
|
||||
props: {
|
||||
country: {
|
||||
type: String,
|
||||
default: 'us',
|
||||
},
|
||||
squared: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
template: `
|
||||
<div class="flex items-center gap-4 p-4 border rounded border-n-weak">
|
||||
<Flag :country="country" :squared="squared" />
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
|
||||
const SizeVariants = {
|
||||
components: { Flag },
|
||||
setup() {
|
||||
const isSquared = ref(false);
|
||||
return { isSquared };
|
||||
},
|
||||
template: `
|
||||
<div class="flex flex-col gap-4">
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="checkbox" v-model="isSquared">
|
||||
Squared flags
|
||||
</label>
|
||||
<div class="flex items-center gap-4 p-4 border rounded border-n-weak">
|
||||
<Flag country="in" class="!size-4" :squared="isSquared" />
|
||||
<Flag country="in" class="!size-6" :squared="isSquared" />
|
||||
<Flag country="in" class="!size-8" :squared="isSquared" />
|
||||
<Flag country="in" class="!size-10" :squared="isSquared" />
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
|
||||
const AllFlags = {
|
||||
components: { Flag },
|
||||
setup() {
|
||||
const isSquared = ref(false);
|
||||
return { countries, isSquared };
|
||||
},
|
||||
template: `
|
||||
<div class="flex flex-col gap-4">
|
||||
<label class="flex items-center gap-2">
|
||||
<input type="checkbox" v-model="isSquared">
|
||||
Squared flags
|
||||
</label>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 p-4 border rounded border-n-strong md:grid-cols-3 lg:grid-cols-4">
|
||||
<div
|
||||
v-for="country in countries"
|
||||
:key="country.id"
|
||||
class="flex items-center gap-2 px-4 py-2 border rounded border-n-strong"
|
||||
>
|
||||
<Flag
|
||||
:country="country.id"
|
||||
:squared="isSquared"
|
||||
class="size-6"
|
||||
/>
|
||||
<span class="text-sm">{{ country.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Story title="Components/Flag">
|
||||
<Variant title="Basic Usage">
|
||||
<BasicTemplate country="us" :squared="false" />
|
||||
</Variant>
|
||||
|
||||
<Variant title="Size Variants">
|
||||
<SizeVariants />
|
||||
</Variant>
|
||||
|
||||
<Variant title="All Flags">
|
||||
<AllFlags />
|
||||
</Variant>
|
||||
</Story>
|
||||
</template>
|
||||
@@ -9,7 +9,6 @@ import SocialIcons from './SocialIcons.vue';
|
||||
import EditContact from './EditContact.vue';
|
||||
import NewConversation from './NewConversation.vue';
|
||||
import ContactMergeModal from 'dashboard/modules/contact/ContactMergeModal.vue';
|
||||
import { getCountryFlag } from 'dashboard/helper/flag';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import {
|
||||
isAConversationRoute,
|
||||
@@ -127,8 +126,12 @@ export default {
|
||||
},
|
||||
findCountryFlag(countryCode, cityAndCountry) {
|
||||
try {
|
||||
const countryFlag = countryCode ? getCountryFlag(countryCode) : '🌎';
|
||||
return `${cityAndCountry} ${countryFlag}`;
|
||||
if (!countryCode) {
|
||||
return `${cityAndCountry} 🌎`;
|
||||
}
|
||||
|
||||
const code = countryCode?.toLowerCase();
|
||||
return `${cityAndCountry} <span class="fi fi-${code} size-3.5"></span>`;
|
||||
} catch (error) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -87,10 +87,9 @@ export default {
|
||||
/>
|
||||
<span
|
||||
v-if="value"
|
||||
v-dompurify-html="value"
|
||||
class="overflow-hidden text-sm whitespace-nowrap text-ellipsis"
|
||||
>
|
||||
{{ value }}
|
||||
</span>
|
||||
/>
|
||||
<span v-else class="text-sm text-slate-300 dark:text-slate-600">{{
|
||||
$t('CONTACT_PANEL.NOT_AVAILABLE')
|
||||
}}</span>
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"date-fns": "2.21.1",
|
||||
"date-fns-tz": "^1.3.3",
|
||||
"dompurify": "3.1.6",
|
||||
"flag-icons": "^7.2.3",
|
||||
"floating-vue": "^5.2.2",
|
||||
"highlight.js": "^11.10.0",
|
||||
"idb": "^8.0.0",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -121,6 +121,9 @@ importers:
|
||||
dompurify:
|
||||
specifier: 3.1.6
|
||||
version: 3.1.6
|
||||
flag-icons:
|
||||
specifier: ^7.2.3
|
||||
version: 7.2.3
|
||||
floating-vue:
|
||||
specifier: ^5.2.2
|
||||
version: 5.2.2(vue@3.5.12(typescript@5.6.2))
|
||||
@@ -2854,6 +2857,9 @@ packages:
|
||||
resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
flag-icons@7.2.3:
|
||||
resolution: {integrity: sha512-X2gUdteNuqdNqob2KKTJTS+ZCvyWeLCtDz9Ty8uJP17Y4o82Y+U/Vd4JNrdwTAjagYsRznOn9DZ+E/Q52qbmqg==}
|
||||
|
||||
flat-cache@3.1.0:
|
||||
resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -8159,6 +8165,8 @@ snapshots:
|
||||
locate-path: 7.2.0
|
||||
path-exists: 5.0.0
|
||||
|
||||
flag-icons@7.2.3: {}
|
||||
|
||||
flat-cache@3.1.0:
|
||||
dependencies:
|
||||
flatted: 3.2.9
|
||||
|
||||
Reference in New Issue
Block a user