feat: initial commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<!-- Ported from legacy henza_pwa/src/components/ui/container.vue -->
|
||||
<template>
|
||||
<main class="px-[16px] max-w-screen-md m-auto relative no-scrollbar">
|
||||
<slot />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
padding?: string
|
||||
}
|
||||
withDefaults(defineProps<Props>(), {})
|
||||
</script>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- Ported from legacy henza_pwa/src/components/content.vue -->
|
||||
<template>
|
||||
<main :style="{ marginTop: transition.transitionEnded ? `${transition.headerHeight}px` : 'unset' }">
|
||||
<slot />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useTransitionStore } from '../../stores/transitionStore'
|
||||
|
||||
const transition = useTransitionStore()
|
||||
</script>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!-- Ported from legacy henza_pwa/src/components/dropDown.vue -->
|
||||
<template>
|
||||
<div :class="`rounded-[5px] border border-solid text-primary px-[15px] py-[15px] ${show ? 'border-secondary' : 'border-lightGray/20'}`">
|
||||
<div class="flex w-full justify-between cursor-pointer" @click="show = !show">
|
||||
<h5 class="flex-1 m-0">
|
||||
<slot name="text">{{ text }}</slot>
|
||||
</h5>
|
||||
<Icon icon="vuesax-linear:arrow-down" :class="`transition-all duration-200 ease-linear ${show ? 'rotate-180' : undefined}`" size="25" />
|
||||
</div>
|
||||
<transition name="drop" mode="out-in" appear>
|
||||
<div class="w-full" style="overflow: hidden" v-if="show">
|
||||
<slot />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Icon from '../icon/Icon.vue'
|
||||
|
||||
withDefaults(defineProps<{ text?: string }>(), {})
|
||||
const show = ref(false)
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.drop-enter-active {
|
||||
animation: drop 0.2s cubic-bezier(0.33, 1, 0.68, 1);
|
||||
}
|
||||
.drop-leave-to {
|
||||
animation: drop 0.2s cubic-bezier(0.33, 1, 0.68, 1) reverse;
|
||||
}
|
||||
@keyframes drop {
|
||||
0% {
|
||||
max-height: 0;
|
||||
}
|
||||
100% {
|
||||
max-height: 120px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,155 @@
|
||||
<!-- Ported from legacy henza_pwa/src/components/ui/navigation.vue -->
|
||||
<template>
|
||||
<nav class="fixed bottom-0 w-full z-[10]" v-show="nav.showNav">
|
||||
<div class="flex flex-row-reverse justify-around items-center absolute w-full h-[50px] z-[2] bottom-0">
|
||||
<template v-for="item in nav.navBarRoutes" :key="item.index">
|
||||
<router-link :to="{ name: item.name }" class="relative h-[50px]" @click="setLastPositionX(item)">
|
||||
<div class="nav-item">
|
||||
<Icon
|
||||
:icon="item.icon"
|
||||
class="cursor-pointer"
|
||||
:style="{ transform: nav.activeLink === item.index ? `translateY(50px)` : undefined }"
|
||||
size="25"
|
||||
/>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
<div class="active-nav-item absolute" :style="{ top: output[0] + 'px', left: output[1] + 'px' }">
|
||||
<Icon v-if="activeItem?.icon === 'vuesax-linear:user'" icon="vuesax-linear:user" class="cursor-pointer" size="25" />
|
||||
<Icon
|
||||
v-if="activeItem?.icon === 'vuesax-linear:bag-happy'"
|
||||
icon="vuesax-linear:bag-happy"
|
||||
class="cursor-pointer"
|
||||
size="25"
|
||||
/>
|
||||
<Icon
|
||||
v-if="activeItem?.icon === 'vuesax-linear:search-normal-1'"
|
||||
icon="vuesax-linear:search-normal-1"
|
||||
class="cursor-pointer"
|
||||
size="25"
|
||||
/>
|
||||
<Icon
|
||||
v-if="activeItem?.icon === 'vuesax-linear:home-2'"
|
||||
icon="vuesax-linear:home-2"
|
||||
class="cursor-pointer"
|
||||
size="25"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<svg class="h-[50px] w-full">
|
||||
<path id="nav" class="from-path" :d="shape" />
|
||||
</svg>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { gsap } from 'gsap'
|
||||
import { TransitionPresets, useTransition } from '@vueuse/core'
|
||||
import Icon from '../icon/Icon.vue'
|
||||
import { useNavStore } from '../../stores/navStore'
|
||||
import { useWindowDataStore } from '../../stores/windowDataStore'
|
||||
|
||||
const nav = useNavStore()
|
||||
const windowData = useWindowDataStore()
|
||||
|
||||
const shape = ref('')
|
||||
|
||||
const centershape = [95, 45]
|
||||
const width = computed(() => windowData.width)
|
||||
const centerOfCentershape = computed(() => {
|
||||
return (width.value / nav.navBarRoutes.length) * nav.activeLink - width.value / (nav.navBarRoutes.length * 2)
|
||||
})
|
||||
|
||||
const draw = () => {
|
||||
const drowData = {
|
||||
cornnerCurve: 10,
|
||||
height: 50,
|
||||
centerTopCurve: 20,
|
||||
centerBottpmCurve: 32,
|
||||
centershapeWidth: centershape[0],
|
||||
centershapeHeight: centershape[1],
|
||||
startOfCentershape: centerOfCentershape.value - centershape[0] / 2,
|
||||
endOfCentershape: centerOfCentershape.value + centershape[0] / 2,
|
||||
}
|
||||
|
||||
return `
|
||||
M0,0
|
||||
H${drowData.startOfCentershape}
|
||||
C${drowData.startOfCentershape + drowData.centerTopCurve}, 0, ${centerOfCentershape.value - drowData.centerBottpmCurve}, ${drowData.centershapeHeight}, ${centerOfCentershape.value}, ${drowData.centershapeHeight}
|
||||
C${centerOfCentershape.value + drowData.centerBottpmCurve},${drowData.centershapeHeight},${drowData.endOfCentershape - drowData.centerTopCurve},0,${drowData.endOfCentershape},0
|
||||
H${width.value}
|
||||
V50
|
||||
H0
|
||||
Z`
|
||||
}
|
||||
|
||||
watch(width, (val) => {
|
||||
if (val > 0) {
|
||||
shape.value = draw()
|
||||
}
|
||||
})
|
||||
watch(
|
||||
() => nav.$state,
|
||||
() => {
|
||||
if (!shape.value) {
|
||||
shape.value = draw()
|
||||
} else {
|
||||
gsap.to('.from-path', { duration: 0.3, attr: { d: draw() } })
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
shape.value = draw()
|
||||
|
||||
let lastPositionX = width.value
|
||||
const activeItem = computed(() => {
|
||||
const item = nav.navBarRoutes.find((x) => x.index === nav.activeLink)
|
||||
setActiveIcon()
|
||||
return item
|
||||
})
|
||||
|
||||
// Legacy called this with an `item` argument that the function body never
|
||||
// used (plain JS silently ignores extra args); kept as an unused optional
|
||||
// param so behavior stays identical under TypeScript.
|
||||
const setLastPositionX = (_item?: unknown) => {
|
||||
lastPositionX = centerOfCentershape.value - centershape[0] / 2 + 25
|
||||
}
|
||||
const setActiveIcon = () => {
|
||||
const positionX = centerOfCentershape.value - centershape[0] / 2 + 25
|
||||
baseVector.value = [50, positionX + (lastPositionX - positionX) / 2]
|
||||
setTimeout(() => {
|
||||
baseVector.value = [-20, positionX]
|
||||
}, 150)
|
||||
}
|
||||
|
||||
const baseVector = ref([0, 0])
|
||||
|
||||
const output = useTransition(baseVector, {
|
||||
duration: 150,
|
||||
transition: TransitionPresets.easeInOutCubic,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
#nav {
|
||||
opacity: 0.4;
|
||||
fill: #dddddd;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.active-nav-item {
|
||||
.nav-item;
|
||||
background: @primary;
|
||||
color: @white;
|
||||
border-radius: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,41 @@
|
||||
<!-- Ported from legacy henza_pwa/src/components/ui/ScrollFetch.vue — the plain
|
||||
infinite-scroll container (default ant Empty, no custom illustration). -->
|
||||
<template>
|
||||
<main
|
||||
class="overflow-auto no-scrollbar w-[100vw] h-screen max-w-screen-md m-auto"
|
||||
:style="{ paddingTop: padding || transition.headerHeight + 'px' }"
|
||||
ref="scrollArea"
|
||||
>
|
||||
<slot></slot>
|
||||
<Spinner cls="my-[10px]" :spinning="loading" :indicator="true" tip="درحال دریافت اطلاعات" />
|
||||
<EmptyState v-if="flag" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useInfiniteScroll } from '@vueuse/core'
|
||||
import Spinner from '../Spinner.vue'
|
||||
import EmptyState from '../EmptyState.vue'
|
||||
import { useTransitionStore } from '../../stores/transitionStore'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
loading: boolean
|
||||
flag: boolean
|
||||
padding?: string
|
||||
}>(),
|
||||
{},
|
||||
)
|
||||
const emit = defineEmits<{ (e: 'fetch'): void }>()
|
||||
const transition = useTransitionStore()
|
||||
const scrollArea = ref(null)
|
||||
|
||||
useInfiniteScroll(
|
||||
scrollArea,
|
||||
() => {
|
||||
emit('fetch')
|
||||
},
|
||||
{ distance: 50 },
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,48 @@
|
||||
<!--
|
||||
Ported from legacy henza_pwa/src/components/ui/ScrollFetchAfterSearch.vue.
|
||||
Legacy imported emp_after_search.svg / emp_before_search.svg and an `isSearch`
|
||||
prop to pick between them, but the live template used neither — it rendered
|
||||
`ordersEmpty.svg` unconditionally. Ported as-is (unused imports dropped,
|
||||
`isSearch` kept since it is part of the component's declared prop contract).
|
||||
-->
|
||||
<template>
|
||||
<main
|
||||
class="overflow-auto no-scrollbar w-[100vw] h-screen max-w-screen-md m-auto"
|
||||
:style="{ paddingTop: padding || transition.headerHeight + 'px' }"
|
||||
ref="scrollArea"
|
||||
>
|
||||
<slot></slot>
|
||||
<Spinner cls="my-[10px]" :spinning="loading" :indicator="true" tip="درحال دریافت اطلاعات" />
|
||||
<EmptyState v-if="flag && !loading" :image="ordersEmpty" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useInfiniteScroll } from '@vueuse/core'
|
||||
import Spinner from '../Spinner.vue'
|
||||
import EmptyState from '../EmptyState.vue'
|
||||
import ordersEmpty from '../../assets/img/empty/ordersEmpty.svg'
|
||||
import { useTransitionStore } from '../../stores/transitionStore'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
loading: boolean
|
||||
flag: boolean
|
||||
padding?: string
|
||||
isSearch?: boolean
|
||||
}>(),
|
||||
{},
|
||||
)
|
||||
const emit = defineEmits<{ (e: 'fetch'): void }>()
|
||||
const transition = useTransitionStore()
|
||||
const scrollArea = ref(null)
|
||||
|
||||
useInfiniteScroll(
|
||||
scrollArea,
|
||||
() => {
|
||||
emit('fetch')
|
||||
},
|
||||
{ distance: 50 },
|
||||
)
|
||||
</script>
|
||||
@@ -0,0 +1,50 @@
|
||||
<!--
|
||||
Ported from legacy henza_pwa/src/components/ui/ScrollFetchSearch.vue.
|
||||
EXISTING ISSUE, ported as-is: both the isFav and non-isFav empty states use
|
||||
the same `favoritEmpty` image (legacy imported cartEmpty/emp_search/
|
||||
emp_favorite too, but never actually used them in the template — those
|
||||
unused imports are dropped here as genuinely dead code; the duplicate-image
|
||||
*behavior* itself is preserved).
|
||||
-->
|
||||
<template>
|
||||
<main
|
||||
class="flex flex-col flex-1 overflow-auto no-scrollbar w-[100vw] max-w-screen-md m-auto"
|
||||
:style="{ paddingTop: padding || transition.headerHeight + 'px' }"
|
||||
ref="scrollArea"
|
||||
>
|
||||
<slot></slot>
|
||||
<Spinner cls="my-[10px]" :spinning="loading" :indicator="true" tip="درحال دریافت اطلاعات" />
|
||||
<EmptyState v-if="flag && !isFav" :image="favoritEmpty" />
|
||||
<EmptyState v-if="flag && isFav" :image="favoritEmpty" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useInfiniteScroll } from '@vueuse/core'
|
||||
import EmptyState from '../EmptyState.vue'
|
||||
import Spinner from '../Spinner.vue'
|
||||
import favoritEmpty from '../../assets/img/empty/favoritEmpty.svg'
|
||||
import { useTransitionStore } from '../../stores/transitionStore'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
loading: boolean
|
||||
flag: boolean
|
||||
padding?: string
|
||||
isFav?: boolean
|
||||
}>(),
|
||||
{},
|
||||
)
|
||||
const emit = defineEmits<{ (e: 'fetch'): void }>()
|
||||
const transition = useTransitionStore()
|
||||
const scrollArea = ref(null)
|
||||
|
||||
useInfiniteScroll(
|
||||
scrollArea,
|
||||
() => {
|
||||
emit('fetch')
|
||||
},
|
||||
{ distance: 50 },
|
||||
)
|
||||
</script>
|
||||
Reference in New Issue
Block a user