Match Angular day-tabs UI + fix flights-map SEO key
Rebuild DayTabs to mirror Angular's flat single-line tab strip: one label
per tab ('17 апр.' siblings, '19 апреля' active), 48px tall, 7 tabs per
page, brand blue label on light-blue background with white active cell.
Single Intl.DateTimeFormat call keeps Russian months in genitive case.
Drop the 60px sticky-content offset to 20px so the date strip aligns
with the left filter column (both already sticky at top:20px).
Correct SEO.FLIGHTS_MAP translation key to SEO.FLIGHTS-MAP.MAIN — the
underscored path never existed in the locale files, so the browser tab
title on /{lang}/flights-map fell back to the raw key.
This commit is contained in:
@@ -14,11 +14,11 @@ const CANONICAL = "https://www.aeroflot.ru";
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("buildFlightsMapSeo", () => {
|
||||
it("uses FLIGHTS_MAP translation keys", () => {
|
||||
it("uses FLIGHTS-MAP.MAIN translation keys", () => {
|
||||
const result = buildFlightsMapSeo(stubT, "ru", CANONICAL);
|
||||
|
||||
expect(result.title).toBe("SEO.FLIGHTS_MAP.TITLE");
|
||||
expect(result.description).toBe("SEO.FLIGHTS_MAP.DESCRIPTION");
|
||||
expect(result.title).toBe("SEO.FLIGHTS-MAP.MAIN.TITLE");
|
||||
expect(result.description).toBe("SEO.FLIGHTS-MAP.MAIN.DESCRIPTION");
|
||||
});
|
||||
|
||||
it("sets canonical to /{locale}/flights-map", () => {
|
||||
|
||||
@@ -37,8 +37,8 @@ export function buildFlightsMapSeo(
|
||||
locale: string,
|
||||
canonicalOrigin: string,
|
||||
): SeoHeadProps {
|
||||
const title = t("SEO.FLIGHTS_MAP.TITLE");
|
||||
const description = t("SEO.FLIGHTS_MAP.DESCRIPTION");
|
||||
const title = t("SEO.FLIGHTS-MAP.MAIN.TITLE");
|
||||
const description = t("SEO.FLIGHTS-MAP.MAIN.DESCRIPTION");
|
||||
const canonical = `${canonicalOrigin}/${locale}${PATH_WITHOUT_LOCALE}`;
|
||||
|
||||
return {
|
||||
|
||||
@@ -9,11 +9,11 @@ describe("DayTabButton", () => {
|
||||
expect(screen.getByTestId("day-tab-20260416")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders weekday, day number, and month", () => {
|
||||
it("renders day number and month in a single label", () => {
|
||||
render(<DayTabButton date="20260416" isActive={false} isDisabled={false} locale="en" onClick={() => {}} />);
|
||||
expect(screen.getByText("16")).toBeTruthy();
|
||||
expect(screen.getByText(/Apr/i)).toBeTruthy();
|
||||
expect(screen.getByText(/Thu/i)).toBeTruthy();
|
||||
const label = screen.getByTestId("day-tab-20260416").textContent ?? "";
|
||||
expect(label).toMatch(/16/);
|
||||
expect(label).toMatch(/Apr/i);
|
||||
});
|
||||
|
||||
it("calls onClick with date when enabled", () => {
|
||||
|
||||
@@ -18,11 +18,11 @@ export const DayTabButton: FC<DayTabButtonProps> = ({
|
||||
onClick,
|
||||
}) => {
|
||||
const d = parseYyyymmdd(date);
|
||||
const weekday = new Intl.DateTimeFormat(locale, { weekday: "short" }).format(d);
|
||||
const day = new Intl.DateTimeFormat(locale, { day: "numeric" }).format(d);
|
||||
// Angular shows the selected tab with the full month name ('18 апреля'),
|
||||
// siblings with the abbreviated form ('19 апр.'). Mirror that.
|
||||
const month = new Intl.DateTimeFormat(locale, {
|
||||
// Single format call keeps Russian month in the correct genitive
|
||||
// case ('19 апреля' rather than '19 апрель'), matching Angular's
|
||||
// ngx-translate output.
|
||||
const label = new Intl.DateTimeFormat(locale, {
|
||||
day: "numeric",
|
||||
month: isActive ? "long" : "short",
|
||||
}).format(d);
|
||||
|
||||
@@ -44,9 +44,7 @@ export const DayTabButton: FC<DayTabButtonProps> = ({
|
||||
if (!isDisabled) onClick(date);
|
||||
}}
|
||||
>
|
||||
<span className="day-tab__weekday">{weekday}</span>
|
||||
<span className="day-tab__day">{day}</span>
|
||||
<span className="day-tab__month">{month}</span>
|
||||
<span className="day-tab__label">{label}</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,67 @@
|
||||
// Matches Angular's date-tabs row. Single flat row, 48px tall, 7 tabs per
|
||||
// page flanked by 50px carousel arrows. Active tab has white bg + dark
|
||||
// text; siblings have light-blue bg + brand link-blue label.
|
||||
|
||||
.day-tabs-wrap {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.day-tabs {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
height: 48px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
&__arrow {
|
||||
width: 50px;
|
||||
flex-shrink: 0;
|
||||
background: #fff;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #657282;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.day-tab {
|
||||
padding: 12px 8px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px 0 15px;
|
||||
background: #f3f9ff;
|
||||
color: #1b62b4;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border: 0;
|
||||
border-right: 1px solid #d1dcea;
|
||||
border-bottom: 1px solid #d1dcea;
|
||||
cursor: pointer;
|
||||
background: #e8f0f7;
|
||||
color: #2060c0;
|
||||
border: none;
|
||||
border-right: 1px solid #d0dae5;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
|
||||
&__label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
@@ -13,33 +69,15 @@
|
||||
|
||||
&--active {
|
||||
background: #fff;
|
||||
color: #1a3a5c;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
border-bottom-color: #fff;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
// Angular's tabs size each line small: weekday 11px, day number 16px,
|
||||
// month 12px. Active tab keeps everything the same size but switches to
|
||||
// #333 and weight 500; siblings stay in the brand link blue.
|
||||
&__weekday {
|
||||
font-size: 11px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__day {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__month {
|
||||
font-size: 12px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.day-select {
|
||||
@@ -54,39 +92,3 @@
|
||||
border: 1px solid #d0dae5;
|
||||
}
|
||||
}
|
||||
|
||||
.day-tabs-wrap {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.day-tabs {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
background: #e8f0f7;
|
||||
border-radius: 8px 8px 0 0;
|
||||
|
||||
&__arrow {
|
||||
width: 48px;
|
||||
flex-shrink: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #2060c0;
|
||||
font-size: 20px;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
@include screen.gt-mobile {
|
||||
position: sticky;
|
||||
z-index: 1000;
|
||||
top: 60px;
|
||||
top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user