Responsive layout

This commit is contained in:
Leo Vasanko 2025-08-25 09:54:53 -06:00
parent dd8b231cbc
commit ff6657cbcc
4 changed files with 52 additions and 107 deletions

View File

@ -1,14 +1,9 @@
/* Layout variables */
:root { :root {
/* Layout */ --week-w: 3rem;
--row-h: 2.2em;
--week-w: 3em;
--day-w: 1fr; --day-w: 1fr;
--row-h: 12vh; --month-w: 2rem;
--month-w: 4em; --row-h: 15vh;
} }
/* Layout & typography */
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
@ -100,35 +95,10 @@ header {
#calendar-viewport::-webkit-scrollbar { #calendar-viewport::-webkit-scrollbar {
display: none; display: none;
} }
.jogwheel-viewport,
#jogwheel-viewport {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: var(--month-w);
overflow-y: auto;
overflow-x: hidden;
scrollbar-width: none;
z-index: 20;
cursor: ns-resize;
}
.jogwheel-viewport::-webkit-scrollbar,
#jogwheel-viewport::-webkit-scrollbar {
display: none;
}
.jogwheel-content,
#jogwheel-content {
position: relative;
width: 100%;
}
.calendar-content, .calendar-content,
#calendar-content { #calendar-content {
position: relative; position: relative;
} }
/* Week row: label + 7-day grid + jogwheel column */ /* Week row: label + 7-day grid + jogwheel column */
.week-row { .week-row {
display: grid; display: grid;

View File

@ -347,7 +347,7 @@ function createWeek(virtualWeek) {
text: `${getLocalizedMonthName(monthToLabel)} '${year}`, text: `${getLocalizedMonthName(monthToLabel)} '${year}`,
month: monthToLabel, month: monthToLabel,
weeksSpan: weeksSpan, weeksSpan: weeksSpan,
height: weeksSpan * rowHeight.value, monthClass: monthAbbr[monthToLabel],
} }
} }
} }
@ -651,18 +651,6 @@ function openSettings() {
// Heuristic: rotate month label (180deg) only for predominantly Latin text. // Heuristic: rotate month label (180deg) only for predominantly Latin text.
// We explicitly avoid locale detection; rely solely on characters present. // We explicitly avoid locale detection; rely solely on characters present.
// Disable rotation if any CJK Unified Ideograph or Compatibility Ideograph appears. // Disable rotation if any CJK Unified Ideograph or Compatibility Ideograph appears.
function shouldRotateMonth(label) {
if (!label) return false
// Rotate ONLY if any Latin script alphabetic character is present.
// Prefer Unicode script property when supported.
try {
if (/\p{Script=Latin}/u.test(label)) return true
} catch (e) {
// Fallback for environments lacking Unicode property escapes.
if (/[A-Za-z\u00C0-\u024F\u1E00-\u1EFF]/u.test(label)) return true
}
return false
}
// Keep roughly same visible date when first_day setting changes. // Keep roughly same visible date when first_day setting changes.
watch( watch(
() => calendarStore.config.first_day, () => calendarStore.config.first_day,
@ -723,19 +711,6 @@ window.addEventListener('resize', () => {
@day-touchstart="handleDayTouchStart" @day-touchstart="handleDayTouchStart"
@event-click="handleEventClick" @event-click="handleEventClick"
/> />
<!-- Month labels positioned absolutely -->
<div
v-for="week in visibleWeeks"
:key="`month-${week.virtualWeek}`"
class="month-name-label"
:class="{ 'no-rotate': !shouldRotateMonth(week.monthLabel?.text) }"
:style="{
top: week.top + 'px',
height: week.monthLabel?.height + 'px',
}"
>
<span>{{ week.monthLabel?.text }}</span>
</div>
</div> </div>
</div> </div>
<!-- Jogwheel as sibling to calendar-viewport --> <!-- Jogwheel as sibling to calendar-viewport -->
@ -796,34 +771,6 @@ header h1 {
width: 100%; width: 100%;
} }
.month-name-label {
position: absolute;
right: 0;
width: 3rem; /* Match jogwheel width */
font-size: 2em;
font-weight: 700;
color: var(--muted);
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
z-index: 15;
overflow: visible;
}
.month-name-label > span {
display: inline-block;
white-space: nowrap;
writing-mode: vertical-rl;
text-orientation: mixed;
transform: rotate(180deg);
transform-origin: center;
}
.month-name-label.no-rotate > span {
transform: none;
}
.row-height-probe { .row-height-probe {
position: absolute; position: absolute;
visibility: hidden; visibility: hidden;

View File

@ -33,6 +33,16 @@ const handleDayTouchStart = (dateStr) => {
const handleEventClick = (payload) => { const handleEventClick = (payload) => {
emit('event-click', payload) emit('event-click', payload)
} }
// Only apply upside-down rotation (bottomup) for Latin script month labels
function shouldRotateMonth(label) {
if (!label) return false
try {
return /\p{Script=Latin}/u.test(label)
} catch (e) {
return /[A-Za-z\u00C0-\u024F\u1E00-\u1EFF]/u.test(label)
}
}
</script> </script>
<template> <template>
@ -51,13 +61,24 @@ const handleEventClick = (payload) => {
/> />
<EventOverlay :week="props.week" @event-click="handleEventClick" /> <EventOverlay :week="props.week" @event-click="handleEventClick" />
</div> </div>
<!-- Month name label (only on first week containing the 1st of the month) -->
<div
v-if="props.week.monthLabel"
class="month-label"
:class="props.week.monthLabel.monthClass"
:style="{ height: `calc(var(--row-h) * ${props.week.monthLabel.weeksSpan})` }"
>
<span :class="{ bottomup: shouldRotateMonth(props.week.monthLabel.text) }">{{
props.week.monthLabel.text
}}</span>
</div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.week-row { .week-row {
display: grid; display: grid;
grid-template-columns: var(--week-w) repeat(7, 1fr) 3rem; grid-template-columns: var(--week-w) repeat(7, 1fr) var(--month-w);
position: absolute; position: absolute;
height: var(--row-h); height: var(--row-h);
width: 100%; width: 100%;
@ -70,15 +91,8 @@ const handleEventClick = (payload) => {
color: var(--muted); color: var(--muted);
font-size: 1.2em; font-size: 1.2em;
font-weight: 500; font-weight: 500;
/* Prevent text selection */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none; user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
} }
.days-grid { .days-grid {
display: grid; display: grid;
grid-template-columns: repeat(7, 1fr); grid-template-columns: repeat(7, 1fr);
@ -86,10 +100,32 @@ const handleEventClick = (payload) => {
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
/* Fixed heights for cells and labels (from cells.css) */
.week-row :deep(.cell),
.week-label { .week-label {
height: var(--row-h); height: var(--row-h);
} }
.month-label {
position: absolute;
top: 0;
right: 0;
width: var(--month-w);
background-image: linear-gradient(to bottom, rgba(186, 186, 186, 0.3), rgba(186, 186, 186, 0.2));
font-size: 2em;
font-weight: 700;
color: var(--muted);
display: flex;
align-items: center;
justify-content: center;
z-index: 15;
overflow: hidden;
}
.month-label > span {
display: inline-block;
white-space: nowrap;
writing-mode: vertical-rl;
text-orientation: mixed;
transform-origin: center;
}
.bottomup {
transform: rotate(180deg);
}
</style> </style>

View File

@ -183,20 +183,12 @@ defineExpose({
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
width: 3rem; /* Use fixed width since minmax() doesn't work for absolute positioning */ width: var(--month-w);
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
scrollbar-width: none; scrollbar-width: none;
z-index: 20; z-index: 20;
cursor: ns-resize; cursor: ns-resize;
background: rgba(0, 0, 0, 0.05); /* Temporary background to see the area */
/* Prevent text selection */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
} }
.jogwheel-viewport::-webkit-scrollbar { .jogwheel-viewport::-webkit-scrollbar {