Files
ghatreh_user/src/components/pwa/PwaInstallGuideModal.vue
T

376 lines
8.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import BasicButton from '@/components/BasicPacks/BasicButton.vue'
import {
PWA_GUIDE_STEP_LABELS,
PWA_INSTALL_GUIDE,
type PwaPlatform,
} from '@/modules/pwa/pwaInstallGuideSteps'
import { getDefaultPwaPlatform } from '@/modules/pwa/pwaInstall'
import PwaInstallStepIllustration from '@/components/pwa/PwaInstallStepIllustration.vue'
const props = defineProps<{
open: boolean
platform?: PwaPlatform
}>()
const emit = defineEmits<{
'update:open': [value: boolean]
complete: []
close: []
}>()
const activePlatform = ref<PwaPlatform>(props.platform ?? getDefaultPwaPlatform())
const stepIndex = ref(0)
const guide = computed(() => PWA_INSTALL_GUIDE[activePlatform.value])
const currentStep = computed(() => guide.value.steps[stepIndex.value])
const totalSteps = computed(() => guide.value.steps.length)
const isFirstStep = computed(() => stepIndex.value === 0)
const isLastStep = computed(() => stepIndex.value === totalSteps.value - 1)
const stepLabel = computed(() => PWA_GUIDE_STEP_LABELS[stepIndex.value] ?? String(stepIndex.value + 1))
watch(
() => props.open,
(visible) => {
if (!visible) return
activePlatform.value = props.platform ?? getDefaultPwaPlatform()
stepIndex.value = 0
},
)
watch(
() => props.platform,
(value) => {
if (value) activePlatform.value = value
},
)
function close() {
emit('update:open', false)
emit('close')
}
function selectPlatform(next: PwaPlatform) {
if (activePlatform.value === next) return
activePlatform.value = next
stepIndex.value = 0
}
function goNext() {
if (isLastStep.value) {
emit('complete')
close()
return
}
stepIndex.value += 1
}
function goPrev() {
if (isFirstStep.value) return
stepIndex.value -= 1
}
function completeGuide() {
emit('complete')
close()
}
</script>
<template>
<Teleport to="body">
<div
v-if="open"
class="pwa-install-guide"
role="dialog"
aria-modal="true"
aria-labelledby="pwa-install-guide-title"
@click.self="close"
>
<div class="pwa-install-guide__card">
<div class="pwa-install-guide__tabs">
<button
type="button"
class="pwa-install-guide__tab"
:class="{ 'pwa-install-guide__tab--active': activePlatform === 'ios' }"
@click="selectPlatform('ios')"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<rect x="7" y="2" width="10" height="20" rx="2.5" stroke="currentColor" stroke-width="1.5" />
<circle cx="12" cy="18.5" r="1" fill="currentColor" />
</svg>
<span>آیفون</span>
</button>
<button
type="button"
class="pwa-install-guide__tab"
:class="{ 'pwa-install-guide__tab--active': activePlatform === 'android' }"
@click="selectPlatform('android')"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M12 4v12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
<path d="M8.5 8.5 12 5l3.5 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path d="M6 16h12v3a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-3Z" stroke="currentColor" stroke-width="1.5" />
</svg>
<span>اندروید</span>
</button>
</div>
<div class="pwa-install-guide__header">
<span class="pwa-install-guide__progress">
مرحله {{ stepLabel }} از {{ PWA_GUIDE_STEP_LABELS.length }}
</span>
<h2 id="pwa-install-guide-title" class="pwa-install-guide__title">
{{ guide.title }}
</h2>
</div>
<div class="pwa-install-guide__content">
<span class="pwa-install-guide__badge">{{ stepLabel }}</span>
<PwaInstallStepIllustration :type="currentStep.illustration" />
<p class="pwa-install-guide__text">{{ currentStep.text }}</p>
<p v-if="currentStep.hint" class="pwa-install-guide__hint">{{ currentStep.hint }}</p>
</div>
<div class="pwa-install-guide__nav">
<template v-if="isLastStep">
<BasicButton class="pwa-install-guide__primary" type="primary" @click="completeGuide">
متوجه شدم
</BasicButton>
</template>
<template v-else>
<button
type="button"
class="pwa-install-guide__nav-btn"
:disabled="isFirstStep"
aria-label="مرحله قبل"
@click="goPrev"
>
‹
</button>
<div class="pwa-install-guide__dots" aria-hidden="true">
<span
v-for="(_, index) in totalSteps"
:key="index"
class="pwa-install-guide__dot"
:class="{ 'pwa-install-guide__dot--active': index === stepIndex }"
/>
</div>
<button
type="button"
class="pwa-install-guide__nav-btn"
aria-label="مرحله بعد"
@click="goNext"
>
›
</button>
</template>
</div>
<template v-if="!isLastStep">
<BasicButton class="pwa-install-guide__primary" type="primary" @click="goNext">
بعدی
<span class="pwa-install-guide__primary-chevron">›</span>
</BasicButton>
<button type="button" class="pwa-install-guide__close" @click="close">
بستن
</button>
</template>
</div>
</div>
</Teleport>
</template>
<style scoped lang="less">
.pwa-install-guide {
position: fixed;
inset: 0;
z-index: var(--app-z-modal, 5500);
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
background: rgba(15, 23, 42, 0.45);
}
.pwa-install-guide__card {
width: min(100%, 360px);
background: #fff;
border-radius: 24px;
padding: 14px 16px 18px;
box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);
}
.pwa-install-guide__tabs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
padding: 4px;
border-radius: 14px;
background: #f1f5f9;
}
.pwa-install-guide__tab {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
border: none;
border-radius: 10px;
padding: 10px 8px;
font-size: 13px;
font-weight: 600;
color: #64748b;
background: transparent;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
&--active {
color: #0f172a;
background: #fff;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
}
}
.pwa-install-guide__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin: 14px 2px 10px;
}
.pwa-install-guide__progress {
font-size: 12px;
color: #94a3b8;
white-space: nowrap;
}
.pwa-install-guide__title {
margin: 0;
font-size: 14px;
font-weight: 700;
color: #0f172a;
text-align: right;
}
.pwa-install-guide__content {
position: relative;
border: 1px solid #e2e8f0;
border-radius: 18px;
background: #f8fafc;
padding: 14px 14px 16px;
min-height: 280px;
}
.pwa-install-guide__badge {
position: absolute;
top: 12px;
right: 12px;
width: 28px;
height: 28px;
border-radius: 999px;
background: #2563eb;
color: #fff;
font-size: 13px;
font-weight: 700;
display: inline-flex;
align-items: center;
justify-content: center;
}
.pwa-install-guide__text {
margin: 10px 0 0;
font-size: 13px;
line-height: 1.8;
color: #334155;
text-align: center;
}
.pwa-install-guide__hint {
margin: 8px 0 0;
font-size: 11px;
line-height: 1.7;
color: #64748b;
text-align: center;
}
.pwa-install-guide__nav {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin: 14px 0 12px;
.pwa-install-guide__primary {
width: 100%;
margin: 0;
}
}
.pwa-install-guide__nav-btn {
width: 34px;
height: 34px;
border: 1px solid #e2e8f0;
border-radius: 10px;
background: #fff;
color: #334155;
font-size: 20px;
line-height: 1;
cursor: pointer;
&:disabled {
opacity: 0.35;
cursor: not-allowed;
}
}
.pwa-install-guide__dots {
display: flex;
align-items: center;
gap: 6px;
}
.pwa-install-guide__dot {
width: 7px;
height: 7px;
border-radius: 999px;
background: #cbd5e1;
transition: width 0.15s ease, background 0.15s ease;
&--active {
width: 22px;
background: #2563eb;
}
}
.pwa-install-guide__primary {
width: 100%;
height: 44px;
border-radius: 14px !important;
font-weight: 700;
}
.pwa-install-guide__primary-chevron {
margin-right: 4px;
font-size: 16px;
}
.pwa-install-guide__close {
display: block;
width: 100%;
margin-top: 10px;
border: none;
background: transparent;
color: #64748b;
font-size: 13px;
cursor: pointer;
}
</style>