diff --git a/packages/pxweb2/src/app/components/ContentTop/ContentTop.module.scss b/packages/pxweb2/src/app/components/ContentTop/ContentTop.module.scss index 359e93f28..ad97dfe1b 100644 --- a/packages/pxweb2/src/app/components/ContentTop/ContentTop.module.scss +++ b/packages/pxweb2/src/app/components/ContentTop/ContentTop.module.scss @@ -30,6 +30,11 @@ align-items: flex-start; gap: fixed.$spacing-4; align-self: stretch; + + // Offset anchor jump only for desktop widths where TableViewer header is sticky. + @media (min-width: fixed.$breakpoints-large-min-width) { + scroll-margin-top: var(--px-skip-to-main-sticky-offset, 0px); + } } .information { display: flex; diff --git a/packages/pxweb2/src/app/components/ContentTop/ContentTop.spec.tsx b/packages/pxweb2/src/app/components/ContentTop/ContentTop.spec.tsx index 2f26088fe..31723c688 100644 --- a/packages/pxweb2/src/app/components/ContentTop/ContentTop.spec.tsx +++ b/packages/pxweb2/src/app/components/ContentTop/ContentTop.spec.tsx @@ -321,9 +321,13 @@ let mockIsXXLargeDesktop = true; vi.mock('../../context/useApp', () => ({ default: () => ({ isXXLargeDesktop: mockIsXXLargeDesktop, + isTablet: false, setTitle: () => { vi.fn(); }, + setTableInformationWantsToHidePageScrollbar: () => { + vi.fn(); + }, }), })); diff --git a/packages/pxweb2/src/app/components/ContentTop/ContentTop.tsx b/packages/pxweb2/src/app/components/ContentTop/ContentTop.tsx index 365675c91..d98e49002 100644 --- a/packages/pxweb2/src/app/components/ContentTop/ContentTop.tsx +++ b/packages/pxweb2/src/app/components/ContentTop/ContentTop.tsx @@ -115,7 +115,12 @@ export function ContentTop({ const { pxTableMetadata, selectedVBValues } = useVariables(); const selectedMetadata = useTableData().data?.metadata; const buildTableTitle = useTableData().buildTableTitle; - const { setTitle, isXXLargeDesktop, isTablet } = useApp(); + const { + setTitle, + isXXLargeDesktop, + isTablet, + setTableInformationWantsToHidePageScrollbar, + } = useApp(); const openInformationButtonRef = useRef(null); const openInformationLinkRef = useRef(null); @@ -128,6 +133,7 @@ export function ContentTop({ setActiveTab(selectedTab); } setIsTableInformationOpen(true); + setTableInformationWantsToHidePageScrollbar(true); }; const noteInfo = @@ -166,6 +172,13 @@ export function ContentTop({ }); }, [isTableInformationOpen, tableInformationOpener, accessibility]); + // Release table info lock on unmount. + useEffect(() => { + return () => { + setTableInformationWantsToHidePageScrollbar(false); + }; + }, [setTableInformationWantsToHidePageScrollbar]); + let tableTitle = ''; if (selectedMetadata) { const titleBy = t('presentation_page.common.table_title_by'); @@ -279,6 +292,7 @@ export function ContentTop({ selectedTab={activeTab} onClose={() => { setIsTableInformationOpen(false); + setTableInformationWantsToHidePageScrollbar(false); }} > )} diff --git a/packages/pxweb2/src/app/components/Errors/NotFound/NotFound.spec.tsx b/packages/pxweb2/src/app/components/Errors/NotFound/NotFound.spec.tsx index 3385a8abd..033730c02 100644 --- a/packages/pxweb2/src/app/components/Errors/NotFound/NotFound.spec.tsx +++ b/packages/pxweb2/src/app/components/Errors/NotFound/NotFound.spec.tsx @@ -41,6 +41,7 @@ vi.mock('@pxweb2/pxweb2-ui', () => ({ ), BreakpointsXsmallMaxWidth: '575px', + BreakpointsSmallMaxWidth: '600px', BreakpointsMediumMaxWidth: '767px', BreakpointsLargeMaxWidth: '991px', BreakpointsXlargeMaxWidth: '1199px', diff --git a/packages/pxweb2/src/app/components/Footer/Footer.tsx b/packages/pxweb2/src/app/components/Footer/Footer.tsx index 059d1b2b0..40bd3cd2c 100644 --- a/packages/pxweb2/src/app/components/Footer/Footer.tsx +++ b/packages/pxweb2/src/app/components/Footer/Footer.tsx @@ -21,12 +21,12 @@ function useSafeLocation(): { pathname: string } { } type FooterProps = { - containerRef?: React.RefObject; + containerRef?: React.RefObject; variant?: 'generic' | 'tableview'; enableWindowScroll?: boolean; }; -export function scrollToTop(ref?: React.RefObject) { +export function scrollToTop(ref?: React.RefObject) { if (ref?.current) { const container = ref.current; const start = container.scrollTop; diff --git a/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.module.scss b/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.module.scss index a9ca749e5..f77abd98b 100644 --- a/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.module.scss +++ b/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.module.scss @@ -18,74 +18,68 @@ @media ((min-width: fixed.$breakpoints-small-min-width) and (max-width: fixed.$breakpoints-medium-max-width)) { width: 428px; - padding-left: fixed.$spacing-8; - padding-right: fixed.$spacing-8; - padding-top: fixed.$spacing-6; - padding-bottom: fixed.$spacing-6; + padding: fixed.$spacing-6 fixed.$spacing-8; } // xsmall, small and medium - @media (min-width: fixed.$breakpoints-xsmall-min-width) and (max-width: fixed.$breakpoints-small-max-width) { + @media (min-width: fixed.$breakpoints-xsmall-min-width) and (max-width: fixed.$breakpoints-medium-max-width) { height: 100vh; // Handle rtl languages border-start-start-radius: var(--px-border-radius-none); border-start-end-radius: var(--px-border-radius-xlarge); border-end-end-radius: var(--px-border-radius-xlarge); border-end-start-radius: var(--px-border-radius-none); - - // Not from Figma - position: absolute; - top: 0; - z-index: 999; - } - - @media (min-width: fixed.$breakpoints-medium-min-width) and (max-width: fixed.$breakpoints-medium-max-width) { - height: calc(100vh - 80px); - // Handle rtl languages - border-start-start-radius: var(--px-border-radius-none); - border-start-end-radius: var(--px-border-radius-xlarge); - border-end-end-radius: var(--px-border-radius-xlarge); - border-end-start-radius: var(--px-border-radius-none); - // Not from Figma - position: absolute; + position: fixed; top: 0; - z-index: 999; + z-index: 70; } // large @media (min-width: fixed.$breakpoints-large-min-width) and (max-width: fixed.$breakpoints-large-max-width) { width: 428px; - padding: fixed.$spacing-8; + padding-inline-start: fixed.$spacing-8; + padding-inline-end: fixed.$spacing-8; + padding-top: fixed.$spacing-5; + padding-bottom: fixed.$spacing-8; // Calculate height of main container, minus the header height - height: calc(100vh - fixed.$spacing-22); + height: calc(100vh - fixed.$spacing-20); // Handle rtl languages - border-start-start-radius: var(--px-border-radius-xlarge); - border-start-end-radius: var(--px-border-radius-xlarge); border-end-end-radius: var(--px-border-radius-xlarge); border-end-start-radius: var(--px-border-radius-none); // Not from Figma - position: absolute; - inset-inline-start: 120px; // Instead of "left" to handle rtl languages - z-index: 999; - - // Position NavigationDrawer below the header - top: fixed.$spacing-22; + position: fixed; + top: fixed.$spacing-20; + inset-inline-start: 100px; // Instead of "left" to handle rtl languages + z-index: 60; &.skipToMainContentVisible { // Calculate position of NavigationDrawer below the header and SkipToMainContent - top: calc(fixed.$spacing-22 + var(--skip-to-main-content-height)); + top: calc(fixed.$spacing-20 + var(--skip-to-main-content-height)); } } // xlarge and xxlarge @media ((min-width: fixed.$breakpoints-xlarge-min-width) and (max-width: fixed.$breakpoints-xlarge-max-width)) or ((min-width: fixed.$breakpoints-xxlarge-min-width)) { - width: 396px; - padding: 0px fixed.$spacing-8 fixed.$spacing-8 0px; - border-radius: var(--px-border-radius-none); + width: 428px; + padding-inline-start: fixed.$spacing-8; + padding-inline-end: fixed.$spacing-8; + padding-top: fixed.$spacing-5; + padding-bottom: fixed.$spacing-8; + + // Calculate height of main container, minus the header height + height: calc(100vh - fixed.$spacing-20); + position: sticky; + top: fixed.$spacing-20; + z-index: 60; + + &.skipToMainContentVisible { + // Calculate position of NavigationDrawer below the header and SkipToMainContent + top: calc(fixed.$spacing-20 + var(--skip-to-main-content-height)); + } } } @@ -151,14 +145,14 @@ // Not from Figma .backdrop { - @media (min-width: fixed.$breakpoints-xsmall-min-width) and (max-width: fixed.$breakpoints-small-max-width) { + @media (min-width: fixed.$breakpoints-xsmall-min-width) and (max-width: fixed.$breakpoints-medium-max-width) { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: var(--px-color-surface-scrim); - z-index: 999; + z-index: 50; } } diff --git a/packages/pxweb2/src/app/components/NavigationMenu/NavigationBar/NavigationBar.module.scss b/packages/pxweb2/src/app/components/NavigationMenu/NavigationBar/NavigationBar.module.scss index c2d0d6d9b..e44c9c1db 100644 --- a/packages/pxweb2/src/app/components/NavigationMenu/NavigationBar/NavigationBar.module.scss +++ b/packages/pxweb2/src/app/components/NavigationMenu/NavigationBar/NavigationBar.module.scss @@ -10,12 +10,12 @@ align-items: center; gap: fixed.$spacing-2; flex-shrink: 0; - background-color: var(--px-color-surface-subtle); + background-color: var(--px-color-surface-default); // fix the navigation bar to the bottom of the screen position: fixed; bottom: 0; - z-index: 2; // 2 to fix gradient issue with table + z-index: 40; border-top: 1px solid var(--px-color-border-subtle); // xsmall, small and medium diff --git a/packages/pxweb2/src/app/components/NavigationMenu/NavigationItem/NavigationItem.tsx b/packages/pxweb2/src/app/components/NavigationMenu/NavigationItem/NavigationItem.tsx index e823bc9c4..2e82fc13d 100644 --- a/packages/pxweb2/src/app/components/NavigationMenu/NavigationItem/NavigationItem.tsx +++ b/packages/pxweb2/src/app/components/NavigationMenu/NavigationItem/NavigationItem.tsx @@ -1,5 +1,5 @@ import cl from 'clsx'; -import { m } from 'motion/react'; +import { m, type Transition, type Variants } from 'motion/react'; import { forwardRef, MouseEvent } from 'react'; import { Icon, IconProps, Label } from '@pxweb2/pxweb2-ui'; @@ -11,7 +11,7 @@ const springConfig = { mass: 1, stiffness: 200, damping: 30, -}; +} satisfies Transition; interface ItemProps { label: string; @@ -24,10 +24,7 @@ interface ItemProps { export const Item = forwardRef( ({ label, parentName, selected, icon, onClick }, ref) => { const btnId = 'px-' + parentName + '-' + label; - const initialBaseBackgroundColor = - parentName === 'navBar' - ? 'var(--px-color-surface-subtle)' - : 'var(--px-color-surface-default)'; + const initialBaseBackgroundColor = 'var(--px-color-surface-default)'; const initialBackgroundColor = selected ? 'var(--px-color-surface-action-subtle-active)' : initialBaseBackgroundColor; @@ -50,7 +47,7 @@ export const Item = forwardRef( ], transition: springConfig, }, - }; + } satisfies Variants; return (
  • diff --git a/packages/pxweb2/src/app/components/NavigationMenu/NavigationRail/NavigationRail.module.scss b/packages/pxweb2/src/app/components/NavigationMenu/NavigationRail/NavigationRail.module.scss index 649b3d175..d43e2482d 100644 --- a/packages/pxweb2/src/app/components/NavigationMenu/NavigationRail/NavigationRail.module.scss +++ b/packages/pxweb2/src/app/components/NavigationMenu/NavigationRail/NavigationRail.module.scss @@ -8,18 +8,24 @@ } .navigationRail { - width: 120px; + width: 100px; height: 100%; - padding: fixed.$spacing-8 0px fixed.$spacing-14 0px; + padding: fixed.$spacing-6 0px fixed.$spacing-14 0px; flex-direction: column; align-items: center; flex-shrink: 0; background: var(--px-color-surface-default); + border-inline-end: 1px solid var(--px-color-border-subtle); gap: 24px; // large, xlarge and xxlarge @media ((min-width: fixed.$breakpoints-large-min-width) and (max-width: fixed.$breakpoints-xlarge-max-width)) or ((min-width: fixed.$breakpoints-xxlarge-min-width)) { display: flex; + position: sticky; + top: fixed.$spacing-20; + height: calc(100vh - fixed.$spacing-20); + z-index: 10; + align-self: flex-start; } &:focus-visible { diff --git a/packages/pxweb2/src/app/components/Presentation/Presentation.tsx b/packages/pxweb2/src/app/components/Presentation/Presentation.tsx index 3f48d6014..7fbd1c05e 100644 --- a/packages/pxweb2/src/app/components/Presentation/Presentation.tsx +++ b/packages/pxweb2/src/app/components/Presentation/Presentation.tsx @@ -14,7 +14,7 @@ import { getConfig } from '../../util/config/getConfig'; type propsType = { readonly selectedTabId: string; - readonly scrollRef?: React.Ref; + readonly scrollRef?: React.RefObject; isExpanded: boolean; setIsExpanded: (expanded: boolean) => void; }; diff --git a/packages/pxweb2/src/app/components/RootLayout.tsx b/packages/pxweb2/src/app/components/RootLayout.tsx index 6143b5042..fd3cfe796 100644 --- a/packages/pxweb2/src/app/components/RootLayout.tsx +++ b/packages/pxweb2/src/app/components/RootLayout.tsx @@ -3,14 +3,12 @@ import { Outlet } from 'react-router'; import useLocalizeDocumentAttributes from '../../i18n/useLocalizeDocumentAttributes'; import { Title, CanonicalUrl, HrefLang } from '../util/seo/headTags'; import ErrorBoundary from './ErrorBoundary/ErrorBoundary'; -import WipStatusMessage from '../components/Banners/WipStatusMessage'; export default function RootLayout() { useLocalizeDocumentAttributes(); return ( <> - <CanonicalUrl /> <HrefLang /> diff --git a/packages/pxweb2/src/app/components/Selection/Selection.tsx b/packages/pxweb2/src/app/components/Selection/Selection.tsx index 77a1b3183..e937c2e70 100644 --- a/packages/pxweb2/src/app/components/Selection/Selection.tsx +++ b/packages/pxweb2/src/app/components/Selection/Selection.tsx @@ -235,7 +235,7 @@ export function Selection({ }: SelectionProps) { const variables = useVariables(); const app = useApp(); - const { isTablet } = useApp(); + const { isTablet, isMobile, setSelectionWantsToHidePageScrollbar } = useApp(); const { selectedVBValues, setSelectedVBValues, @@ -590,6 +590,26 @@ export function Selection({ /> ); + useEffect(() => { + if (selectedNavigationView !== 'none' && (isTablet || isMobile)) { + setSelectionWantsToHidePageScrollbar(true); + } else { + setSelectionWantsToHidePageScrollbar(false); + } + }, [ + selectedNavigationView, + isTablet, + isMobile, + setSelectionWantsToHidePageScrollbar, + ]); + + // Release selection lock on unmount. + useEffect(() => { + return () => { + setSelectionWantsToHidePageScrollbar(false); + }; + }, [setSelectionWantsToHidePageScrollbar]); + return ( selectedNavigationView !== 'none' && ( <NavigationDrawer diff --git a/packages/pxweb2/src/app/components/SkipToContent/SkipToContent.tsx b/packages/pxweb2/src/app/components/SkipToContent/SkipToContent.tsx index f59e7c9a1..72f2a4381 100644 --- a/packages/pxweb2/src/app/components/SkipToContent/SkipToContent.tsx +++ b/packages/pxweb2/src/app/components/SkipToContent/SkipToContent.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { HTMLAttributes, Ref } from 'react'; import cl from 'clsx'; import { useLocation, useSearchParams } from 'react-router'; import { useTranslation } from 'react-i18next'; @@ -8,18 +8,22 @@ import { Link } from '@pxweb2/pxweb2-ui'; import { getConfig } from '../../util/config/getConfig'; import { getLanguagePath } from '../../util/language/getLanguagePath'; -export type SkipToContentProps = React.HTMLAttributes<HTMLDivElement> & { +export type SkipToContentProps = HTMLAttributes<HTMLDivElement> & { // Jump to targetId?: string; // label for the link label?: string; - containerRef?: React.Ref<HTMLDivElement>; + containerRef?: Ref<HTMLDivElement>; }; -export function SkipToContent(props: SkipToContentProps) { - const { targetId, label, containerRef, ...rest } = props; +export function SkipToContent({ + targetId, + label, + containerRef, + ...rest +}: SkipToContentProps) { const { i18n } = useTranslation(); const config = getConfig(); const location = useLocation().pathname; diff --git a/packages/pxweb2/src/app/components/SkipToMain/SkipToMain.spec.tsx b/packages/pxweb2/src/app/components/SkipToMain/SkipToMain.spec.tsx new file mode 100644 index 000000000..14cc65bca --- /dev/null +++ b/packages/pxweb2/src/app/components/SkipToMain/SkipToMain.spec.tsx @@ -0,0 +1,39 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { render } from '@testing-library/react'; + +import { SkipToMain } from './SkipToMain'; + +const STICKY_SKIP_OFFSET_CSS_VAR = '--px-skip-to-main-sticky-offset'; + +vi.mock('react-router', () => ({ + useLocation: () => ({ pathname: '/en/table/123' }), + useSearchParams: () => [new URLSearchParams('foo=bar')], +})); + +describe('SkipToMain', () => { + afterEach(() => { + document.body.style.removeProperty(STICKY_SKIP_OFFSET_CSS_VAR); + }); + + it('sets sticky offset css var when withStickyHeaderOffset is true', () => { + const { unmount } = render(<SkipToMain withStickyHeaderOffset />); + + expect( + document.body.style.getPropertyValue(STICKY_SKIP_OFFSET_CSS_VAR), + ).toBe('80px'); + + unmount(); + + expect( + document.body.style.getPropertyValue(STICKY_SKIP_OFFSET_CSS_VAR), + ).toBe(''); + }); + + it('does not set sticky offset css var when withStickyHeaderOffset is false', () => { + render(<SkipToMain />); + + expect( + document.body.style.getPropertyValue(STICKY_SKIP_OFFSET_CSS_VAR), + ).toBe(''); + }); +}); diff --git a/packages/pxweb2/src/app/components/SkipToMain/SkipToMain.tsx b/packages/pxweb2/src/app/components/SkipToMain/SkipToMain.tsx index bdb66f42d..8e9904788 100644 --- a/packages/pxweb2/src/app/components/SkipToMain/SkipToMain.tsx +++ b/packages/pxweb2/src/app/components/SkipToMain/SkipToMain.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import { HTMLAttributes, Ref, useEffect } from 'react'; import cl from 'clsx'; import { useTranslation } from 'react-i18next'; import { useLocation, useSearchParams } from 'react-router'; @@ -8,15 +8,35 @@ import { Link } from '@pxweb2/pxweb2-ui'; import { getConfig } from '../../util/config/getConfig'; import { getLanguagePath } from '../../util/language/getLanguagePath'; -export const SkipToMain = React.forwardRef< - HTMLDivElement, - React.HTMLAttributes<HTMLDivElement> ->((props, ref) => { +const STICKY_SKIP_OFFSET_CSS_VAR = '--px-skip-to-main-sticky-offset'; + +type SkipToMainProps = HTMLAttributes<HTMLDivElement> & { + withStickyHeaderOffset?: boolean; + ref?: Ref<HTMLDivElement>; +}; + +export function SkipToMain({ + withStickyHeaderOffset = false, + ref, + ...rest +}: SkipToMainProps) { const { t, i18n } = useTranslation(); const config = getConfig(); const location = useLocation().pathname; const [searchParams] = useSearchParams(); + useEffect(() => { + if (!withStickyHeaderOffset) { + return; + } + + document.body.style.setProperty(STICKY_SKIP_OFFSET_CSS_VAR, '80px'); + + return () => { + document.body.style.removeProperty(STICKY_SKIP_OFFSET_CSS_VAR); + }; + }, [withStickyHeaderOffset]); + const basePath = getLanguagePath( location, i18n.language, @@ -36,13 +56,13 @@ export const SkipToMain = React.forwardRef< <div ref={ref} className={cl(classes['skip-to-main'], classes['screen-reader-only'])} - {...props} + {...rest} > <Link href={path} size="medium"> {t('common.skip_to_main')} </Link> </div> ); -}); +} SkipToMain.displayName = 'SkipToMain'; diff --git a/packages/pxweb2/src/app/context/AppProvider.tsx b/packages/pxweb2/src/app/context/AppProvider.tsx index 9b15dacc5..c540ef794 100644 --- a/packages/pxweb2/src/app/context/AppProvider.tsx +++ b/packages/pxweb2/src/app/context/AppProvider.tsx @@ -19,6 +19,11 @@ export type AppContextType = { setSkipToMainFocused: (focused: boolean) => void; title: string; setTitle: (title: string) => void; + hidePageScrollbar: boolean; + selectionWantsToHidePageScrollbar: boolean; + setSelectionWantsToHidePageScrollbar: (hide: boolean) => void; + tableInformationWantsToHidePageScrollbar: boolean; + setTableInformationWantsToHidePageScrollbar: (hide: boolean) => void; }; // Create the context with default values @@ -37,6 +42,15 @@ export const AppContext = createContext<AppContextType>({ setTitle: () => { return; }, + hidePageScrollbar: false, + selectionWantsToHidePageScrollbar: false, + setSelectionWantsToHidePageScrollbar: () => { + return; + }, + tableInformationWantsToHidePageScrollbar: false, + setTableInformationWantsToHidePageScrollbar: () => { + return; + }, }); // Provider component @@ -46,9 +60,20 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ const [isInitialized] = useState(true); const [skipToMainFocused, setSkipToMainFocused] = useState(false); const [title, setTitle] = useState<string>(''); + const [ + selectionWantsToHidePageScrollbar, + setSelectionWantsToHidePageScrollbar, + ] = useState(false); + const [ + tableInformationWantsToHidePageScrollbar, + setTableInformationWantsToHidePageScrollbar, + ] = useState(false); + const hidePageScrollbar = + selectionWantsToHidePageScrollbar || + tableInformationWantsToHidePageScrollbar; /** - * Keep state if window screen size is mobile, pad or desktop. + * Keep state if window screen size is mobile, small tablet, tablet, or desktop. */ const largeBreakpoint = Number(BreakpointsLargeMaxWidth.replace('px', '')); const xLargeBreakpoint = Number(BreakpointsXlargeMaxWidth.replace('px', '')); @@ -67,6 +92,14 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ window.innerWidth <= mobileBreakpoint, ); + useEffect(() => { + document.body.classList.toggle('hide-scrollbar', hidePageScrollbar); + + return () => { + document.body.classList.remove('hide-scrollbar'); + }; + }, [hidePageScrollbar]); + // Use effect to set the isMobile and isTablet state useEffect(() => { const handleResize = () => { @@ -106,6 +139,11 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ setSkipToMainFocused, title, setTitle, + hidePageScrollbar, + selectionWantsToHidePageScrollbar, + setSelectionWantsToHidePageScrollbar, + tableInformationWantsToHidePageScrollbar, + setTableInformationWantsToHidePageScrollbar, }), [ getSavedQueryId, @@ -118,6 +156,11 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ setSkipToMainFocused, title, setTitle, + hidePageScrollbar, + selectionWantsToHidePageScrollbar, + setSelectionWantsToHidePageScrollbar, + tableInformationWantsToHidePageScrollbar, + setTableInformationWantsToHidePageScrollbar, ], ); diff --git a/packages/pxweb2/src/app/pages/StartPage/StartPage.tsx b/packages/pxweb2/src/app/pages/StartPage/StartPage.tsx index 28e56527b..6702e4d92 100644 --- a/packages/pxweb2/src/app/pages/StartPage/StartPage.tsx +++ b/packages/pxweb2/src/app/pages/StartPage/StartPage.tsx @@ -59,6 +59,7 @@ import { BreadcrumbItemsParm, } from '../../util/createBreadcrumbItems'; import { createTableListSEO } from '../../util/seo/tableListSEO'; +import WipStatusMessage from '../../components/Banners/WipStatusMessage'; const StartPage = () => { const { t, i18n } = useTranslation(); @@ -739,6 +740,7 @@ const StartPage = () => { /> </nav> <div className={styles.startPageLayout}> + <WipStatusMessage /> <Header stroke={true} /> <main className={styles.startPage}> <div className={cl(styles.startPageHeader)}> diff --git a/packages/pxweb2/src/app/pages/TableViewer/TableViewer.module.scss b/packages/pxweb2/src/app/pages/TableViewer/TableViewer.module.scss index 474715997..ea51ae9f5 100644 --- a/packages/pxweb2/src/app/pages/TableViewer/TableViewer.module.scss +++ b/packages/pxweb2/src/app/pages/TableViewer/TableViewer.module.scss @@ -1,6 +1,12 @@ @use '$ui/style-dictionary/dist/scss/fixed-variables.scss' as fixed; @use '$ui/src/lib/breakpoints.scss' as breakpoints; +.stickyHeader { + position: sticky; + top: 0; + z-index: 20; +} + // Not from Figma .navigationAndContentContainer { display: flex; @@ -9,7 +15,6 @@ // Tablet and mobile sizes @media (breakpoints.$xsmall) or (breakpoints.$small) or (breakpoints.$medium) { flex-direction: column; - overflow-y: auto; } } @@ -17,10 +22,7 @@ --skip-to-main-content-height: 48px; display: flex; - background: var(--px-color-surface-subtle); - - // Calculate height of main container, minus the header - height: calc(100vh - fixed.$spacing-20); + height: 100%; // xsmall, small and medium general settings @media (breakpoints.$xsmall) or (breakpoints.$small) or (breakpoints.$medium) { @@ -28,36 +30,10 @@ width: 100%; } - // height calculations - @media (breakpoints.$xsmall) or (breakpoints.$small) { - // Calculate height of main container, minus the header and navigation bar heights - height: calc(100vh - fixed.$spacing-19 - 78px); - - &.skipToMainContentVisible { - // Calculate height of main container, minus the header and navigation bar and SkipToMainContent heights - height: calc( - 100vh - fixed.$spacing-19 - - fixed.$spacing-20 - var(--skip-to-main-content-height) - ); - } - } - @media (breakpoints.$medium) { - // Calculate height of main container, minus the header and navigation bar heights - height: calc(100vh - fixed.$spacing-20 - 78px); - - &.skipToMainContentVisible { - // Calculate height of main container, minus the header and navigation bar and SkipToMainContent heights - height: calc( - 100vh - fixed.$spacing-20 - - fixed.$spacing-20 - var(--skip-to-main-content-height) - ); - } - } - // large, xlarge and xxlarge @media (breakpoints.$large) or (breakpoints.$xlarge) or (breakpoints.$xxlarge) { - width: calc(100% - 120px); - justify-content: center; + width: calc(100% - 100px); + justify-content: start; align-items: flex-start; flex-shrink: 0; @@ -66,18 +42,24 @@ border-start-end-radius: var(--px-border-radius-none); border-end-end-radius: var(--px-border-radius-none); border-end-start-radius: var(--px-border-radius-none); - - padding: fixed.$spacing-8 fixed.$spacing-8 0px fixed.$spacing-8; } } +.contentAndFooterContainerWrapper { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + height: 100%; +} + .contentAndFooterContainer { // Not from Figma display: flex; flex-direction: column; align-items: flex-start; width: 100%; - max-width: 1320px; + max-width: 1368px; height: 100%; @media (breakpoints.$xsmall) { @@ -95,7 +77,6 @@ // large, xlarge and xxlarge @media (breakpoints.$large) or (breakpoints.$xlarge) or (breakpoints.$xxlarge) { padding-bottom: fixed.$spacing-8; - overflow-y: auto; gap: fixed.$spacing-8; } @@ -103,3 +84,6 @@ max-width: 100%; } } +.globalAlertWrapper { + width: 100%; +} diff --git a/packages/pxweb2/src/app/pages/TableViewer/TableViewer.tsx b/packages/pxweb2/src/app/pages/TableViewer/TableViewer.tsx index 9be60f944..470e998ab 100644 --- a/packages/pxweb2/src/app/pages/TableViewer/TableViewer.tsx +++ b/packages/pxweb2/src/app/pages/TableViewer/TableViewer.tsx @@ -16,6 +16,7 @@ import useApp from '../../context/useApp'; import { AccessibilityProvider } from '../../context/AccessibilityProvider'; import { VariablesProvider } from '../../context/VariablesProvider'; import { TableDataProvider } from '../../context/TableDataProvider'; +import WipStatusMessage from '../../components/Banners/WipStatusMessage'; export function TableViewer() { const { @@ -33,7 +34,7 @@ export function TableViewer() { useState<NavigationItem>(isXLargeDesktop ? 'selection' : 'none'); const [hasFocus, setHasFocus] = useState<NavigationItem>('none'); const [openedWithKeyboard, setOpenedWithKeyboard] = useState(false); - const outerContainerRef = useRef<HTMLDivElement | null>(null); + const outerContainerRef = useRef<HTMLElement | null>(null); const [isExpanded, setIsExpanded] = useState(false); const navigationBarRef = useRef<{ @@ -53,6 +54,12 @@ export function TableViewer() { } }, [hasFocus]); + useEffect(() => { + // Use the actual document scroll root instead of a component div. + outerContainerRef.current = + (document.scrollingElement as HTMLElement | null) ?? document.body; + }, []); + useEffect(() => { if (!navigationBarRef.current || !hideMenuRef.current) { return; @@ -176,14 +183,14 @@ export function TableViewer() { return ( <> - <SkipToMain ref={skipToMainRef} /> - {!isSmallScreen && <Header />} + <SkipToMain ref={skipToMainRef} withStickyHeaderOffset /> + {!isSmallScreen && ( + <div className={styles.stickyHeader}> + <Header stroke={true} /> + </div> + )} {/* tabindex={-1} to fix firefox focusing this div*/} - <div - ref={isSmallScreen ? outerContainerRef : undefined} - className={styles.navigationAndContentContainer} - tabIndex={-1} - > + <div className={styles.navigationAndContentContainer} tabIndex={-1}> {isSmallScreen ? ( <> <Header stroke={true} /> @@ -212,19 +219,23 @@ export function TableViewer() { openedWithKeyboard={openedWithKeyboard} hideMenuRef={hideMenuRef} /> - <div - ref={isSmallScreen ? undefined : outerContainerRef} - className={cl(styles.contentAndFooterContainer, { - [styles.expanded]: isExpanded, - })} - > - <Presentation - scrollRef={outerContainerRef} - selectedTabId={selectedTableId} - isExpanded={isExpanded} - setIsExpanded={setIsExpanded} - ></Presentation> - <Footer containerRef={outerContainerRef} variant="tableview" /> + <div className={cl(styles.contentAndFooterContainerWrapper)}> + <div className={cl(styles.globalAlertWrapper)}> + <WipStatusMessage /> + </div> + <div + className={cl(styles.contentAndFooterContainer, { + [styles.expanded]: isExpanded, + })} + > + <Presentation + scrollRef={outerContainerRef} + selectedTabId={selectedTableId} + isExpanded={isExpanded} + setIsExpanded={setIsExpanded} + ></Presentation> + <Footer containerRef={outerContainerRef} variant="tableview" /> + </div> </div> </div> </div> diff --git a/packages/pxweb2/src/styles.scss b/packages/pxweb2/src/styles.scss index 20e366ff9..25ff1bfb0 100644 --- a/packages/pxweb2/src/styles.scss +++ b/packages/pxweb2/src/styles.scss @@ -7,6 +7,10 @@ body { } } +body.hide-scrollbar { + overflow-y: hidden; +} + @font-face { font-family: 'PxWeb-font'; src: url('/fonts/PxWeb-font-400.ttf') format('truetype');