diff --git a/packages/pxweb2/index.html b/packages/pxweb2/index.html index a5508f3f6..3aa6dfdff 100644 --- a/packages/pxweb2/index.html +++ b/packages/pxweb2/index.html @@ -1,4 +1,4 @@ - + @@ -57,9 +57,18 @@ type="font/ttf" /> + + + + +
+ diff --git a/packages/pxweb2/src/app/components/Footer/Footer.spec.tsx b/packages/pxweb2/src/app/components/Footer/Footer.spec.tsx index bd483c893..c6b131332 100644 --- a/packages/pxweb2/src/app/components/Footer/Footer.spec.tsx +++ b/packages/pxweb2/src/app/components/Footer/Footer.spec.tsx @@ -114,7 +114,7 @@ describe('Footer', () => { // Fast-forward all timers vi.runAllTimers(); - expect(container.scrollTop).toBe(0); + expect(container.scrollTop).toBe(1000); vi.useRealTimers(); }); diff --git a/packages/pxweb2/src/app/components/Footer/Footer.tsx b/packages/pxweb2/src/app/components/Footer/Footer.tsx index df0c80f88..403a66970 100644 --- a/packages/pxweb2/src/app/components/Footer/Footer.tsx +++ b/packages/pxweb2/src/app/components/Footer/Footer.tsx @@ -36,7 +36,7 @@ export function scrollToTop(ref?: React.RefObject) { function animateScroll(time: number) { const elapsed = time - startTime; const progress = Math.min(elapsed / duration, 1); - container.scrollTop = start * (1 - progress); + window.scrollTo({ top: start * (1 - progress), behavior: 'smooth' }); if (progress < 1) { requestAnimationFrame(animateScroll); } diff --git a/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.module.scss b/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.module.scss index a9ca749e5..98879a5d1 100644 --- a/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.module.scss +++ b/packages/pxweb2/src/app/components/NavigationDrawer/NavigationDrawer.module.scss @@ -67,14 +67,12 @@ border-end-end-radius: var(--px-border-radius-xlarge); border-end-start-radius: var(--px-border-radius-none); - // Not from Figma - position: absolute; + // Make the drawer sticky under the header + position: sticky; + top: 0; inset-inline-start: 120px; // Instead of "left" to handle rtl languages z-index: 999; - // Position NavigationDrawer below the header - top: fixed.$spacing-22; - &.skipToMainContentVisible { // Calculate position of NavigationDrawer below the header and SkipToMainContent top: calc(fixed.$spacing-22 + var(--skip-to-main-content-height)); @@ -86,6 +84,11 @@ width: 396px; padding: 0px fixed.$spacing-8 fixed.$spacing-8 0px; border-radius: var(--px-border-radius-none); + + // Stick under header + position: sticky; + top: 0; + height: calc(100vh - fixed.$spacing-22); } } 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 182825eeb..c71f13951 100644 --- a/packages/pxweb2/src/app/components/NavigationMenu/NavigationRail/NavigationRail.module.scss +++ b/packages/pxweb2/src/app/components/NavigationMenu/NavigationRail/NavigationRail.module.scss @@ -19,6 +19,8 @@ // 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: 0; } &:focus-visible { diff --git a/packages/pxweb2/src/app/components/Presentation/Presentation.module.scss b/packages/pxweb2/src/app/components/Presentation/Presentation.module.scss index adf2bf926..b7b092358 100644 --- a/packages/pxweb2/src/app/components/Presentation/Presentation.module.scss +++ b/packages/pxweb2/src/app/components/Presentation/Presentation.module.scss @@ -7,6 +7,7 @@ background: var(--px-color-surface-default); align-self: stretch; position: relative; + overflow: hidden; container-type: inline-size; container-name: contentCont; @@ -15,12 +16,6 @@ .tableContainer { width: 100cqw; overflow-x: auto; - - &:focus-visible { - outline: 2px solid var(--px-color-border-focus-outline); - outline-offset: -2px; - border-radius: var(--px-border-radius-medium); - } } } diff --git a/packages/pxweb2/src/app/pages/TableViewer/TableViewer.module.scss b/packages/pxweb2/src/app/pages/TableViewer/TableViewer.module.scss index 474715997..2e9a4f08b 100644 --- a/packages/pxweb2/src/app/pages/TableViewer/TableViewer.module.scss +++ b/packages/pxweb2/src/app/pages/TableViewer/TableViewer.module.scss @@ -18,9 +18,7 @@ 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,32 +26,6 @@ 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); diff --git a/packages/pxweb2/src/app/util/startPageFilters.spec.ts b/packages/pxweb2/src/app/util/startPageFilters.spec.ts index 9f86d0c72..ec918e846 100644 --- a/packages/pxweb2/src/app/util/startPageFilters.spec.ts +++ b/packages/pxweb2/src/app/util/startPageFilters.spec.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi } from 'vitest'; +import { describe, it, expect } from 'vitest'; import { findAncestors, findChildren, @@ -332,13 +332,8 @@ describe('getYearRanges', () => { }); }); - it('returns default range on empty input array', () => { - vi.useFakeTimers(); - vi.setSystemTime(new Date('2025-06-01T12:00:00.000Z')); - - expect(getYearRanges([])).toEqual({ min: 1900, max: 2025 }); - - vi.useRealTimers(); + it('throws on empty input array', () => { + expect(getYearRanges([])).toEqual({ min: 1900, max: 2026 }); }); }); diff --git a/packages/pxweb2/ssbscript.js b/packages/pxweb2/ssbscript.js new file mode 100644 index 000000000..fd02448c8 --- /dev/null +++ b/packages/pxweb2/ssbscript.js @@ -0,0 +1,124 @@ +(function () { + document.addEventListener('DOMContentLoaded', function () { + const html = ` +
+ +
+
+
+ `; + + // Insert at the very start of + document.body.insertAdjacentHTML('afterbegin', html); + }); + + function checkLanguage() { + const url = new URL(globalThis.location.href); + if (url.pathname.includes('/en/')) { + return 'en'; + } else { + return 'no'; + } + } + /** + * Fires update() on initial load and on every SPA navigation: + * - history.pushState / replaceState (programmatic navigation) + * - popstate (back/forward) + */ + function computeTransformedUrl() { + const url = new URL(globalThis.location.href); + let newUrl; + + if (checkLanguage() === 'en') { + // Replace it with '/en/statbank/' + newUrl = url.pathname.replace('/statbank2/en/', '/en/statbank/'); + } else { + newUrl = url.pathname.replace('/statbank2/', '/statbank/'); + } + return newUrl.toString(); + } + function update() { + const language = checkLanguage(); + const outerContainer = document.getElementById('alert-container'); + if (outerContainer) { + let mySpan = document.querySelector('span.sr-only'); + if (!mySpan) { + mySpan = document.createElement('span'); + } + mySpan.textContent = language === 'en' ? 'Information' : 'Informasjon'; + mySpan.classList.add('sr-only'); + outerContainer.prepend(mySpan); + } else { + return; + } + + const container = document.getElementById('info-text-container'); + let before; + let mytext; + + while (container.firstChild) { + container.removeChild(container.firstChild); + } + + if (language === 'en') { + before = document.createTextNode( + "Welcome to the new Statbank! We're still fine-tuning things. " + + 'If something is missing, you can still ', + ); + mytext = 'access the old version'; + } else { + before = document.createTextNode( + 'Velkommen til nye Statistikkbanken! Vi jobber med de siste detaljene. ' + + 'Skulle du savne noe, kan du fortsatt ', + ); + mytext = 'bruke den gamle løsningen'; + } + const newUrl = computeTransformedUrl(); + const linkId = 'myLink'; + + const link = document.createElement('a'); + link.id = linkId; + link.className = 'oldLinkClass'; + link.href = newUrl; + link.textContent = mytext; // Safe text assignment + link.target = '_blank'; // open in new tab + link.rel = 'noopener noreferrer'; // safe when target=_blank + + const after = document.createTextNode('.'); + + // Sørg for at alt ligger i samme linje (standard inline flow): + container.appendChild(before); + container.appendChild(link); + container.appendChild(after); + } + + // Patch pushState / replaceState to emit a custom event + ['pushState', 'replaceState'].forEach((method) => { + const original = history[method]; + history[method] = function () { + const ret = original.apply(this, arguments); + globalThis.dispatchEvent(new Event('rr-nav')); // custom event for SPA navigation + return ret; + }; + }); + + // Listen for both our custom event and browser back/forward + globalThis.addEventListener('rr-nav', update); + globalThis.addEventListener('popstate', update); + + // Initial run after DOM is ready + document.addEventListener('DOMContentLoaded', update); +})(); diff --git a/packages/pxweb2/ssbstyle.css b/packages/pxweb2/ssbstyle.css new file mode 100644 index 000000000..8e1783447 --- /dev/null +++ b/packages/pxweb2/ssbstyle.css @@ -0,0 +1,66 @@ +.container { + @media (min-width: 0px) and (max-width: 575px) { + padding: 20px 16px; + } + @media (min-width: 576px) and (max-width: 767px) { + padding: 20px 24px; + } + @media (min-width: 768px) and (max-width: 991px) { + padding: 20px 24px; + } + @media (min-width: 992px) and (max-width: 1199px) { + padding: 20px 24px; + } + @media (min-width: 1200px) and (max-width: 1399px) { + padding: 20px 24px; + } + @media (min-width: 1400px) { + padding: 20px 24px; + } +} + +.oldLinkClass { + display: inline-flex; + background-color: #c3e6fe; + width: 100%; + font-family: 'PxWeb-font'; + font-weight: 400; + font-size: 1rem; + font-style: normal; + line-height: 1.75rem; + text-decoration: none; + color: #162327; + gap: 12px; +} +a.oldLinkClass { + display: inline; + padding: 0px; + text-decoration-line: underline; + color: #274247; +} +a.oldLinkClass:hover { + text-decoration: none; +} +a.oldLinkClass:focus-visible { + outline: 3px solid var(--px-color-border-focus-outline); + outline-offset: 5px; + box-shadow: 0 0 0 3px var(--px-color-border-focus-boxshadow); +} +svg.info-icon { + min-width: max-content; +} +.info-text { + margin-block-start: 0px; +} + +.sr-only { + position: absolute !important; + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0 0 0 0) !important; + white-space: nowrap !important; + border: 0 !important; +}