diff --git a/apps/calendar-macos/CompassKit/Sources/CompassKit/Life/DatePickerCaption.swift b/apps/calendar-macos/CompassKit/Sources/CompassKit/Life/DatePickerCaption.swift index 405bc2d3b7..2eef42dfc6 100644 --- a/apps/calendar-macos/CompassKit/Sources/CompassKit/Life/DatePickerCaption.swift +++ b/apps/calendar-macos/CompassKit/Sources/CompassKit/Life/DatePickerCaption.swift @@ -28,12 +28,6 @@ public enum DatePickerCaption { return (start ..< start + yearsPerPage).map { $0 } } - /// Decade-aligned pages (1980–1999 for 1993); legacy sidebar grids. - public static func yearPage(containing year: Int) -> [Int] { - let start = year - ((year % yearsPerPage) + yearsPerPage) % yearsPerPage - return (start ..< start + yearsPerPage).map { $0 } - } - public static let monthLabels = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ] diff --git a/apps/calendar-web/src/components/DatePicker/caption-picker-year-page.test.ts b/apps/calendar-web/src/components/DatePicker/caption-picker-year-page.test.ts index 46fda33455..dc5752588f 100644 --- a/apps/calendar-web/src/components/DatePicker/caption-picker-year-page.test.ts +++ b/apps/calendar-web/src/components/DatePicker/caption-picker-year-page.test.ts @@ -1,5 +1,4 @@ import { - macYearRangeCaption, reactDatepickerYearPageBounds, shiftYearPageBounds, yearRangeCaptionFromBounds, @@ -33,8 +32,4 @@ describe("caption-picker-year-page", () => { expect(shiftYearPageBounds(page, -1)).toEqual({ start: 1961, end: 1980 }); expect(shiftYearPageBounds(page, 1)).toEqual({ start: 2001, end: 2020 }); }); - - it("keeps mac decade alignment separate", () => { - expect(macYearRangeCaption(1993)).toBe("Years 1980 to 1999"); - }); }); diff --git a/apps/calendar-web/src/components/DatePicker/caption-picker-year-page.ts b/apps/calendar-web/src/components/DatePicker/caption-picker-year-page.ts index dabf4a9719..6aea14f809 100644 --- a/apps/calendar-web/src/components/DatePicker/caption-picker-year-page.ts +++ b/apps/calendar-web/src/components/DatePicker/caption-picker-year-page.ts @@ -1,4 +1,4 @@ -/** Keep in sync with `DatePickerCaption.reactYearsPerPage` on macOS. */ +/** Keep in sync with `DatePickerCaption.yearsPerPage` on macOS. */ export const CAPTION_YEARS_PER_PAGE = 20; export type YearPageBounds = { readonly start: number; readonly end: number }; @@ -30,23 +30,3 @@ export const yearRangeCaptionFromBounds = (bounds: YearPageBounds): string => { const { start, end } = bounds; return start === end ? `Year ${start}` : `Years ${start} to ${end}`; }; - -/** macOS decade-aligned pages (0, 20, 40, …) for non-react grids. */ -export const macYearPageContaining = ( - year: number, - pageSize = CAPTION_YEARS_PER_PAGE, -): readonly number[] => { - const start = year - (((year % pageSize) + pageSize) % pageSize); - return Array.from({ length: pageSize }, (_, index) => start + index); -}; - -export const macYearRangeCaption = ( - anchorYear: number, - pageSize = CAPTION_YEARS_PER_PAGE, -): string => { - const page = macYearPageContaining(anchorYear, pageSize); - const first = page[0]; - const last = page[page.length - 1]; - if (first === undefined || last === undefined) return "Select year"; - return first === last ? `Year ${first}` : `Years ${first} to ${last}`; -}; diff --git a/apps/calendar-web/src/views/Life/LifeSidebarContent.tsx b/apps/calendar-web/src/views/Life/LifeSidebarContent.tsx index 47af1b1c56..1fd0793240 100644 --- a/apps/calendar-web/src/views/Life/LifeSidebarContent.tsx +++ b/apps/calendar-web/src/views/Life/LifeSidebarContent.tsx @@ -14,6 +14,10 @@ import { ID_LIFE_BIRTH_DATE } from "@web/common/constants/web.constants"; import { ArrowButton } from "@web/components/Button/ArrowButton"; import { NumberInput } from "@web/components/NumberInput/NumberInput"; import { TooltipWrapper } from "@web/components/Tooltip/TooltipWrapper"; +import { + selectPageJumpHintsVisible, + usePageJumpHintStore, +} from "@web/shortcuts/page-jump/page-jump.store"; import { pageJumpAttrs, pageJumpChipAttrs, @@ -46,6 +50,7 @@ export function LifeSidebarContent({ today, onPreferencesChange, }: LifeSidebarContentProps) { + const pageJumpHintsVisible = usePageJumpHintStore(selectPageJumpHintsVisible); const [isBirthDatePickerOpen, setIsBirthDatePickerOpen] = useState(false); const [lifespanInput, setLifespanInput] = useState(() => String(preferences.lifespan), @@ -124,9 +129,14 @@ export function LifeSidebarContent({ /> - {/* Portaled Mod chips do not affect layout; reserve space so chip 3 - sits between the arrows and the title without covering the label. */} - +

