Client - add dropdown to select language
This commit is contained in:
parent
1e77fa68b6
commit
b01f867d80
@ -1,2 +0,0 @@
|
|||||||
VUE_APP_I18N_LOCALE=en
|
|
||||||
VUE_APP_I18N_FALLBACK_LOCALE=en
|
|
97
fittrackee_client/src/components/Common/Dropdown.vue
Normal file
97
fittrackee_client/src/components/Common/Dropdown.vue
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dropdown-wrapper">
|
||||||
|
<div class="dropdown-selected" @click="openDropdown">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<ul class="dropdown-list" v-if="isOpen">
|
||||||
|
<li
|
||||||
|
class="dropdown-item"
|
||||||
|
:class="{ selected: option.value === selected }"
|
||||||
|
v-for="(option, index) in dropdownOptions"
|
||||||
|
:key="index"
|
||||||
|
@click="updateSelected(option)"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { PropType, defineComponent, ref } from 'vue'
|
||||||
|
import { IDropdownOption, TDropdownOptions } from '@/types'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'Dropdown',
|
||||||
|
props: {
|
||||||
|
options: {
|
||||||
|
type: Object as PropType<TDropdownOptions>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: {
|
||||||
|
selected: (option: IDropdownOption) => option,
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
let isOpen = ref(false)
|
||||||
|
let dropdownOptions = props.options.map((option) => option)
|
||||||
|
|
||||||
|
function openDropdown() {
|
||||||
|
isOpen.value = true
|
||||||
|
}
|
||||||
|
function updateSelected(option: IDropdownOption) {
|
||||||
|
emit('selected', option)
|
||||||
|
isOpen.value = false
|
||||||
|
}
|
||||||
|
function getSelectedLabel(selectedValue: string) {
|
||||||
|
return props.options.filter(
|
||||||
|
(option: IDropdownOption) => option.value === selectedValue
|
||||||
|
)[0].label
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dropdownOptions,
|
||||||
|
updateSelected,
|
||||||
|
getSelectedLabel,
|
||||||
|
isOpen,
|
||||||
|
openDropdown,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.dropdown-list {
|
||||||
|
list-style-type: none;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 0;
|
||||||
|
margin: 5px 0;
|
||||||
|
position: absolute;
|
||||||
|
text-align: left;
|
||||||
|
border: solid 1px lightgrey;
|
||||||
|
box-shadow: 2px 2px 5px lightgrey;
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
li:last-child {
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected::after {
|
||||||
|
content: ' ✔';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -2,14 +2,14 @@
|
|||||||
<div id="nav">
|
<div id="nav">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="nav-app-name">
|
<div class="nav-app-name">
|
||||||
<span class="nav-item app-name">FitTrackee</span>
|
<div class="nav-item app-name">FitTrackee</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-icon-open" :class="{ 'menu-open': isMenuOpen }">
|
<div class="nav-icon-open" :class="{ 'menu-open': isMenuOpen }">
|
||||||
<i class="fa fa-bars hamburger-icon" @click="openMenu()"></i>
|
<i class="fa fa-bars hamburger-icon" @click="openMenu()"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-items" :class="{ 'menu-open': isMenuOpen }">
|
<div class="nav-items" :class="{ 'menu-open': isMenuOpen }">
|
||||||
<div class="nav-items-close">
|
<div class="nav-items-close">
|
||||||
<span class="app-name">FitTrackee</span>
|
<div class="app-name">FitTrackee</div>
|
||||||
<i
|
<i
|
||||||
class="fa fa-close close-icon nav-item"
|
class="fa fa-close close-icon nav-item"
|
||||||
:class="{ 'menu-closed': !isMenuOpen }"
|
:class="{ 'menu-closed': !isMenuOpen }"
|
||||||
@ -20,16 +20,25 @@
|
|||||||
<router-link class="nav-item" to="/">{{
|
<router-link class="nav-item" to="/">{{
|
||||||
t('dashboard.DASHBOARD')
|
t('dashboard.DASHBOARD')
|
||||||
}}</router-link>
|
}}</router-link>
|
||||||
<span class="nav-item">{{ t('workouts.WORKOUTS') }}</span>
|
<div class="nav-item">{{ t('workouts.WORKOUTS') }}</div>
|
||||||
<span class="nav-item">{{ t('statistics.STATISTICS') }}</span>
|
<div class="nav-item">{{ t('statistics.STATISTICS') }}</div>
|
||||||
<span class="nav-item">{{ t('administration.ADMIN') }}</span>
|
<div class="nav-item">{{ t('administration.ADMIN') }}</div>
|
||||||
<span class="nav-item">{{ t('workouts.ADD_WORKOUT') }}</span>
|
<div class="nav-item">{{ t('workouts.ADD_WORKOUT') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-items-user-menu">
|
<div class="nav-items-user-menu">
|
||||||
<span class="nav-item">User</span>
|
<div class="nav-item">User</div>
|
||||||
<span class="nav-item">{{ t('user.LOGOUT') }}</span>
|
<div class="nav-item">{{ t('user.LOGOUT') }}</div>
|
||||||
<span class="nav-item">{{ t('user.REGISTER') }}</span>
|
<!-- <span class="nav-item">{{ t('user.REGISTER') }}</span>-->
|
||||||
<span class="nav-item">{{ t('user.LOGIN') }}</span>
|
<!-- <span class="nav-item">{{ t('user.LOGIN') }}</span>-->
|
||||||
|
<Dropdown
|
||||||
|
v-if="availableLanguages && language"
|
||||||
|
class="nav-item"
|
||||||
|
:options="availableLanguages"
|
||||||
|
:selected="language"
|
||||||
|
@selected="updateLanguage"
|
||||||
|
>
|
||||||
|
<i class="fa fa-language"></i>
|
||||||
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -37,21 +46,45 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref } from 'vue'
|
import { computed, defineComponent, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
|
||||||
|
import { IDropdownOption } from '@/types'
|
||||||
|
import Dropdown from '@/components/Common/Dropdown.vue'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'NavBar',
|
name: 'NavBar',
|
||||||
|
components: {
|
||||||
|
Dropdown,
|
||||||
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const { t, locale, availableLocales } = useI18n()
|
||||||
|
const store = useStore()
|
||||||
|
const availableLanguages = availableLocales.map((l) => {
|
||||||
|
return { label: l.toUpperCase(), value: l }
|
||||||
|
})
|
||||||
let isMenuOpen = ref(false)
|
let isMenuOpen = ref(false)
|
||||||
const { t } = useI18n()
|
|
||||||
function openMenu() {
|
function openMenu() {
|
||||||
isMenuOpen.value = true
|
isMenuOpen.value = true
|
||||||
}
|
}
|
||||||
function closeMenu() {
|
function closeMenu() {
|
||||||
isMenuOpen.value = false
|
isMenuOpen.value = false
|
||||||
}
|
}
|
||||||
return { isMenuOpen, openMenu, closeMenu, t }
|
function updateLanguage(option: IDropdownOption) {
|
||||||
|
locale.value = option.value.toString()
|
||||||
|
store.commit('setLanguage', option.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
availableLanguages,
|
||||||
|
isMenuOpen,
|
||||||
|
language: computed(() => store.state.language),
|
||||||
|
t,
|
||||||
|
openMenu,
|
||||||
|
closeMenu,
|
||||||
|
updateLanguage,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@ -74,7 +107,6 @@
|
|||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
line-height: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fa {
|
.fa {
|
||||||
@ -84,6 +116,7 @@
|
|||||||
.nav-icon-open {
|
.nav-icon-open {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hamburger-icon,
|
.hamburger-icon,
|
||||||
.close-icon {
|
.close-icon {
|
||||||
display: none;
|
display: none;
|
||||||
@ -99,8 +132,25 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-items-app-menu,
|
||||||
|
.nav-items-user-menu {
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
padding: 10px 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
|
&.dropdown-wrapper {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.dropdown-list) {
|
||||||
|
margin-left: -10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
width: 75px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,9 +205,13 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
.app-name {
|
.app-name {
|
||||||
padding: 19px 25px;
|
padding: 15px 25px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
padding: 7px 25px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,8 @@ function loadLocaleMessages(): LocaleMessages<VueMessageType> {
|
|||||||
|
|
||||||
export default createI18n({
|
export default createI18n({
|
||||||
legacy: false,
|
legacy: false,
|
||||||
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
|
locale: 'en',
|
||||||
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
|
fallbackLocale: 'en',
|
||||||
|
globalInjection: true,
|
||||||
messages: loadLocaleMessages(),
|
messages: loadLocaleMessages(),
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"LANGUAGE": "Language",
|
||||||
"LOGIN": "Login",
|
"LOGIN": "Login",
|
||||||
"LOGOUT": "Logout",
|
"LOGOUT": "Logout",
|
||||||
"REGISTER": "Register"
|
"REGISTER": "Register"
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"LANGUAGE": "Langue",
|
||||||
"LOGIN": "Se connecter",
|
"LOGIN": "Se connecter",
|
||||||
"LOGOUT": "Se déconnecter",
|
"LOGOUT": "Se déconnecter",
|
||||||
"REGISTER": "S'inscrire"
|
"REGISTER": "S'inscrire"
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
import { createStore } from 'vuex'
|
import { createStore } from 'vuex'
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
state: {},
|
state: {
|
||||||
mutations: {},
|
language: 'en',
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setLanguage(state, language: string) {
|
||||||
|
state.language = language
|
||||||
|
},
|
||||||
|
},
|
||||||
actions: {},
|
actions: {},
|
||||||
modules: {},
|
modules: {},
|
||||||
})
|
})
|
||||||
|
6
fittrackee_client/src/types/index.ts
Normal file
6
fittrackee_client/src/types/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface IDropdownOption {
|
||||||
|
value: string | number
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TDropdownOptions = IDropdownOption[]
|
Loading…
Reference in New Issue
Block a user