feat: let the Jalali date picker jump to any month or year from its title
This commit is contained in:
@@ -59,6 +59,11 @@ const draftMinute = ref(0);
|
||||
const clockFace = ref<'hour' | 'minute'>('hour');
|
||||
const isMobile = ref(false);
|
||||
const dragging = ref(false);
|
||||
/** نمای تقویم: روزها ← کلیک عنوان → ماهها ← کلیک عنوان → سالها */
|
||||
const calView = ref<'days' | 'months' | 'years'>('days');
|
||||
const YEAR_PAGE = 12;
|
||||
const yearPageStart = ref(todayJ.jy - 5);
|
||||
const MONTHS = Array.from({ length: 12 }, (_, i) => i + 1);
|
||||
|
||||
type Cell = {
|
||||
key: string;
|
||||
@@ -107,6 +112,26 @@ const monthTitle = computed(
|
||||
() => `${jalaliMonthName(cursor.value.jm)} ${toFaDigits(cursor.value.jy)}`,
|
||||
);
|
||||
|
||||
const navTitle = computed(() => {
|
||||
if (calView.value === 'months') return toFaDigits(cursor.value.jy);
|
||||
if (calView.value === 'years') {
|
||||
return `${toFaDigits(yearPageStart.value)} – ${toFaDigits(yearPageStart.value + YEAR_PAGE - 1)}`;
|
||||
}
|
||||
return monthTitle.value;
|
||||
});
|
||||
|
||||
const navLabels = computed(() => {
|
||||
if (calView.value === 'months') return { forward: 'سال بعد', back: 'سال قبل' };
|
||||
if (calView.value === 'years') return { forward: 'سالهای بعد', back: 'سالهای قبل' };
|
||||
return { forward: 'ماه بعد', back: 'ماه قبل' };
|
||||
});
|
||||
|
||||
const yearOptions = computed(() =>
|
||||
Array.from({ length: YEAR_PAGE }, (_, i) => yearPageStart.value + i),
|
||||
);
|
||||
|
||||
const selectedJ = computed(() => (parsed.value ? dateToJalali(parsed.value) : null));
|
||||
|
||||
const hours = Array.from({ length: 24 }, (_, i) => i);
|
||||
const minuteMarks = Array.from({ length: 12 }, (_, i) => i * 5);
|
||||
const columnMinutes = Array.from({ length: 60 }, (_, i) => i);
|
||||
@@ -316,6 +341,7 @@ function onWindowChange() {
|
||||
async function show() {
|
||||
if (props.disabled) return;
|
||||
syncDraftFrom(parsed.value);
|
||||
calView.value = 'days';
|
||||
clockFace.value = 'hour';
|
||||
syncViewport();
|
||||
open.value = true;
|
||||
@@ -341,6 +367,38 @@ function goMonth(delta: number) {
|
||||
void nextTick(place);
|
||||
}
|
||||
|
||||
function navStep(delta: number) {
|
||||
if (calView.value === 'months') {
|
||||
cursor.value = { jy: cursor.value.jy + delta, jm: cursor.value.jm };
|
||||
} else if (calView.value === 'years') {
|
||||
yearPageStart.value += delta * YEAR_PAGE;
|
||||
} else {
|
||||
goMonth(delta);
|
||||
}
|
||||
}
|
||||
|
||||
function onTitleClick() {
|
||||
if (calView.value === 'days') {
|
||||
calView.value = 'months';
|
||||
} else if (calView.value === 'months') {
|
||||
yearPageStart.value = cursor.value.jy - 5;
|
||||
calView.value = 'years';
|
||||
}
|
||||
void nextTick(place);
|
||||
}
|
||||
|
||||
function pickMonth(jm: number) {
|
||||
cursor.value = { jy: cursor.value.jy, jm };
|
||||
calView.value = 'days';
|
||||
void nextTick(place);
|
||||
}
|
||||
|
||||
function pickYear(jy: number) {
|
||||
cursor.value = { jy, jm: cursor.value.jm };
|
||||
calView.value = 'months';
|
||||
void nextTick(place);
|
||||
}
|
||||
|
||||
function pickDay(cell: Cell) {
|
||||
if (!cell.inMonth) {
|
||||
cursor.value = { jy: cell.jy, jm: cell.jm };
|
||||
@@ -483,16 +541,53 @@ onBeforeUnmount(() => {
|
||||
:style="{ top: `${top}px`, left: `${left}px` }"
|
||||
>
|
||||
<header v-if="mode !== 'time'" class="nav">
|
||||
<a-button type="text" class="nav-btn" aria-label="ماه بعد" @click="goMonth(1)">
|
||||
<a-button type="text" class="nav-btn" :aria-label="navLabels.forward" @click="navStep(1)">
|
||||
<template #icon><RightOutlined /></template>
|
||||
</a-button>
|
||||
<strong>{{ monthTitle }}</strong>
|
||||
<a-button type="text" class="nav-btn next" aria-label="ماه قبل" @click="goMonth(-1)">
|
||||
<a-button
|
||||
type="text"
|
||||
class="nav-title"
|
||||
:class="{ static: calView === 'years' }"
|
||||
:aria-label="calView === 'days' ? 'انتخاب ماه و سال' : calView === 'months' ? 'انتخاب سال' : undefined"
|
||||
@click="onTitleClick"
|
||||
>
|
||||
{{ navTitle }}
|
||||
</a-button>
|
||||
<a-button type="text" class="nav-btn next" :aria-label="navLabels.back" @click="navStep(-1)">
|
||||
<template #icon><LeftOutlined /></template>
|
||||
</a-button>
|
||||
</header>
|
||||
|
||||
<template v-if="mode !== 'time'">
|
||||
<div v-if="mode !== 'time' && calView === 'months'" class="pick-grid">
|
||||
<a-button
|
||||
v-for="m in MONTHS"
|
||||
:key="`pm-${m}`"
|
||||
type="text"
|
||||
class="pick-cell"
|
||||
:class="{
|
||||
on: selectedJ?.jy === cursor.jy && selectedJ?.jm === m,
|
||||
today: todayJ.jy === cursor.jy && todayJ.jm === m,
|
||||
}"
|
||||
@click="pickMonth(m)"
|
||||
>
|
||||
{{ jalaliMonthName(m) }}
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="mode !== 'time' && calView === 'years'" class="pick-grid">
|
||||
<a-button
|
||||
v-for="y in yearOptions"
|
||||
:key="`py-${y}`"
|
||||
type="text"
|
||||
class="pick-cell"
|
||||
:class="{ on: selectedJ?.jy === y, today: todayJ.jy === y }"
|
||||
@click="pickYear(y)"
|
||||
>
|
||||
{{ toFaDigits(y) }}
|
||||
</a-button>
|
||||
</div>
|
||||
|
||||
<template v-if="mode !== 'time' && calView === 'days'">
|
||||
<div class="week">
|
||||
<span v-for="day in WEEKDAYS" :key="day">{{ day }}</span>
|
||||
</div>
|
||||
@@ -781,8 +876,42 @@ onBeforeUnmount(() => {
|
||||
margin-bottom: 0.55rem;
|
||||
}
|
||||
|
||||
.nav strong {
|
||||
.nav-title {
|
||||
font-size: 0.92rem;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
}
|
||||
|
||||
.nav-title:not(.static):hover {
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.nav-title.static {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.pick-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.35rem;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
.pick-cell {
|
||||
height: 44px;
|
||||
border-radius: var(--radius);
|
||||
color: #0f172a;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.pick-cell.today {
|
||||
box-shadow: inset 0 0 0 1px #99f6e4;
|
||||
}
|
||||
|
||||
.pick-cell.on,
|
||||
.pick-cell:hover {
|
||||
background: #0d9488;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
|
||||
Reference in New Issue
Block a user