{LIFE_VARIATIONS[preferences.variation].label}

diff --git a/apps/calendar-web/src/views/Life/LifeView.test.tsx b/apps/calendar-web/src/views/Life/LifeView.test.tsx index 7fd3b4981c..7b5437b533 100644 --- a/apps/calendar-web/src/views/Life/LifeView.test.tsx +++ b/apps/calendar-web/src/views/Life/LifeView.test.tsx @@ -13,6 +13,7 @@ import { pressKey } from "@web/__tests__/utils/keyboard.test.util"; import { mockModuleForFile } from "@web/__tests__/utils/mock-module.test.util"; import { STORAGE_KEYS } from "@web/common/constants/storage.constants"; import { viewActions } from "@web/events/stores/view.store"; +import { pageJumpHintActions } from "@web/shortcuts/page-jump/page-jump.store"; import { LifeView } from "./LifeView"; import { afterAll, @@ -413,6 +414,15 @@ describe("LifeView", () => { expect(randomAge).toBeLessThanOrEqual(100); }); + it("does not shift the life variation title when Mod page-jump hints appear", async () => { + await renderLifeViewWithSidebar(); + const title = screen.getByRole("heading", { level: 2, name: "Average" }); + const before = title.getBoundingClientRect().left; + act(() => pageJumpHintActions.setHintsVisible(true)); + expect(title.getBoundingClientRect().left).toBe(before); + act(() => pageJumpHintActions.setHintsVisible(false)); + }); + it("cycles life variations with J and K", async () => { await renderLifeViewWithSidebar(); ( diff --git a/e2e/life/caption-year-grid-narrow.spec.ts b/e2e/life/caption-year-grid-narrow.spec.ts new file mode 100644 index 0000000000..8e1308987b --- /dev/null +++ b/e2e/life/caption-year-grid-narrow.spec.ts @@ -0,0 +1,79 @@ +import { expect, test } from "@playwright/test"; + +async function openLifeBirthYearGrid(page: import("@playwright/test").Page) { + await page.goto("/life", { waitUntil: "domcontentloaded" }); + await expect(page.getByRole("heading", { name: "Life" })).toBeVisible(); + + const openSidebar = page.getByRole("button", { name: "Open sidebar" }); + if (await openSidebar.isVisible()) { + await openSidebar.click(); + } + + const birth = page.getByRole("textbox", { name: "Date of birth" }); + await birth.focus(); + await birth.press("ArrowDown"); + + const popper = page.locator( + ".react-datepicker-popper:has(.lifeBirthDatePicker)", + ); + await expect(popper).toBeVisible({ timeout: 15000 }); + + const caption = popper.getByRole("button", { + name: "Choose month and year", + }); + await caption.click(); + + await expect(popper.getByText("Years 1981 to 2000")).toBeVisible({ + timeout: 15000, + }); +} + +for (const { name, stored } of [ + { name: "light", stored: "light-beach" }, + { name: "dark", stored: "dark-abyss" }, +] as const) { + test(`life birth date picker year grid fits 360px sidebar (${name})`, async ({ + page, + }) => { + await page.addInitScript((theme) => { + localStorage.setItem("compass.theme", theme); + localStorage.setItem( + "compass.onboarding.has-seen-life-view-intro", + "true", + ); + localStorage.setItem("compass.view.sidebar-open", "true"); + localStorage.setItem( + "compass.life.preferences", + JSON.stringify({ + birthDate: "1993-09-14", + lifespan: 79, + variation: "average", + }), + ); + }, stored); + + await page.setViewportSize({ width: 360, height: 720 }); + await openLifeBirthYearGrid(page); + + const popper = page.locator( + ".react-datepicker-popper:has(.lifeBirthDatePicker)", + ); + + await expect(popper.locator(".react-datepicker__year-text")).toHaveCount( + 20, + ); + const years = await popper + .locator(".react-datepicker__year-text") + .allTextContents(); + expect(years.map((y) => Number.parseInt(y.trim(), 10))).toEqual( + expect.arrayContaining([1981, 2000]), + ); + + const withinViewport = await popper.evaluate((el) => { + const b = el.getBoundingClientRect(); + return b.left >= 0 && b.right <= window.innerWidth; + }); + expect(withinViewport).toBe(true); + await expect(popper.getByText("Years 1981 to 2000")).toBeVisible(); + }); +}