日历优化
This commit is contained in:
@@ -3,12 +3,39 @@
|
|||||||
<div class="memory-calendar" aria-label="记忆日历">
|
<div class="memory-calendar" aria-label="记忆日历">
|
||||||
<div class="memory-calendar-head">
|
<div class="memory-calendar-head">
|
||||||
<div class="memory-calendar-nav">
|
<div class="memory-calendar-nav">
|
||||||
<button type="button" data-calendar-previous aria-label="上一个月">←</button>
|
<button class="memory-calendar-step" type="button" data-calendar-previous aria-label="上一个月">←</button>
|
||||||
<strong data-calendar-label aria-live="polite">本月</strong>
|
<div class="memory-calendar-period" aria-label="快速选择年月" aria-live="polite">
|
||||||
<button type="button" data-calendar-next aria-label="下一个月">→</button>
|
<button type="button" data-calendar-year aria-expanded="false" aria-controls="memory-calendar-jump">2026年</button>
|
||||||
|
<button type="button" data-calendar-month aria-expanded="false" aria-controls="memory-calendar-jump">本月</button>
|
||||||
|
</div>
|
||||||
|
<button class="memory-calendar-step" type="button" data-calendar-next aria-label="下一个月">→</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="memory-calendar-today" type="button" data-calendar-today>回到今天</button>
|
<button class="memory-calendar-today" type="button" data-calendar-today>回到今天</button>
|
||||||
</div>
|
</div>
|
||||||
|
<form class="memory-calendar-jump" id="memory-calendar-jump" data-calendar-jump hidden>
|
||||||
|
<label>
|
||||||
|
<span>年份</span>
|
||||||
|
<input type="number" min="1900" max="2100" step="1" inputmode="numeric" data-calendar-year-input required />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>月份</span>
|
||||||
|
<select data-calendar-month-select required>
|
||||||
|
<option value="1">1月</option>
|
||||||
|
<option value="2">2月</option>
|
||||||
|
<option value="3">3月</option>
|
||||||
|
<option value="4">4月</option>
|
||||||
|
<option value="5">5月</option>
|
||||||
|
<option value="6">6月</option>
|
||||||
|
<option value="7">7月</option>
|
||||||
|
<option value="8">8月</option>
|
||||||
|
<option value="9">9月</option>
|
||||||
|
<option value="10">10月</option>
|
||||||
|
<option value="11">11月</option>
|
||||||
|
<option value="12">12月</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<button type="submit">跳转</button>
|
||||||
|
</form>
|
||||||
<div class="memory-calendar-weekdays" aria-hidden="true">
|
<div class="memory-calendar-weekdays" aria-hidden="true">
|
||||||
<span>日</span><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span>
|
<span>日</span><span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -72,7 +99,11 @@
|
|||||||
const summary = root.querySelector<HTMLElement>("[data-memory-summary]");
|
const summary = root.querySelector<HTMLElement>("[data-memory-summary]");
|
||||||
const grid = root.querySelector<HTMLElement>("[data-memory-grid]");
|
const grid = root.querySelector<HTMLElement>("[data-memory-grid]");
|
||||||
const empty = root.querySelector<HTMLElement>("[data-memory-empty]");
|
const empty = root.querySelector<HTMLElement>("[data-memory-empty]");
|
||||||
const calendarLabel = root.querySelector<HTMLElement>("[data-calendar-label]");
|
const calendarYearButton = root.querySelector<HTMLButtonElement>("[data-calendar-year]");
|
||||||
|
const calendarMonthButton = root.querySelector<HTMLButtonElement>("[data-calendar-month]");
|
||||||
|
const jumpForm = root.querySelector<HTMLFormElement>("[data-calendar-jump]");
|
||||||
|
const yearInput = root.querySelector<HTMLInputElement>("[data-calendar-year-input]");
|
||||||
|
const monthSelect = root.querySelector<HTMLSelectElement>("[data-calendar-month-select]");
|
||||||
const calendarGrid = root.querySelector<HTMLElement>("[data-calendar-grid]");
|
const calendarGrid = root.querySelector<HTMLElement>("[data-calendar-grid]");
|
||||||
const previousButton = root.querySelector<HTMLButtonElement>("[data-calendar-previous]");
|
const previousButton = root.querySelector<HTMLButtonElement>("[data-calendar-previous]");
|
||||||
const nextButton = root.querySelector<HTMLButtonElement>("[data-calendar-next]");
|
const nextButton = root.querySelector<HTMLButtonElement>("[data-calendar-next]");
|
||||||
@@ -86,6 +117,26 @@
|
|||||||
return onThisDayMemories.filter((memory) => memory.monthDay === monthDay);
|
return onThisDayMemories.filter((memory) => memory.monthDay === monthDay);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setJumpOpen = (open: boolean, focusTarget?: "year" | "month") => {
|
||||||
|
if (!jumpForm) return;
|
||||||
|
jumpForm.hidden = !open;
|
||||||
|
calendarYearButton?.setAttribute("aria-expanded", String(open));
|
||||||
|
calendarMonthButton?.setAttribute("aria-expanded", String(open));
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
if (yearInput) yearInput.value = String(calendarYear);
|
||||||
|
if (monthSelect) monthSelect.value = String(calendarMonth);
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
if (focusTarget === "year") {
|
||||||
|
yearInput?.focus();
|
||||||
|
yearInput?.select();
|
||||||
|
} else {
|
||||||
|
monthSelect?.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const renderMemories = (month: number, day: number) => {
|
const renderMemories = (month: number, day: number) => {
|
||||||
selectedMonth = month;
|
selectedMonth = month;
|
||||||
selectedDay = day;
|
selectedDay = day;
|
||||||
@@ -165,7 +216,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderCalendar = () => {
|
const renderCalendar = () => {
|
||||||
if (calendarLabel) calendarLabel.textContent = `${calendarYear}年${calendarMonth}月`;
|
if (calendarYearButton) calendarYearButton.textContent = `${calendarYear}年`;
|
||||||
|
if (calendarMonthButton) calendarMonthButton.textContent = `${calendarMonth}月`;
|
||||||
if (!calendarGrid) return;
|
if (!calendarGrid) return;
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
@@ -225,10 +277,30 @@
|
|||||||
if (!button) return;
|
if (!button) return;
|
||||||
|
|
||||||
renderMemories(calendarMonth, Number(button.dataset.calendarDay));
|
renderMemories(calendarMonth, Number(button.dataset.calendarDay));
|
||||||
|
setJumpOpen(false);
|
||||||
|
renderCalendar();
|
||||||
|
});
|
||||||
|
|
||||||
|
calendarYearButton?.addEventListener("click", () => {
|
||||||
|
setJumpOpen(jumpForm?.hidden ?? true, "year");
|
||||||
|
});
|
||||||
|
|
||||||
|
calendarMonthButton?.addEventListener("click", () => {
|
||||||
|
setJumpOpen(jumpForm?.hidden ?? true, "month");
|
||||||
|
});
|
||||||
|
|
||||||
|
jumpForm?.addEventListener("submit", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!jumpForm.reportValidity() || !yearInput || !monthSelect) return;
|
||||||
|
|
||||||
|
calendarYear = Number(yearInput.value);
|
||||||
|
calendarMonth = Number(monthSelect.value);
|
||||||
|
setJumpOpen(false);
|
||||||
renderCalendar();
|
renderCalendar();
|
||||||
});
|
});
|
||||||
|
|
||||||
previousButton?.addEventListener("click", () => {
|
previousButton?.addEventListener("click", () => {
|
||||||
|
setJumpOpen(false);
|
||||||
calendarMonth -= 1;
|
calendarMonth -= 1;
|
||||||
if (calendarMonth === 0) {
|
if (calendarMonth === 0) {
|
||||||
calendarMonth = 12;
|
calendarMonth = 12;
|
||||||
@@ -238,6 +310,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
nextButton?.addEventListener("click", () => {
|
nextButton?.addEventListener("click", () => {
|
||||||
|
setJumpOpen(false);
|
||||||
calendarMonth += 1;
|
calendarMonth += 1;
|
||||||
if (calendarMonth === 13) {
|
if (calendarMonth === 13) {
|
||||||
calendarMonth = 1;
|
calendarMonth = 1;
|
||||||
@@ -247,12 +320,28 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
todayButton?.addEventListener("click", () => {
|
todayButton?.addEventListener("click", () => {
|
||||||
|
setJumpOpen(false);
|
||||||
calendarYear = currentYear;
|
calendarYear = currentYear;
|
||||||
calendarMonth = todayMonth;
|
calendarMonth = todayMonth;
|
||||||
renderMemories(todayMonth, todayDay);
|
renderMemories(todayMonth, todayDay);
|
||||||
renderCalendar();
|
renderCalendar();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", (event) => {
|
||||||
|
if (jumpForm?.hidden) return;
|
||||||
|
const target = event.target;
|
||||||
|
if (!(target instanceof Node)) return;
|
||||||
|
if (jumpForm?.contains(target) || calendarYearButton?.contains(target) || calendarMonthButton?.contains(target)) return;
|
||||||
|
setJumpOpen(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
root.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key === "Escape" && jumpForm && !jumpForm.hidden) {
|
||||||
|
setJumpOpen(false);
|
||||||
|
calendarYearButton?.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
renderMemories(todayMonth, todayDay);
|
renderMemories(todayMonth, todayDay);
|
||||||
renderCalendar();
|
renderCalendar();
|
||||||
}
|
}
|
||||||
|
|||||||
+97
-8
@@ -275,12 +275,6 @@ h1 {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.memory-calendar-nav strong {
|
|
||||||
min-width: 126px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.memory-calendar-nav button,
|
.memory-calendar-nav button,
|
||||||
.memory-calendar-today {
|
.memory-calendar-today {
|
||||||
min-height: 38px;
|
min-height: 38px;
|
||||||
@@ -293,12 +287,44 @@ h1 {
|
|||||||
transition: border-color 160ms ease, background 160ms ease, color 160ms ease;
|
transition: border-color 160ms ease, background 160ms ease, color 160ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.memory-calendar-nav button {
|
.memory-calendar-step {
|
||||||
width: 38px;
|
width: 38px;
|
||||||
|
flex: 0 0 38px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.memory-calendar-period {
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-calendar-period button {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 38px;
|
||||||
|
padding: 4px 7px;
|
||||||
|
border-color: transparent;
|
||||||
|
border-radius: 7px;
|
||||||
|
background: transparent;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-calendar-period button::after {
|
||||||
|
content: "⌄";
|
||||||
|
margin-left: 3px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-calendar-period button:hover,
|
||||||
|
.memory-calendar-period button[aria-expanded="true"] {
|
||||||
|
border-color: rgba(55, 109, 90, 0.2);
|
||||||
|
background: rgba(55, 109, 90, 0.08);
|
||||||
|
color: var(--green);
|
||||||
|
}
|
||||||
|
|
||||||
.memory-calendar-today {
|
.memory-calendar-today {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 7px 13px;
|
padding: 7px 13px;
|
||||||
@@ -307,13 +333,76 @@ h1 {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.memory-calendar-nav button:hover,
|
.memory-calendar-step:hover,
|
||||||
.memory-calendar-today:hover {
|
.memory-calendar-today:hover {
|
||||||
border-color: var(--green);
|
border-color: var(--green);
|
||||||
background: var(--green);
|
background: var(--green);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.memory-calendar-period button:hover::after,
|
||||||
|
.memory-calendar-period button[aria-expanded="true"]::after {
|
||||||
|
color: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-calendar-jump {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin: -4px 0 16px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid rgba(55, 109, 90, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fffdf7;
|
||||||
|
box-shadow: 0 12px 28px rgba(39, 55, 52, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-calendar-jump[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-calendar-jump label {
|
||||||
|
display: grid;
|
||||||
|
gap: 5px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-calendar-jump input,
|
||||||
|
.memory-calendar-jump select {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 38px;
|
||||||
|
padding: 6px 9px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 7px;
|
||||||
|
outline: none;
|
||||||
|
background: #fff;
|
||||||
|
color: var(--ink);
|
||||||
|
font: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-calendar-jump input:focus,
|
||||||
|
.memory-calendar-jump select:focus {
|
||||||
|
border-color: var(--green);
|
||||||
|
box-shadow: 0 0 0 3px rgba(55, 109, 90, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.memory-calendar-jump > button {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
min-height: 38px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 7px;
|
||||||
|
background: var(--green);
|
||||||
|
color: #fff;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.memory-calendar-weekdays,
|
.memory-calendar-weekdays,
|
||||||
.memory-calendar-grid {
|
.memory-calendar-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
Reference in New Issue
Block a user