Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
macYearRangeCaption,
reactDatepickerYearPageBounds,
shiftYearPageBounds,
yearRangeCaptionFromBounds,
Expand Down Expand Up @@ -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");
});
});
Original file line number Diff line number Diff line change
@@ -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 };
Expand Down Expand Up @@ -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}`;
};
16 changes: 13 additions & 3 deletions apps/calendar-web/src/views/Life/LifeSidebarContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -124,9 +129,14 @@ export function LifeSidebarContent({
/>
</TooltipWrapper>
</div>
{/* Portaled Mod chips do not affect layout; reserve space so chip 3
sits between the arrows and the title without covering the label. */}
<span aria-hidden className="inline-block w-7 shrink-0" />
<span
aria-hidden
className={
pageJumpHintsVisible
? "inline-block w-7 shrink-0"
: "invisible inline-block w-7 shrink-0"
}
/>
<h2 className="min-w-0 font-semibold text-text">
{LIFE_VARIATIONS[preferences.variation].label}
</h2>
Expand Down
10 changes: 10 additions & 0 deletions apps/calendar-web/src/views/Life/LifeView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
(
Expand Down
79 changes: 79 additions & 0 deletions e2e/life/caption-year-grid-narrow.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
}
Loading