Files
hedena_gold/apps/user-app/src/components/product/ProductRow.vue
T

207 lines
4.3 KiB
Vue
Raw Normal View History

<!--
Full-width horizontal product row for home «تازه‌ها».
Same fields and morph open as ProductCard — only the composition changes.
-->
<template>
<article ref="root" class="pr" data-product-origin @click="open">
<div class="pr__art">
<img v-if="image" v-lazy="image" :alt="title" />
<div v-else class="pr__artEmpty" aria-hidden="true">
<Icon icon="vuesax-linear:diamonds" size="22" />
</div>
<button
type="button"
class="pr__fav"
:class="{ 'is-on': favorites.isFavorite(id) }"
:aria-label="favorites.isFavorite(id) ? 'حذف از علاقه‌مندی' : 'افزودن به علاقه‌مندی'"
@click.stop="favorites.toggle(id)"
>
<Icon :icon="favorites.isFavorite(id) ? 'vuesax-linear:heart-fill' : 'vuesax-linear:heart'" size="15" />
</button>
<span v-if="!is_exist" class="pr__flag">ناموجود</span>
<span v-else-if="isPreOrder && qty === 0" class="pr__flag pr__flag--soft">پیش‌سفارش</span>
</div>
<div class="pr__body">
<h3 class="pr__title">{{ title }}</h3>
<div class="pr__meta">
<span class="pr__weight">{{ min_gram }} گرم</span>
<span v-if="wage != null" class="pr__wage">اجرت {{ wageLabel }}٪</span>
</div>
<span class="pr__cta">مشاهده</span>
</div>
<Icon icon="vuesax-linear:arrow-left-1" size="18" class="pr__chev" aria-hidden="true" />
</article>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import Icon from '../icon/Icon.vue'
import { useProduct } from '../../composables/useProduct'
import { openProduct } from '../../composables/useProductMorph'
import { useFavoritesStore } from '../../stores/favoritesStore'
import type { ProductSummary } from '../../api/client'
const props = defineProps<{ value: ProductSummary }>()
const favorites = useFavoritesStore()
const { image, id, title, min_gram, wage, is_exist, qty, isPreOrder } = useProduct(props.value)
const root = ref<HTMLElement | null>(null)
function open() {
openProduct(props.value, root.value)
}
const wageLabel = computed(() => {
const w = wage.value
if (w == null) return ''
return w % 1 === 0 ? String(w) : w.toFixed(2)
})
</script>
<style scoped lang="less">
.pr {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
padding: 8px 12px 8px 10px;
cursor: pointer;
background: @canvas;
border: 1px solid @hairline;
border-radius: 18px;
transition: transform 0.2s ease, border-color 0.2s ease;
}
.pr:active {
transform: scale(0.99);
}
.pr__art {
position: relative;
flex: 0 0 88px;
width: 88px;
height: 88px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 14px;
overflow: hidden;
background: #f7f7f7;
}
.pr__art img {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
padding: 4px;
box-sizing: border-box;
object-fit: contain;
object-position: center;
display: block;
}
html.theme-dark .pr__art img {
background: @photoMat;
border-radius: 10px;
}
.pr__artEmpty {
width: 100%;
height: 100%;
display: grid;
place-items: center;
color: @inkFaint;
}
.pr__fav {
position: absolute;
top: 6px;
left: 6px;
width: 26px;
height: 26px;
display: grid;
place-items: center;
border: 0;
cursor: pointer;
border-radius: 50%;
color: @inkMuted;
background: alpha(@canvas, 90%);
backdrop-filter: blur(6px);
}
.pr__fav.is-on {
color: @wine;
}
.pr__flag {
position: absolute;
top: 6px;
right: 6px;
padding: 3px 7px;
border-radius: 999px;
font-size: 9.5px;
font-weight: 700;
color: #fff;
background: @primary;
}
.pr__flag--soft {
background: @wineSoft;
}
.pr__body {
flex: 1 1 auto;
min-width: 0;
display: flex;
flex-direction: column;
gap: 5px;
}
.pr__title {
margin: 0;
font-size: 13.5px;
font-weight: 700;
color: @ink;
line-height: 1.5;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pr__meta {
display: flex;
align-items: center;
gap: 7px;
}
.pr__weight {
font-size: 12px;
font-weight: 700;
color: @inkSoft;
}
.pr__wage {
font-size: 11px;
color: @inkFaint;
padding-inline-start: 7px;
border-inline-start: 1px solid @hairline;
}
.pr__cta {
margin-top: 2px;
font-size: 11.5px;
font-weight: 700;
color: @wine;
}
.pr__chev {
flex: 0 0 auto;
color: @inkFaint;
}
</style>