You've already forked AstralRinth
forked from didirus/AstralRinth
feat: i18n switcher in app-frontend (#4990)
* feat: app i18n stuff * feat: locale switching on load * feat: db migration * feat: polish + fade indicator impl onto TabbedModal * fix: prepr checks * fix: remove staging lock for language switching * fix: lint
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { type Component, ref } from 'vue'
|
||||
import { type Component, nextTick, ref } from 'vue'
|
||||
|
||||
import { type MessageDescriptor, useVIntl } from '../../composables/i18n'
|
||||
import { useScrollIndicator } from '../../composables/scroll-indicator'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
@@ -10,6 +11,7 @@ export type Tab<Props> = {
|
||||
icon: Component
|
||||
content: Component<Props>
|
||||
props?: Props
|
||||
badge?: MessageDescriptor
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
@@ -19,8 +21,13 @@ defineProps<{
|
||||
|
||||
const selectedTab = ref(0)
|
||||
|
||||
const scrollContainer = ref<HTMLElement | null>(null)
|
||||
const { showTopFade, showBottomFade, checkScrollState, forceCheck } =
|
||||
useScrollIndicator(scrollContainer)
|
||||
|
||||
function setTab(index: number) {
|
||||
selectedTab.value = index
|
||||
nextTick(() => forceCheck())
|
||||
}
|
||||
|
||||
defineExpose({ selectedTab, setTab })
|
||||
@@ -34,16 +41,56 @@ defineExpose({ selectedTab, setTab })
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
:class="`flex gap-2 items-center text-left rounded-xl px-4 py-2 border-none text-nowrap font-semibold cursor-pointer active:scale-[0.97] transition-all ${selectedTab === index ? 'bg-button-bgSelected text-button-textSelected' : 'bg-transparent text-button-text hover:bg-button-bg hover:text-contrast'}`"
|
||||
@click="() => (selectedTab = index)"
|
||||
@click="() => setTab(index)"
|
||||
>
|
||||
<component :is="tab.icon" class="w-4 h-4" />
|
||||
<span>{{ formatMessage(tab.name) }}</span>
|
||||
<span
|
||||
v-if="tab.badge"
|
||||
class="rounded-full px-1.5 py-0.5 text-xs font-bold bg-brand-highlight text-brand-green"
|
||||
>
|
||||
{{ formatMessage(tab.badge) }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
<div class="w-[600px] h-[500px] overflow-y-auto px-4">
|
||||
<component :is="tabs[selectedTab].content" v-bind="tabs[selectedTab].props ?? {}" />
|
||||
<div class="relative">
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0 max-h-0"
|
||||
enter-to-class="opacity-100 max-h-24"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 max-h-24"
|
||||
leave-to-class="opacity-0 max-h-0"
|
||||
>
|
||||
<div
|
||||
v-if="showTopFade"
|
||||
class="pointer-events-none absolute left-0 right-0 top-0 z-10 h-24 bg-gradient-to-b from-bg-raised to-transparent"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<div
|
||||
ref="scrollContainer"
|
||||
class="w-[600px] h-[500px] overflow-y-auto px-4"
|
||||
@scroll="checkScrollState"
|
||||
>
|
||||
<component :is="tabs[selectedTab].content" v-bind="tabs[selectedTab].props ?? {}" />
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0 max-h-0"
|
||||
enter-to-class="opacity-100 max-h-24"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 max-h-24"
|
||||
leave-to-class="opacity-0 max-h-0"
|
||||
>
|
||||
<div
|
||||
v-if="showBottomFade"
|
||||
class="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-24 bg-gradient-to-t from-bg-raised to-transparent"
|
||||
/>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user