- Fix silent redirect-to-home for unauthenticated users on protected routes; now redirects to login with a return path - Restyle OrderCard, SearchInput, Filters, DropDown, and product card image sizing/centering to match the app's LESS-token design language - Fix ant-design Slider handle placement/drag under RTL and seed a valid default range so handles actually render - Fix chip-row edge padding and top spacing across Orders/Cart/Search lists - Add pull-to-refresh to the dashboard and fix its header/padding to match the fixed PageBar pattern - Remove the floating WhatsApp button from index.html
120 lines
3.1 KiB
Vue
120 lines
3.1 KiB
Vue
<!--
|
|
Redesigned bottom nav — replaces the old GSAP SVG-notch bar (4 tabs: user,
|
|
cart, search, home) with a flat pill-bar of 5 tabs (Home, Search, Likes,
|
|
Notifications, Profile), each showing icon + label, and a single sliding
|
|
indicator behind the active tab instead of a cut-out notch.
|
|
|
|
windowData/SVG path math from the old version is gone — the indicator is
|
|
now pure CSS, positioned with a transform driven by the active tab's index,
|
|
so it needs no measurement of the container width.
|
|
-->
|
|
<template>
|
|
<nav class="tabbar" v-show="nav.showNav">
|
|
<div class="tabbar__inner" :style="{ '--count': nav.navBarRoutes.length }">
|
|
<div
|
|
class="tabbar__indicator"
|
|
:style="{ transform: `translateX(${indicatorOffset}%)` }"
|
|
aria-hidden="true"
|
|
/>
|
|
<router-link
|
|
v-for="item in nav.navBarRoutes"
|
|
:key="item.index"
|
|
:to="{ name: item.name }"
|
|
class="tabbar__item"
|
|
:class="{ 'is-active': nav.activeLink === item.index }"
|
|
>
|
|
<Icon :icon="item.icon" size="21" />
|
|
<span class="tabbar__label">{{ item.label }}</span>
|
|
</router-link>
|
|
</div>
|
|
</nav>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import Icon from '../icon/Icon.vue'
|
|
import { useNavStore } from '../../stores/navStore'
|
|
|
|
const nav = useNavStore()
|
|
|
|
// Index is 1-based; the indicator sits in tab slot (activeLink - 1) out of N.
|
|
// The whole app runs `direction: rtl` (see App.vue), which flips the grid's
|
|
// visual column order but NOT a physical translateX — so slot 0 renders on
|
|
// the right, and moving right needs a NEGATIVE offset here.
|
|
const indicatorOffset = computed(() => -(nav.activeLink - 1) * 100)
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
.tabbar {
|
|
position: fixed;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 10;
|
|
padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
|
|
background: transparent;
|
|
}
|
|
|
|
.tabbar__inner {
|
|
position: relative;
|
|
display: grid;
|
|
grid-template-columns: repeat(var(--count), 1fr);
|
|
max-width: 420px;
|
|
margin: 0 auto;
|
|
padding: 6px;
|
|
border-radius: 22px;
|
|
background: @canvas;
|
|
border: 1px solid @hairline;
|
|
box-shadow: 0 10px 28px -12px rgba(20, 22, 26, 0.18);
|
|
}
|
|
|
|
.tabbar__indicator {
|
|
position: absolute;
|
|
inset: 6px 6px 6px auto;
|
|
width: calc((100% - 12px) / var(--count));
|
|
border-radius: 16px;
|
|
background: @wineWash;
|
|
transition: transform 0.35s cubic-bezier(0.65, 0, 0.35, 1);
|
|
will-change: transform;
|
|
}
|
|
|
|
.tabbar__item {
|
|
position: relative;
|
|
z-index: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 2px;
|
|
padding: 8px 4px;
|
|
border-radius: 16px;
|
|
color: @inkFaint;
|
|
text-decoration: none;
|
|
transition: color 0.25s ease, transform 0.25s cubic-bezier(0.65, 0, 0.35, 1.4);
|
|
}
|
|
|
|
.tabbar__item :deep(svg) {
|
|
transition: transform 0.3s cubic-bezier(0.65, 0, 0.35, 1.4);
|
|
}
|
|
|
|
.tabbar__item.is-active {
|
|
color: @wine;
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.tabbar__item.is-active :deep(svg) {
|
|
transform: scale(1.08);
|
|
}
|
|
|
|
.tabbar__item:active {
|
|
transform: scale(0.94);
|
|
}
|
|
|
|
.tabbar__label {
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
opacity: 0.9;
|
|
}
|
|
</style>
|