From 83d25ec1c504d34b6cd4db81a107904df41d0b12 Mon Sep 17 00:00:00 2001 From: Mahdi Date: Sat, 5 Sep 2026 01:56:50 +0330 Subject: [PATCH] Replace time selects with circular dial pickers in JalaliDateInput. Co-authored-by: Cursor --- apps/web/src/components/JalaliDateInput.vue | 321 +++++++++++++++++--- 1 file changed, 284 insertions(+), 37 deletions(-) diff --git a/apps/web/src/components/JalaliDateInput.vue b/apps/web/src/components/JalaliDateInput.vue index 478f82b..4999d50 100644 --- a/apps/web/src/components/JalaliDateInput.vue +++ b/apps/web/src/components/JalaliDateInput.vue @@ -94,14 +94,51 @@ const monthTitle = computed( ); const hours = Array.from({ length: 24 }, (_, i) => i); +const minuteMarks = Array.from({ length: 12 }, (_, i) => i * 5); const minutes = computed(() => { - const steps = Array.from({ length: 12 }, (_, i) => i * 5); - if (!steps.includes(draftMinute.value)) { - return [...steps, draftMinute.value].sort((a, b) => a - b); + if (!minuteMarks.includes(draftMinute.value)) { + return [...minuteMarks, draftMinute.value].sort((a, b) => a - b); } - return steps; + return minuteMarks; }); +const draftTimeLabel = computed( + () => + `${toFaDigits(String(draftHour.value).padStart(2, '0'))}:${toFaDigits(String(draftMinute.value).padStart(2, '0'))}`, +); + +function dialPoint(index: number, total: number, radius: number) { + const angle = (index / total) * 2 * Math.PI - Math.PI / 2; + return { + left: `calc(50% + ${Math.cos(angle) * radius}px)`, + top: `calc(50% + ${Math.sin(angle) * radius}px)`, + }; +} + +function hourDialStyle(hour: number) { + // حلقه داخلی ۰–۱۱، حلقه بیرونی ۱۲–۲۳ + const ring = hour < 12 ? 46 : 72; + const index = hour % 12; + return dialPoint(index, 12, ring); +} + +function minuteDialStyle(minute: number) { + const index = minuteMarks.includes(minute) + ? minuteMarks.indexOf(minute) + : Math.round(minute / 5) % 12; + return dialPoint(index, 12, 64); +} + +function setHour(hour: number) { + draftHour.value = hour; + applyTime(); +} + +function setMinute(minute: number) { + draftMinute.value = minute; + applyTime(); +} + const cells = computed(() => { const { jy, jm } = cursor.value; const first = jalaliToDate(jy, jm, 1); @@ -178,8 +215,8 @@ function place() { const panel = panelRef.value; if (!trigger) return; const rect = trigger.getBoundingClientRect(); - const width = panel?.offsetWidth || 320; - const height = panel?.offsetHeight || 360; + const width = panel?.offsetWidth || (props.mode === 'time' ? 340 : 320); + const height = panel?.offsetHeight || (props.mode === 'time' ? 420 : 360); let nextLeft = rect.left; if (getComputedStyle(document.documentElement).direction === 'rtl') { nextLeft = rect.right - width; @@ -298,6 +335,14 @@ watch(open, (value) => { window.removeEventListener('keydown', onKeydown, true); }); +watch( + () => model.value, + () => { + if (props.mode === 'time') syncDraftFrom(null); + }, + { immediate: true }, +); + onBeforeUnmount(() => { window.removeEventListener('resize', onWindowChange); window.removeEventListener('scroll', onWindowChange, true); @@ -307,19 +352,40 @@ onBeforeUnmount(() => { -
- - +
+

{{ draftTimeLabel }}

+
+
+ ساعت +
+ {{ toFaDigits(String(draftHour).padStart(2, '0')) }} + +
+
+
+ دقیقه +
+ {{ toFaDigits(String(draftMinute).padStart(2, '0')) }} + +
+
+