diff --git a/src/ui/layout/PageLayout.scss b/src/ui/layout/PageLayout.scss new file mode 100644 index 00000000..c44833cb --- /dev/null +++ b/src/ui/layout/PageLayout.scss @@ -0,0 +1,128 @@ +@use "../../styles/variables" as vars; +@use "../../styles/screen" as screen; + +.page-layout { + &__row { + position: relative; + + display: flex; + flex-flow: row nowrap; + align-items: flex-start; + + margin: 0 auto; + width: 100%; + max-width: vars.$site-width; + + @include screen.print { + width: 100% !important; + padding: 0 !important; + margin: 0 !important; + max-width: 100% !important; + } + + @include screen.smTablet { + flex-flow: column wrap; + } + } + + &__column-left { + position: sticky; + top: 20px; + bottom: 20px; + z-index: 1001; + + flex-shrink: 0; + margin-left: 0; + margin-right: 1.5%; + width: vars.$left-aside-width; + + @media (max-width: vars.$media-breakpoint-desktop) { + width: vars.$left-aside-width-desktop; + } + + @media (max-width: vars.$media-breakpoint-tablet) { + position: relative; + top: 0; + + width: 100%; + margin-right: 0; + } + } + + &__column-right { + position: relative; + width: calc(100% - #{vars.$left-aside-width} - #{vars.$column-spacing}); + + @include screen.print { + width: 100% !important; + margin-top: 20px !important; + } + + @media (max-width: vars.$media-breakpoint-desktop) { + width: calc(100% - #{vars.$left-aside-width-desktop} - 1.5%); + } + + @media (max-width: vars.$media-breakpoint-tablet) { + width: 100%; + } + } + + &__header-right { + margin-top: auto; + display: flex; + justify-content: space-between; + + @include screen.smTablet { + flex-direction: column; + order: 1; + } + } + + &__header-left { + margin-top: auto; + + @include screen.smTablet { + order: 2; + } + } + + &__title { + width: 100%; + } + + &__header { + position: relative; + z-index: 1001; + + padding-top: vars.$space-top-site; + margin-bottom: vars.$space-xl; + + @include screen.mobile { + margin-bottom: vars.$space-m; + } + } + + &__content &__column-left { + @include screen.smTablet { + margin-bottom: vars.$space-xl; + } + + @include screen.mobile { + margin-bottom: vars.$space-m; + } + } + + &__content &__column-left:empty { + @include screen.smTablet { + margin-bottom: 0; + } + } + + &__sticky-content { + @include screen.gt-mobile { + position: sticky; + z-index: 1000; + top: 60px; + } + } +} diff --git a/src/ui/layout/PageLayout.tsx b/src/ui/layout/PageLayout.tsx new file mode 100644 index 00000000..3bb2aded --- /dev/null +++ b/src/ui/layout/PageLayout.tsx @@ -0,0 +1,56 @@ +/** + * Shared page layout wrapper matching the Angular `page-layout` component. + * + * Produces the same DOM structure and CSS class names so global SCSS + * styles apply identically. + */ + +import type { ReactNode, FC } from "react"; +import "./PageLayout.scss"; + +export interface PageLayoutProps { + /** Content rendered in the header left column (e.g. page tabs). */ + headerLeft?: ReactNode; + /** Page title rendered in the header right column. */ + title?: ReactNode; + /** Content in the left column of the main content area (e.g. filter). */ + contentLeft?: ReactNode; + /** Sticky content in the right column above main children. */ + stickyContent?: ReactNode; + /** Main content rendered in the right column. */ + children?: ReactNode; +} + +export const PageLayout: FC = ({ + headerLeft, + title, + contentLeft, + stickyContent, + children, +}) => { + return ( +
+
+ +
+
+ {title} +
+
+
+
+ +
+
+ {stickyContent} +
+ {children} +
+
+
+ ); +}; diff --git a/src/ui/layout/PageTabs.scss b/src/ui/layout/PageTabs.scss new file mode 100644 index 00000000..9e6a2067 --- /dev/null +++ b/src/ui/layout/PageTabs.scss @@ -0,0 +1,45 @@ +@use "../../styles/variables" as vars; +@use "../../styles/fonts" as fonts; +@use "../../styles/colors" as colors; + +.tabs { + .tabs__row { + display: flex; + width: 100%; + + &:not(:first-child) { + margin-top: 5px; + } + } + + .tabs__tab { + display: flex; + flex: 1; + align-items: center; + justify-content: center; + height: vars.$button-height; + border: 1px solid colors.$white; + color: colors.$white; + text-decoration: none; + background-color: transparent; + @include fonts.font-overflow(); + + &:first-child { + border-radius: vars.$border-radius 0 0 vars.$border-radius; + } + + &:last-child { + border-radius: 0 vars.$border-radius vars.$border-radius 0; + } + + &.active { + background-color: colors.$white; + font-weight: fonts.$font-bold; + color: colors.$blue; + } + } + + &__tab--full { + border-radius: vars.$border-radius vars.$border-radius vars.$border-radius vars.$border-radius !important; + } +} diff --git a/src/ui/layout/PageTabs.tsx b/src/ui/layout/PageTabs.tsx new file mode 100644 index 00000000..0738bc50 --- /dev/null +++ b/src/ui/layout/PageTabs.tsx @@ -0,0 +1,60 @@ +/** + * Page navigation tabs matching the Angular `flights-page-tabs` component. + * + * Renders "Online Timetable" / "Schedule" / "Flights Map" tab buttons + * using the same `.tabs` class names as the Angular version. + */ + +import type { FC } from "react"; +import { Link, useParams } from "@modern-js/runtime/router"; +import { useTranslation } from "@/i18n/provider.js"; +import "./PageTabs.scss"; + +export type ViewType = "onlineboard" | "schedule" | "flights-map"; + +export interface PageTabsProps { + viewType: ViewType; + showFlightsMap?: boolean; +} + +export const PageTabs: FC = ({ + viewType, + showFlightsMap = false, +}) => { + const { t } = useTranslation(); + const routeParams = useParams<{ lang: string }>(); + const lang = routeParams.lang ?? "ru"; + + return ( +
+
+ + {t("BOARD.TITLE")} + + + {t("SCHEDULE.TITLE-TAB")} + +
+ + {showFlightsMap && ( +
+ + {t("FLIGHTS-MAP.TITLE")} + +
+ )} +
+ ); +};