From 63aaafdb0d48036b11d5c047df1021d2cf2e351f Mon Sep 17 00:00:00 2001 From: Alexandra Goff Date: Thu, 3 Oct 2024 11:28:58 -0700 Subject: [PATCH 1/5] chore: trigger build From 0eb2a947a31a2a1c42023a424647ea8533017b88 Mon Sep 17 00:00:00 2001 From: Alexandra Goff Date: Thu, 3 Oct 2024 11:28:58 -0700 Subject: [PATCH 2/5] chore: trigger build From f50b1bcf0886dde8beaaac717413e45c8966caa1 Mon Sep 17 00:00:00 2001 From: Alexandra Goff Date: Thu, 3 Oct 2024 11:28:58 -0700 Subject: [PATCH 3/5] chore: trigger build From e0ba1e04440149facf3f1f966f1c63ba6a12b2a7 Mon Sep 17 00:00:00 2001 From: Alexandra Goff Date: Mon, 25 Nov 2024 10:48:46 -0700 Subject: [PATCH 4/5] feat: updated sidebar --- app/[locale]/[...uriSegments]/page.tsx | 1 - components/atomic/Button/Hamburger/index.tsx | 36 +++ .../Button/Hamburger/styles.module.scss | 22 ++ .../atomic/Button/patterns/GoogleSSOButton.js | 1 + .../atomic/Button/patterns/SSOButton.js | 1 + components/auth/AuthorizePage/index.js | 3 - components/global/Header/Hamburger.js | 30 --- components/global/Header/Navigation.js | 169 -------------- components/global/Header/UserNav/index.js | 82 ------- components/global/Header/UserNav/styles.js | 112 --------- components/global/Header/_header.scss | 212 ------------------ components/global/Header/_navigation.scss | 130 ----------- components/global/Header/_search-bar.scss | 72 ------ components/global/Header/index.js | 131 ----------- components/layout/SharePopup/index.js | 1 + components/modal/ActivateModal/index.js | 1 + components/modal/BasicModal/index.js | 1 + components/modal/ConfirmModal/index.js | 1 + components/modal/ForgotPasswordModal/index.js | 1 + components/modal/RegisterModal/index.js | 1 + components/modal/SSOModal/index.js | 1 + components/modal/SetPasswordModal/index.js | 1 + components/modal/SignInModal/index.js | 1 + .../molecules/CollapsibleHeader/index.tsx | 31 +++ .../CollapsibleHeader/styles.module.scss | 18 ++ components/molecules/HeaderLevel/index.tsx | 25 +++ .../molecules/HeaderLevel/styles.module.css | 8 + components/molecules/Logo/index.tsx | 68 ++++++ components/molecules/Logo/styles.module.css | 13 ++ .../SearchBar/index.tsx} | 65 +++--- .../molecules/SearchBar/styles.module.scss | 99 ++++++++ .../Header/LanguageSelect/index.js | 13 +- .../Header/LanguageSelect/styles.js | 8 - components/organisms/Header/Lower/index.tsx | 38 ++++ .../organisms/Header/Lower/styles.module.scss | 31 +++ .../{global => organisms}/Header/NavItem.js | 0 .../Header/NavItemWithChildren.js | 3 +- .../Header/Subnavigation.js | 0 components/organisms/Header/Upper/index.tsx | 29 +++ .../organisms/Header/Upper/styles.module.scss | 17 ++ components/organisms/Header/_navigation.scss | 62 +++++ .../Header/_subnavigation.scss | 14 +- .../Header/_subsubnavigation.scss | 27 +-- components/organisms/Header/index.js | 19 ++ .../Header/navigation/Horizontal/index.tsx | 88 ++++++++ .../navigation/Horizontal/styles.module.scss | 22 ++ .../Header/navigation/Vertical/index.tsx | 97 ++++++++ .../navigation/Vertical/styles.module.scss | 43 ++++ components/organisms/PageWrapper/index.tsx | 28 ++- contexts/GlobalData.js | 1 - contexts/Headroom.tsx | 38 ++++ hooks/useAuthModal.js | 1 + lib/api/canto/index.ts | 19 ++ lib/api/entry/index.ts | 8 - lib/api/fragments/page.js | 13 -- lib/api/globals/index.ts | 108 +++++++-- lib/i18n/index.ts | 6 +- lib/utils.js | 33 +-- package.json | 4 +- theme/styles/abstracts/_variables.scss | 6 +- theme/styles/components/_index.scss | 8 +- types/link.d.ts | 9 + yarn.lock | 43 +++- 63 files changed, 1072 insertions(+), 1102 deletions(-) create mode 100644 components/atomic/Button/Hamburger/index.tsx create mode 100644 components/atomic/Button/Hamburger/styles.module.scss delete mode 100644 components/global/Header/Hamburger.js delete mode 100644 components/global/Header/Navigation.js delete mode 100644 components/global/Header/UserNav/index.js delete mode 100644 components/global/Header/UserNav/styles.js delete mode 100644 components/global/Header/_header.scss delete mode 100644 components/global/Header/_navigation.scss delete mode 100644 components/global/Header/_search-bar.scss delete mode 100644 components/global/Header/index.js create mode 100644 components/molecules/CollapsibleHeader/index.tsx create mode 100644 components/molecules/CollapsibleHeader/styles.module.scss create mode 100644 components/molecules/HeaderLevel/index.tsx create mode 100644 components/molecules/HeaderLevel/styles.module.css create mode 100644 components/molecules/Logo/index.tsx create mode 100644 components/molecules/Logo/styles.module.css rename components/{global/Header/SearchBar.js => molecules/SearchBar/index.tsx} (68%) create mode 100644 components/molecules/SearchBar/styles.module.scss rename components/{global => organisms}/Header/LanguageSelect/index.js (86%) rename components/{global => organisms}/Header/LanguageSelect/styles.js (73%) create mode 100644 components/organisms/Header/Lower/index.tsx create mode 100644 components/organisms/Header/Lower/styles.module.scss rename components/{global => organisms}/Header/NavItem.js (100%) rename components/{global => organisms}/Header/NavItemWithChildren.js (96%) rename components/{global => organisms}/Header/Subnavigation.js (100%) create mode 100644 components/organisms/Header/Upper/index.tsx create mode 100644 components/organisms/Header/Upper/styles.module.scss create mode 100644 components/organisms/Header/_navigation.scss rename components/{global => organisms}/Header/_subnavigation.scss (85%) rename components/{global => organisms}/Header/_subsubnavigation.scss (77%) create mode 100644 components/organisms/Header/index.js create mode 100644 components/organisms/Header/navigation/Horizontal/index.tsx create mode 100644 components/organisms/Header/navigation/Horizontal/styles.module.scss create mode 100644 components/organisms/Header/navigation/Vertical/index.tsx create mode 100644 components/organisms/Header/navigation/Vertical/styles.module.scss create mode 100644 contexts/Headroom.tsx create mode 100644 types/link.d.ts diff --git a/app/[locale]/[...uriSegments]/page.tsx b/app/[locale]/[...uriSegments]/page.tsx index c623013d..c0c8e429 100644 --- a/app/[locale]/[...uriSegments]/page.tsx +++ b/app/[locale]/[...uriSegments]/page.tsx @@ -107,7 +107,6 @@ const sectionMap = { searchResults: SearchPageTemplate, slideshows: SlideshowPageTemplate, staffProfiles: StaffPageTemplate, - // userProfilePage: UserProfilePageTemplate, }; const UriSegmentsPage: FunctionComponent = async ({ diff --git a/components/atomic/Button/Hamburger/index.tsx b/components/atomic/Button/Hamburger/index.tsx new file mode 100644 index 00000000..9790c202 --- /dev/null +++ b/components/atomic/Button/Hamburger/index.tsx @@ -0,0 +1,36 @@ +"use client"; +import { FunctionComponent } from "react"; +import classNames from "classnames"; +import { useTranslation } from "react-i18next"; +import { IconComposer } from "@rubin-epo/epo-react-lib"; +import styles from "./styles.module.scss"; + +interface HamburgerProps { + active: boolean; + onClick: MouseEventHandler; + className?: string; +} + +const Hamburger: FunctionComponent = ({ + active, + onClick, + className, +}) => { + const { t } = useTranslation(); + + return ( + + ); +}; + +Hamburger.displayName = "Atom.Button.Hamburger"; + +export default Hamburger; diff --git a/components/atomic/Button/Hamburger/styles.module.scss b/components/atomic/Button/Hamburger/styles.module.scss new file mode 100644 index 00000000..013302db --- /dev/null +++ b/components/atomic/Button/Hamburger/styles.module.scss @@ -0,0 +1,22 @@ +@use "abstracts/mixins/base"; +@use "abstracts/functions"; + +.hamburger { + @include base.fluid-scale( + width, + functions.header(button-width-tablet), + functions.header(button-width-mobile), + functions.break(tablet), + functions.break(mobile) + ); + + height: 100%; + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.2s; + + &[aria-expanded="true"] { + background-color: var(--color-rubin-teal-600, #0c4a47); + } +} diff --git a/components/atomic/Button/patterns/GoogleSSOButton.js b/components/atomic/Button/patterns/GoogleSSOButton.js index 9a995426..4b30d31e 100644 --- a/components/atomic/Button/patterns/GoogleSSOButton.js +++ b/components/atomic/Button/patterns/GoogleSSOButton.js @@ -1,3 +1,4 @@ +"use client"; import PropTypes from "prop-types"; import { useGoogleLogin } from "@react-oauth/google"; import { usePathname, useRouter } from "next/navigation"; diff --git a/components/atomic/Button/patterns/SSOButton.js b/components/atomic/Button/patterns/SSOButton.js index bc2129e7..9031a950 100644 --- a/components/atomic/Button/patterns/SSOButton.js +++ b/components/atomic/Button/patterns/SSOButton.js @@ -1,3 +1,4 @@ +"use client"; /* eslint-disable react/prop-types */ import { useState, useEffect } from "react"; import PropTypes from "prop-types"; diff --git a/components/auth/AuthorizePage/index.js b/components/auth/AuthorizePage/index.js index 8e89a6d0..589b383a 100644 --- a/components/auth/AuthorizePage/index.js +++ b/components/auth/AuthorizePage/index.js @@ -9,14 +9,12 @@ const AUTHORIZED_TYPES = { pages: false, educatorPages: true, studentPages: false, - userProfilePage: true, investigationLandingPage: false, }; function isAuthorized(typeHandle, user, status) { if (typeHandle === "educatorPages") return user?.group === "educators" && status === "active"; - if (typeHandle === "userProfilePage") return !!user && status === "active"; return false; } @@ -95,7 +93,6 @@ AuthorizePage.propTypes = { "pages", "educatorPages", "studentPages", - "userProfilePage", "investigationLandingPage", ]), children: PropTypes.node, diff --git a/components/global/Header/Hamburger.js b/components/global/Header/Hamburger.js deleted file mode 100644 index cbba97de..00000000 --- a/components/global/Header/Hamburger.js +++ /dev/null @@ -1,30 +0,0 @@ -import PropTypes from "prop-types"; -import classNames from "classnames"; -import { useTranslation } from "react-i18next"; -import { IconComposer } from "@rubin-epo/epo-react-lib"; - -export default function Hamburger({ mobileNavActive, onClick }) { - const { t } = useTranslation(); - - return ( - - ); -} - -Hamburger.displayName = "Global.Header.Hamburger"; - -Hamburger.propTypes = { - mobileNavActive: PropTypes.bool.isRequired, - onClick: PropTypes.func.isRequired, -}; diff --git a/components/global/Header/Navigation.js b/components/global/Header/Navigation.js deleted file mode 100644 index 15ccafe9..00000000 --- a/components/global/Header/Navigation.js +++ /dev/null @@ -1,169 +0,0 @@ -import { useState, useRef } from "react"; -import PropTypes from "prop-types"; -import classNames from "classnames"; -import { useTranslation } from "react-i18next"; -import { useClickEvent, useAuthModal } from "@/hooks"; -import { useAuthenticationContext } from "@/contexts/Authentication"; -import { internalLinkWithChildrenShape } from "@/shapes/link"; -import LanguageSelect from "./LanguageSelect"; -import NavItem from "./NavItem"; -import NavItemWithChildren from "./NavItemWithChildren"; - -export default function Navigation({ - items, - userProfilePage, - theme = "desktop", - desktopSetter, - mobileActive, - mobileSetter, -}) { - const mobileLangSelectId = "mobileLangSelect"; - const [active, setActive] = useState(null); - const navList = useRef(); - const { openModal } = useAuthModal(); - const { t } = useTranslation(); - const { isAuthenticated, status, signOut } = useAuthenticationContext(); - - const showUserItems = isAuthenticated && status === "active"; - - useClickEvent(handleClick); - - function handleClick(e) { - const isLink = - e.target.nodeName === "A" || e.target.parentElement?.nodeName === "A"; - const isMobileLangSelect = e.target.id === mobileLangSelectId; - if (mobileSetter && mobileActive && (isLink || isMobileLangSelect)) { - mobileSetter(false); - setActive(null); - } - if (navList?.current?.contains(e.target)) return; - setActive(null); - if (desktopSetter) desktopSetter(false); - } - - function handleToggleClick(id) { - setActive((prevActive) => (prevActive === id ? null : id)); - if (desktopSetter) desktopSetter(true); - } - - function handleSignOut() { - setActive(null); - if (mobileSetter) mobileSetter(false); - signOut(); - } - - if (!items || items.length < 1) return null; - - return ( -
-
    - {theme === "mobile" && ( -
  • - -
  • - )} - {items.map(({ id, title, uri, children }) => { - const hasChildren = children && children.length > 0; - - return ( -
  • - {hasChildren && ( - setActive(null)} - theme={theme} - /> - )} - {!hasChildren && ( - setActive(null)} - title={title} - theme={theme} - /> - )} -
  • - ); - })} - {theme === "mobile" && ( - <> - {showUserItems && ( - <> -
  • - { - setActive(null); - }} - href={userProfilePage.uri} - title={userProfilePage.title} - theme={theme} - className="a-bg-turquoise50 a-show-mobile" - icon="account" - /> -
  • -
  • - -
  • - - )} - {!showUserItems && ( - <> -
  • - { - setActive(null); - openModal("signIn"); - }} - title={t("auth.log_in")} - theme={theme} - className="a-bg-turquoise50 a-show-mobile" - /> -
  • -
  • - { - setActive(null); - openModal("register"); - }} - title={t("auth.sign_up")} - theme={theme} - className="a-bg-turquoise50 a-show-mobile" - /> -
  • - - )} - - )} -
-
- ); -} - -Navigation.displayName = "Header.Navigation"; - -Navigation.propTypes = { - items: PropTypes.arrayOf(internalLinkWithChildrenShape), - userProfilePage: PropTypes.object, - theme: PropTypes.oneOf(["desktop", "mobile"]), - desktopSetter: PropTypes.func, - mobileActive: PropTypes.bool, - mobileSetter: PropTypes.func, -}; diff --git a/components/global/Header/UserNav/index.js b/components/global/Header/UserNav/index.js deleted file mode 100644 index 33451e10..00000000 --- a/components/global/Header/UserNav/index.js +++ /dev/null @@ -1,82 +0,0 @@ -import PropTypes from "prop-types"; -import { useTranslation } from "react-i18next"; -import { Popover, Portal } from "@headlessui/react"; -import { useAuthModal, useBoundingBox } from "@/hooks"; -import { IconComposer } from "@rubin-epo/epo-react-lib"; -import { useAuthenticationContext } from "@/contexts/Authentication"; -import * as Styled from "./styles"; - -function UserNav({ headerVisible, userProfilePage }) { - const { openModal } = useAuthModal(); - const { t } = useTranslation(); - const { isAuthenticated, status, signOut } = useAuthenticationContext(); - const [box, ref] = useBoundingBox(); - - const showUserMenu = isAuthenticated && status === "active"; - - return ( - - { - showUserMenu ? ( - - {({ open }) => ( - - - - {t("account.header")} - - - - - - - {userProfilePage.title} - - - - - - {t("auth.log_out")} - - - - - - )} - - ) : null - // uncomment to restore sign-in - // <> - // openModal("signIn")}> - // {t("auth.log_in")} - // - // openModal("register")}> - // {t("auth.sign_up")} - // - // - } - - ); -} - -UserNav.displayName = "Global.Header.UserNav"; - -UserNav.propTypes = { - headerVisible: PropTypes.bool, - userProfilePage: PropTypes.object, -}; - -export default UserNav; diff --git a/components/global/Header/UserNav/styles.js b/components/global/Header/UserNav/styles.js deleted file mode 100644 index 9e62088a..00000000 --- a/components/global/Header/UserNav/styles.js +++ /dev/null @@ -1,112 +0,0 @@ -import styled from "styled-components"; -import { fluidScale, respond, tokens } from "@/styles/globalStyles"; - -export const Nav = styled.nav` - display: flex; - gap: 0.85em; - align-items: center; - height: var(--header-height); - font-size: ${fluidScale( - "20px", - "18px", - tokens.BREAK_TABLET, - tokens.BREAK_MOBILE - )}; - font-weight: 700; - - ${respond( - ` - gap: 1.25em; - `, - tokens.BREAK_HEADER_LAYOUT - )} -`; - -export const Toggle = styled.button` - display: flex; - align-items: center; -`; - -export const DropdownWrapper = styled.div` - position: relative; - z-index: 5; - margin-inline-start: 15px; - margin-inline-end: 15px; -`; - -export const UserButton = styled.button` - display: flex; - align-items: center; - border-radius: 100%; - transition: background-color 0.2s ease-in; - - &:hover { - background-color: ${tokens.turquoise85}; - } -`; - -export const SubnavList = styled.ul` - --UserNav-subnav-margin-top: ${fluidScale("16px", "6px")}; - - position: fixed; - top: var(--UserNav-button-top); - right: var(--UserNav-button-right); - z-index: -1; - min-width: 236px; - margin-block-start: calc(var(--UserNav-subnav-margin-top) * -1); - overflow: hidden; - background-color: var(--white); - border: 1px solid #707070; - border-radius: 6px; - opacity: 0; - transition: transform 0.25s ease-in-out, opacity 0.125s ease-in; - transform: translateY(-30px); - - .invisible & { - display: none; - } - - &[open] { - z-index: 1000; - opacity: 1; - transform: translateY(0); - } -`; - -export const SubnavItem = styled.li``; - -export const SubnavLink = styled.a` - display: flex; - align-items: center; - width: 100%; - padding: 13px; - color: ${tokens.neutral90}; - text-align: left; - text-decoration: none; - cursor: pointer; - transition: color 0.2s, background-color 0.2s; - - &:hover { - color: var(--white); - background-color: ${tokens.turquoise85}; - } - - &:not(:hover) svg { - color: ${tokens.turquoise85}; - } - - svg { - margin-inline-end: 13px; - } -`; - -export const RegisterToggle = styled(Toggle)` - padding: 0.5em 0.85em; - background-color: var(--turquoise85); - border-radius: 6px; - transition: background-color 0.2s; - - &:hover { - background-color: var(--turquoise80); - } -`; diff --git a/components/global/Header/_header.scss b/components/global/Header/_header.scss deleted file mode 100644 index 58132a8f..00000000 --- a/components/global/Header/_header.scss +++ /dev/null @@ -1,212 +0,0 @@ -@use "abstracts/functions"; -@use "abstracts/mixins/base"; -@use "abstracts/mixins/layout"; - -$_breakpoint: functions.header(layout-breakpoint); - -.c-global-header { - @include base.fluid-scale( - header-height, - functions.header(height-tablet), - functions.header(height-mobile), - functions.break(tablet), - functions.break(mobile) - ); - - position: sticky; - top: 0; - height: var(--header-height); - display: grid; - grid-template: "logo nav search toggle user-nav" auto / auto minmax(0, 1fr) auto auto auto; - gap: functions.header(gap); - color: functions.palette(white); - background-color: functions.palette(turquoise60); - transform: none; - transition: transform 0.4s; - z-index: functions.z-stack(header); - - &.invisible { - transform: translate3d(0, -100%, 0); - } - - @include base.respond($_breakpoint) { - grid-template: "logo . search user-nav hamburger" auto / auto 1fr auto auto auto; - gap: functions.header(gap); - overflow: hidden; - } - - @include base.respond(functions.header(breakpoint-for-search-bar)) { - grid-template: "logo search user-nav hamburger" auto / auto minmax(0, 1fr) auto auto; - gap: functions.header(gap); - } - - @include base.respond(functions.break(mobile)) { - grid-template: "logo search hamburger" auto / auto minmax(0, 1fr) auto; - gap: functions.header(gap); - } - - &__logo-block { - grid-area: logo; - } - - &__nav-block { - grid-area: nav; - - @include base.respond($_breakpoint) { - display: none; - } - } - - &__search-block { - grid-area: search; - background-color: functions.palette(turquoise60); - - @include base.respond($_breakpoint) { - justify-self: end; - } - } - - &__toggle-block { - grid-area: toggle; - display: grid; - grid-auto-flow: column; - place-content: center; - background-color: functions.palette(turquoise60); - - fieldset { - border: 0; - padding: 0 15px; - } - - @include base.respond($_breakpoint) { - display: none; - } - } - - &__user-nav-block { - grid-area: user-nav; - align-self: center; - padding-right: 20px; - background-color: functions.palette(turquoise60); - - @include base.respond($_breakpoint) { - padding-left: 0.65em; - } - - @include base.respond(functions.break(mobile)) { - display: none; - } - } - - &__hamburger-block { - display: flex; - grid-area: hamburger; - align-items: center; - height: var(--header-height); - - @include base.respond($_breakpoint + 1, min) { - display: none; - } - } - - &__hamburger { - @include base.fluid-scale( - width, - functions.header(button-width-tablet), - functions.header(button-width-mobile), - functions.break(tablet), - functions.break(mobile) - ); - - height: 100%; - display: flex; - align-items: center; - justify-content: center; - transition: background-color 0.2s; - - &--is-active { - background-color: var(--turquoise90); - } - } - - &__icon { - @include base.fluid-scale( - width height, - 41.85px, - 26px, - functions.break(tablet), - functions.break(mobile) - ); - } - - &__logo-link { - @include base.fluid-scale( - padding-left padding-right, - 36px, - 16.5px, - functions.break(tablet), - functions.break(mobile) - ); - - display: flex; - align-items: center; - height: var(--header-height); - margin-top: -3px; - } - - &__logo { - @include base.fluid-scale( - width, - 86.09px, - 48.78px, - functions.break(tablet), - functions.break(mobile) - ); - @include base.fluid-scale( - height, - 57.71px, - 32.7px, - functions.break(tablet), - functions.break(mobile) - ); - - fill: functions.palette(white); - } - - &__logo-full { - @include base.fluid-scale( - width, - 97.76px, - 48.78px, - functions.break(tablet), - functions.break(mobile) - ); - @include base.fluid-scale( - height, - 60px, - 32.7px, - functions.break(tablet), - functions.break(mobile) - ); - - fill: functions.palette(white); - } - - &__skip-link { - position: fixed; - top: 0; - left: 0; - padding: 0.5em 1em; - color: functions.palette(turquoise70); - text-decoration: none; - background-color: functions.palette(white); - transform: translateY(-100%); - - &:focus-visible { - border: 3px solid; - outline: 0; - transition: transform 0.2s; - transform: translateX(0); - } - } -} diff --git a/components/global/Header/_navigation.scss b/components/global/Header/_navigation.scss deleted file mode 100644 index eb6e5a6a..00000000 --- a/components/global/Header/_navigation.scss +++ /dev/null @@ -1,130 +0,0 @@ -@use "abstracts/functions"; -@use "abstracts/mixins/base"; - -$_breakpoint: functions.header(layout-breakpoint); -$_link-gap: 14px; - -.c-nav-list { - @include base.fluid-scale( - font-size, - 20px, - 14px, - $_breakpoint, - functions.break(mobile) - ); - - font-weight: functions.font-weight(bold); - - &--desktop { - @include base.respond($_breakpoint) { - display: none; - } - } - - &--mobile { - position: fixed; - top: var(--header-height); - right: 0; - max-height: calc(100vh - var(--header-height)); - flex-direction: column; - transition: transform 0.25s ease-in-out, visibility 0s 0.25s; - transform: translateX(100%); - visibility: hidden; - - &.c-nav-list--is-active { - transform: translateX(0); - visibility: visible; - transition: transform 0.25s ease-in-out; - } - - @include base.respond($_breakpoint + 1, min) { - display: none; - } - } - - &__list { - display: flex; - background-color: functions.palette(turquoise60); - - .c-nav-list--mobile & { - flex-direction: column; - overflow-y: auto; - height: calc(100vh - var(--header-height)); - } - } - - &__item { - .c-nav-list--desktop & { - position: relative; - } - } - - &__lang { - width: functions.header(nav-width-desktop); - max-width: 53vw; - padding-left: functions.header(nav-link-padding-lateral) * 1.5; - padding-right: functions.header(nav-link-padding-lateral); - background-color: functions.palette(turquoise60); - - @include base.respond($_breakpoint) { - width: functions.header(nav-width-mobile); - } - - fieldset { - height: var(--header-height); - - @include base.respond($_breakpoint) { - height: clamp(60px, 13.453vw, 80px); - } - } - } - - &__link { - padding-right: functions.header(nav-link-padding-lateral); - padding-left: functions.header(nav-link-padding-lateral); - text-decoration: none; - cursor: pointer; - background-color: functions.palette(turquoise60); - transition: background-color 0.2s; - - &:hover, - &:focus-visible, - &--is-active { - background-color: functions.palette(turquoise50); - } - - &--desktop { - display: flex; - flex-direction: row-reverse; - align-items: center; - height: var(--header-height); - white-space: nowrap; - } - - &--mobile { - display: grid; - grid-template: "icon text" auto / 24px auto; - gap: $_link-gap; - width: functions.header(nav-width-mobile); - max-width: 53vw; - padding: functions.header(nav-link-padding-lateral); - } - } - - &__link-icon { - grid-area: icon; - align-self: center; - justify-self: end; - - .c-nav-list__link--desktop & { - margin-left: 8px; - transform: rotate(-90deg) scale(0.9); - } - } - - &__link-text { - grid-area: text; - align-self: center; - text-align: left; - } -} diff --git a/components/global/Header/_search-bar.scss b/components/global/Header/_search-bar.scss deleted file mode 100644 index 9ec93827..00000000 --- a/components/global/Header/_search-bar.scss +++ /dev/null @@ -1,72 +0,0 @@ -@use "abstracts/mixins/appearance"; -@use "abstracts/mixins/base"; -@use "abstracts/functions"; - -.c-search-bar { - @include base.fluid-scale( - toggle-width, - 75px, - 60px, - functions.break(tablet), - functions.break(mobile) - ); - - position: relative; - height: 100%; - overflow: hidden; - font-size: 0.818em; - font-weight: functions.font-weight(bold); - color: functions.palette(white); - - &__toggle { - position: absolute; - width: var(--toggle-width); - top: 0; - left: 0; - height: 100%; - - svg { - transform: translateY(12%); - } - } - - &__form { - display: flex; - align-items: center; - width: var(--toggle-width); - height: 100%; - transition: width 0.35s ease-in-out, background-color 0.35s ease-in-out, visibility 0s 0.35s; - visibility: hidden; - - &--is-open { - width: 468px; - background-color: functions.palette(black); - visibility: visible; - transition: width 0.35s ease-in-out, background-color 0.35s ease-in-out; - - @include base.respond(functions.header(breakpoint-for-search-bar)) { - // overlap logo - width: calc(var(--header-width, 100vw) - var(--width)); - min-width: 55vw; - } - } - } - - &__input { - @include appearance.input-unstyled; - @include base.fluid-scale( - width, - functions.header(button-width-tablet), - functions.header(button-width-mobile), - functions.break(tablet), - functions.break(mobile) - ); - - width: calc(100% - var(--width)); - margin-left: var(--width); - - &.focus-visible { - outline-width: 0; - } - } -} diff --git a/components/global/Header/index.js b/components/global/Header/index.js deleted file mode 100644 index 2377b5ab..00000000 --- a/components/global/Header/index.js +++ /dev/null @@ -1,131 +0,0 @@ -"use client"; -import { Suspense, useState } from "react"; -import PropTypes from "prop-types"; -import Link from "next/link"; -import useResizeObserver from "use-resize-observer"; -import { useTranslation } from "react-i18next"; -import Logo from "@/components/svg/unique/site/Logo"; -import LogoFullSize from "@/components/svg/unique/site/LogoFullSize"; -import internalLinkShape from "@/shapes/link"; -import { useNavHider } from "@/lib/utils"; -import { - SignInModal, - RegisterModal, - SSOModal, - ForgotPasswordModal, - SetPasswordModal, - ActivateModal, -} from "@/components/modal"; -import { tokens } from "@/styles/globalStyles"; -import Navigation from "./Navigation"; -import SearchBar from "./SearchBar"; -import Hamburger from "./Hamburger"; -import LanguageSelect from "./LanguageSelect"; -import UserNavigation from "./UserNav"; -import SRAuthStatus from "../../auth/SRAuthStatus"; - -export default function Header({ navItems, userProfilePage, locale }) { - const [mobileNavActive, setMobileNavActive] = useState(false); - const [desktopNavActive, setDesktopNavActive] = useState(false); - const [mobileLogoActive, setMobileLogoActive] = useState(false); - const [prevScrollPos, setPrevScrollPos] = useState(0); - const [visible, setVisible] = useState(true); - const { t } = useTranslation(); - const homeUrl = locale === "es" ? `/es` : `/`; - - useNavHider(prevScrollPos, setPrevScrollPos, visible, setVisible); - - const breakpoint = parseInt(tokens.BREAK_HEADER_LAYOUT, 10); - const mobileLogoBreakpoint = parseInt(tokens.BREAK_TABLET, 10); - const { ref } = useResizeObserver({ - onResize: ({ width }) => { - if (width >= breakpoint) setMobileNavActive(false); - document.documentElement.style.setProperty( - "--header-width", - `${width}px` - ); - - if (width <= mobileLogoBreakpoint && !mobileLogoActive) { - setMobileLogoActive(true); - } else if (width > mobileLogoBreakpoint && mobileLogoActive) { - setMobileLogoActive(false); - } - }, - }); - - return ( -
- - {t("skip-to-content")} - - -
- - {t("homepage")} - {mobileLogoActive ? ( - - ) : ( - - )} - -
- -
- -
-
- {!mobileNavActive && } -
-
- -
-
- setMobileNavActive(!mobileNavActive)} - /> -
- - - - - - - - - -
- ); -} - -Header.displayName = "Global.Header"; - -Header.propTypes = { - locale: PropTypes.string, - navItems: PropTypes.arrayOf(internalLinkShape), - userProfilePage: PropTypes.object, -}; diff --git a/components/layout/SharePopup/index.js b/components/layout/SharePopup/index.js index adce3c9a..1ef2787c 100644 --- a/components/layout/SharePopup/index.js +++ b/components/layout/SharePopup/index.js @@ -1,3 +1,4 @@ +"use client"; import { useState } from "react"; import PropTypes from "prop-types"; import { Popover } from "@headlessui/react"; diff --git a/components/modal/ActivateModal/index.js b/components/modal/ActivateModal/index.js index 330cc119..5811a2b3 100644 --- a/components/modal/ActivateModal/index.js +++ b/components/modal/ActivateModal/index.js @@ -1,3 +1,4 @@ +"use client"; import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import { useAuthenticationContext } from "@/contexts/Authentication"; diff --git a/components/modal/BasicModal/index.js b/components/modal/BasicModal/index.js index c2f530b2..71aa48c7 100644 --- a/components/modal/BasicModal/index.js +++ b/components/modal/BasicModal/index.js @@ -1,3 +1,4 @@ +"use client"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { Button } from "@rubin-epo/epo-react-lib"; diff --git a/components/modal/ConfirmModal/index.js b/components/modal/ConfirmModal/index.js index 1cb93829..0b6c0266 100644 --- a/components/modal/ConfirmModal/index.js +++ b/components/modal/ConfirmModal/index.js @@ -1,3 +1,4 @@ +"use client"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { Button } from "@rubin-epo/epo-react-lib"; diff --git a/components/modal/ForgotPasswordModal/index.js b/components/modal/ForgotPasswordModal/index.js index 53e094c6..4ddb962b 100644 --- a/components/modal/ForgotPasswordModal/index.js +++ b/components/modal/ForgotPasswordModal/index.js @@ -1,3 +1,4 @@ +"use client"; import { useState } from "react"; import { useTranslation, Trans } from "react-i18next"; import { useForm } from "react-hook-form"; diff --git a/components/modal/RegisterModal/index.js b/components/modal/RegisterModal/index.js index dde911d5..8dc36434 100644 --- a/components/modal/RegisterModal/index.js +++ b/components/modal/RegisterModal/index.js @@ -1,3 +1,4 @@ +"use client"; import { useTranslation, Trans } from "react-i18next"; import { useAuthenticationContext } from "@/contexts/Authentication"; import RegisterForm from "./RegisterForm"; diff --git a/components/modal/SSOModal/index.js b/components/modal/SSOModal/index.js index df1735d4..2a86f814 100644 --- a/components/modal/SSOModal/index.js +++ b/components/modal/SSOModal/index.js @@ -1,3 +1,4 @@ +"use client"; import { useTranslation, Trans } from "react-i18next"; import { useAuthenticationContext } from "@/contexts/Authentication"; import useAuthModal from "@/hooks/useAuthModal"; diff --git a/components/modal/SetPasswordModal/index.js b/components/modal/SetPasswordModal/index.js index acdb3c80..da52c229 100644 --- a/components/modal/SetPasswordModal/index.js +++ b/components/modal/SetPasswordModal/index.js @@ -1,3 +1,4 @@ +"use client"; import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { useForm } from "react-hook-form"; diff --git a/components/modal/SignInModal/index.js b/components/modal/SignInModal/index.js index 393220ee..d4439214 100644 --- a/components/modal/SignInModal/index.js +++ b/components/modal/SignInModal/index.js @@ -1,3 +1,4 @@ +"use client"; import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import Link from "next/link"; diff --git a/components/molecules/CollapsibleHeader/index.tsx b/components/molecules/CollapsibleHeader/index.tsx new file mode 100644 index 00000000..39850f57 --- /dev/null +++ b/components/molecules/CollapsibleHeader/index.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { FunctionComponent, PropsWithChildren, useState } from "react"; +import Headroom from "react-headroom"; +import styles from "./styles.module.scss"; +import { HeadroomProvider } from "@/contexts/Headroom"; +import Center from "@rubin-epo/epo-react-lib/Center"; + +const CollapsibleHeader: FunctionComponent = ({ + children, +}) => { + const [pinned, setPinned] = useState(false); + + return ( + + +
+
{children}
+
+
+
+ ); +}; + +CollapsibleHeader.displayName = "Molecule.CollapsibleHeader"; + +export default CollapsibleHeader; diff --git a/components/molecules/CollapsibleHeader/styles.module.scss b/components/molecules/CollapsibleHeader/styles.module.scss new file mode 100644 index 00000000..58aa5f45 --- /dev/null +++ b/components/molecules/CollapsibleHeader/styles.module.scss @@ -0,0 +1,18 @@ +@use "abstracts/functions"; +@use "abstracts/mixins/base"; +@use "abstracts/mixins/layout"; + +.header { + @include base.fluid-scale( + header-height, + functions.header(height-tablet), + functions.header(height-mobile), + functions.break(tablet), + functions.break(mobile) + ); + + display: grid; + grid-template-columns: 1fr; + grid-template-rows: 1fr 1fr; + z-index: var(--elevation-element-header, 25); +} diff --git a/components/molecules/HeaderLevel/index.tsx b/components/molecules/HeaderLevel/index.tsx new file mode 100644 index 00000000..d220883a --- /dev/null +++ b/components/molecules/HeaderLevel/index.tsx @@ -0,0 +1,25 @@ +import classNames from "classnames"; +import { FunctionComponent, PropsWithChildren } from "react"; +import styles from "./styles.module.css"; +import Center from "@rubin-epo/epo-react-lib/Center"; + +interface HeaderLevelProps { + className?: string; +} + +const HeaderLevel: FunctionComponent> = ({ + children, + className, +}) => { + return ( +
+
+ {children} +
+
+ ); +}; + +HeaderLevel.displayName = "Molecule.HeaderLevel"; + +export default HeaderLevel; diff --git a/components/molecules/HeaderLevel/styles.module.css b/components/molecules/HeaderLevel/styles.module.css new file mode 100644 index 00000000..7ffbe2c9 --- /dev/null +++ b/components/molecules/HeaderLevel/styles.module.css @@ -0,0 +1,8 @@ +.headerLevel { + color: var(--color-text-button-primary, #fff); + display: flex; + align-items: center; + justify-content: space-between; + height: var(--header-height); + overflow: visible; +} diff --git a/components/molecules/Logo/index.tsx b/components/molecules/Logo/index.tsx new file mode 100644 index 00000000..f9ff0e7b --- /dev/null +++ b/components/molecules/Logo/index.tsx @@ -0,0 +1,68 @@ +import { FunctionComponent } from "react"; +import classNames from "classnames"; +import Link from "next/link"; +import { ImgProps } from "next/dist/shared/lib/get-img-props"; +import Picture from "@rubin-epo/epo-react-lib/Picture"; +import { tokens } from "@rubin-epo/epo-react-lib/styles"; +import { fallbackLng } from "@/lib/i18n/settings"; +import { isDefaultLocale, useTranslation } from "@/lib/i18n"; +import styles from "./styles.module.css"; +interface LogoProps { + large: ImgProps; + small?: ImgProps; + className?: string; + locale?: string; +} + +const Logo: FunctionComponent = async ({ + large, + small, + className, + locale = fallbackLng, +}) => { + const { t } = await useTranslation(locale); + + const homeUrl = isDefaultLocale(locale) ? "/" : `/${locale}`; + + const sources = [ + { + mediaCondition: `(min-width: ${tokens.BREAK_HEADER_LAYOUT})`, + width: large.width, + height: large.height, + srcSet: large.srcSet || "", + }, + ]; + + if (small) { + sources.push({ + mediaCondition: `(max-width: ${tokens.BREAK_HEADER_LAYOUT})`, + width: small.width, + height: small.height, + srcSet: small.srcSet || "", + }); + } + + return ( + + + + ); +}; + +Logo.displayName = "Molecule.Logo"; + +export default Logo; diff --git a/components/molecules/Logo/styles.module.css b/components/molecules/Logo/styles.module.css new file mode 100644 index 00000000..078e71f5 --- /dev/null +++ b/components/molecules/Logo/styles.module.css @@ -0,0 +1,13 @@ +.logoWrapper { + display: inline-block; + height: 100%; +} + +.logo { + height: 100%; + width: auto; + + & > img { + max-height: 100%; + } +} diff --git a/components/global/Header/SearchBar.js b/components/molecules/SearchBar/index.tsx similarity index 68% rename from components/global/Header/SearchBar.js rename to components/molecules/SearchBar/index.tsx index 59cb0967..6450c886 100644 --- a/components/global/Header/SearchBar.js +++ b/components/molecules/SearchBar/index.tsx @@ -1,14 +1,15 @@ -import { useCallback, useRef, useState } from "react"; +"use client"; +import { useCallback, useRef, useState, useId } from "react"; import classNames from "classnames"; import { useTranslation } from "react-i18next"; import { useOnClickOutside, useKeyDownEvent } from "@/hooks/listeners"; import { IconComposer } from "@rubin-epo/epo-react-lib"; import useQueryParams from "@/lib/routing/useQueryParams"; import { useRouter } from "next/navigation"; +import styles from "./styles.module.scss"; -const INPUT_ID = "headerSearchBar"; - -export default function SearchBar() { +const SearchBar: FunctionComponent = () => { + const id = useId(); const ref = useRef(); const inputRef = useRef(); const [open, setOpen] = useState(false); @@ -18,24 +19,34 @@ export default function SearchBar() { const { queryParams } = useQueryParams(); const [searchText, setSearchText] = useState(queryParams.get("search") || ""); - useKeyDownEvent(handleKeyDown); - useOnClickOutside(ref, () => { + const handleOpen = () => { + inputRef.current.focus(); + setOpen(true); + }; + + const handleClose = () => { setOpen(false); - handleReset(); - }); + setSearchText(""); + }; function handleKeyDown({ key }) { if (key !== "Escape") return; - setOpen(false); + handleClose(); } + useKeyDownEvent(handleKeyDown); + useOnClickOutside(ref, handleClose); + const handleChange = useCallback((val) => { setSearchText(val); }, []); - const handleOpen = () => { - setOpen(!open); - inputRef.current.focus(); + const toggleOpen = () => { + if (open) { + handleClose(); + } else { + handleOpen(); + } }; const handleSubmit = (event) => { @@ -46,37 +57,35 @@ export default function SearchBar() { push(`/search?${query.toString()}`); }; - const handleReset = () => { - setSearchText(""); - }; + if (open) { + inputRef.current.focus(); + } return ( -
+
-
); -} +}; + +SearchBar.displayName = "Molecule.SearchBar"; -SearchBar.displayName = "Global.Header.SearchBar"; +export default SearchBar; diff --git a/components/molecules/SearchBar/styles.module.scss b/components/molecules/SearchBar/styles.module.scss new file mode 100644 index 00000000..ed4f146a --- /dev/null +++ b/components/molecules/SearchBar/styles.module.scss @@ -0,0 +1,99 @@ +@use "abstracts/mixins/appearance"; +@use "abstracts/mixins/base"; +@use "abstracts/functions"; + +.searchBar { + @include base.fluid-scale( + toggle-width, + 75px, + 60px, + functions.break(tablet), + functions.break(mobile) + ); + + position: relative; + height: 100%; + overflow: hidden; + font-size: 0.818em; + font-weight: functions.font-weight(bold); + color: functions.palette(white); + + &__input { + @include appearance.input-unstyled; + @include base.fluid-scale( + width, + functions.header(button-width-tablet), + functions.header(button-width-mobile), + functions.break(tablet), + functions.break(mobile) + ); + + width: calc(100% - var(--width)); + margin-left: var(--width); + + &.focus-visible { + outline-width: 0; + } + } +} + +.toggle { + position: absolute; + width: var(--toggle-width); + top: 0; + left: 0; + height: 100%; +} + +.toggleIcon { + transform: translateY(12%); + + @include base.fluid-scale( + width height, + 41.85px, + 26px, + functions.break(tablet), + functions.break(mobile) + ); +} + +.form { + display: flex; + align-items: center; + width: var(--toggle-width); + height: 100%; + transition: width 0.35s ease-in-out, background-color 0.35s ease-in-out, + visibility 0s 0.35s; + visibility: hidden; + + &[data-expanded="true"] { + width: 468px; + background-color: functions.palette(black); + visibility: visible; + transition: width 0.35s ease-in-out, background-color 0.35s ease-in-out; + + @include base.respond(functions.header(breakpoint-for-search-bar)) { + // overlap logo + width: calc(var(--header-width, 100vw) - var(--width)); + min-width: 55vw; + } + } +} + +.input { + @include appearance.input-unstyled; + @include base.fluid-scale( + width, + functions.header(button-width-tablet), + functions.header(button-width-mobile), + functions.break(tablet), + functions.break(mobile) + ); + + width: calc(100% - var(--width)); + margin-left: var(--width); + + &:focus-visible { + outline-width: 0; + } +} diff --git a/components/global/Header/LanguageSelect/index.js b/components/organisms/Header/LanguageSelect/index.js similarity index 86% rename from components/global/Header/LanguageSelect/index.js rename to components/organisms/Header/LanguageSelect/index.js index 3e6ea133..26e0cf4d 100644 --- a/components/global/Header/LanguageSelect/index.js +++ b/components/organisms/Header/LanguageSelect/index.js @@ -1,5 +1,6 @@ +"use client"; import PropTypes from "prop-types"; -import { useEffect, useState } from "react"; +import { useEffect, useId, useState } from "react"; import { useRouter, useSearchParams, usePathname } from "next/navigation"; import { useTranslation } from "react-i18next"; import { fallbackLng } from "@/lib/i18n/settings"; @@ -17,7 +18,8 @@ const filterSearchParams = (searchParams) => { return `?${filteredParams.join("&")}`; }; -export default function LanguageSelect({ id }) { +export default function LanguageSelect({ className }) { + const id = useId(); const [isLoading, setLoading] = useState(false); const router = useRouter(); const pathname = usePathname(); @@ -54,12 +56,9 @@ export default function LanguageSelect({ id }) { }; return ( - + {t("localize-content")} - - {t("language-select-label")} - {t("espanol-site-name")} $disabled && `pointer-events: none;`} `; -export const MobileLabelText = styled.span` - display: none; - padding-inline-end: min(5vw, 1.75em); - - ${respond(`display: block;`, BREAK_HEADER_LAYOUT)} -`; - export const Switch = styled(FormSwitch)` --Switch-Toggle-color: var(--turquoise90); --Switch-background-color: var(--turquoise50); diff --git a/components/organisms/Header/Lower/index.tsx b/components/organisms/Header/Lower/index.tsx new file mode 100644 index 00000000..7bfe0a7b --- /dev/null +++ b/components/organisms/Header/Lower/index.tsx @@ -0,0 +1,38 @@ +import { FunctionComponent } from "react"; +import { getNavigationItems } from "@/lib/api/globals"; +import NavigationHorizontal from "../navigation/Horizontal"; +import HeaderLevel from "@/components/molecules/HeaderLevel"; +import SearchBar from "@/components/molecules/SearchBar"; + +import LanguageSelect from "../LanguageSelect"; +import styles from "./styles.module.scss"; +import NavigationVertical from "../navigation/Vertical"; + +interface LowerHeaderProps { + locale: string; +} + +const LowerHeader: FunctionComponent = async ({ locale }) => { + const navigationItems = await getNavigationItems(locale); + + return ( + + +
+ +
+
+ + +
+
+ ); +}; + +LowerHeader.displayName = "Organism.Header.Lower"; + +export default LowerHeader; diff --git a/components/organisms/Header/Lower/styles.module.scss b/components/organisms/Header/Lower/styles.module.scss new file mode 100644 index 00000000..ca2e73c7 --- /dev/null +++ b/components/organisms/Header/Lower/styles.module.scss @@ -0,0 +1,31 @@ +@use "abstracts/functions"; +@use "abstracts/mixins/base"; + +.lower { + background-color: var(--color-rubin-teal-400, #058b8c); +} + +.desktopNavigation { + @include base.respond(functions.header(layout-breakpoint)) { + display: none; + } +} + +.mobileLanguageToggle { + display: none; + padding-left: 1rem; + flex-shrink: 0; + + @include base.respond(functions.header(layout-breakpoint)) { + display: inline-block; + } +} + +.lowerControls { + display: none; + height: 100%; + + @include base.respond(functions.header(layout-breakpoint)) { + display: flex; + } +} diff --git a/components/global/Header/NavItem.js b/components/organisms/Header/NavItem.js similarity index 100% rename from components/global/Header/NavItem.js rename to components/organisms/Header/NavItem.js diff --git a/components/global/Header/NavItemWithChildren.js b/components/organisms/Header/NavItemWithChildren.js similarity index 96% rename from components/global/Header/NavItemWithChildren.js rename to components/organisms/Header/NavItemWithChildren.js index 38b28c67..90d9b1e5 100644 --- a/components/global/Header/NavItemWithChildren.js +++ b/components/organisms/Header/NavItemWithChildren.js @@ -1,8 +1,9 @@ +"use client"; import { useRef } from "react"; import PropTypes from "prop-types"; import classNames from "classnames"; import Subnavigation from "./Subnavigation"; -import { IconComposer } from "@rubin-epo/epo-react-lib"; +import IconComposer from "@rubin-epo/epo-react-lib/IconComposer"; import { useKeyDownEvent, useFocusTrap } from "@/hooks"; import internalLinkShape, { internalLinkInternalShape } from "@/shapes/link"; diff --git a/components/global/Header/Subnavigation.js b/components/organisms/Header/Subnavigation.js similarity index 100% rename from components/global/Header/Subnavigation.js rename to components/organisms/Header/Subnavigation.js diff --git a/components/organisms/Header/Upper/index.tsx b/components/organisms/Header/Upper/index.tsx new file mode 100644 index 00000000..2e9643d4 --- /dev/null +++ b/components/organisms/Header/Upper/index.tsx @@ -0,0 +1,29 @@ +import { FunctionComponent } from "react"; +import HeaderLevel from "@/components/molecules/HeaderLevel"; +import Logo from "@/components/molecules/Logo"; +import LanguageSelect from "../LanguageSelect"; +import SearchBar from "../../../molecules/SearchBar"; +import styles from "./styles.module.scss"; +import { getLogos } from "@/lib/api/globals"; + +interface UpperHeaderProps { + locale: string; +} + +const UpperHeader: FunctionComponent = async ({ locale }) => { + const logos = await getLogos(); + + return ( + + {logos && } +
+ + +
+
+ ); +}; + +UpperHeader.displayName = "Organism.Header.Upper"; + +export default UpperHeader; diff --git a/components/organisms/Header/Upper/styles.module.scss b/components/organisms/Header/Upper/styles.module.scss new file mode 100644 index 00000000..79496d7e --- /dev/null +++ b/components/organisms/Header/Upper/styles.module.scss @@ -0,0 +1,17 @@ +@use "abstracts/functions"; +@use "abstracts/mixins/base"; + +.upper { + background-color: var(--color-rubin-teal-600, #0c4a47); +} + +.upperControls { + display: flex; + gap: 0.5rem; + height: 100%; + padding-right: 1rem; + + @include base.respond(functions.header(layout-breakpoint)) { + display: none; + } +} diff --git a/components/organisms/Header/_navigation.scss b/components/organisms/Header/_navigation.scss new file mode 100644 index 00000000..15cda3b0 --- /dev/null +++ b/components/organisms/Header/_navigation.scss @@ -0,0 +1,62 @@ +@use "abstracts/functions"; +@use "abstracts/mixins/base"; + +$_breakpoint: functions.header(layout-breakpoint); +$_link-gap: 14px; + +.c-nav-list { + &--mobile { + @include base.respond($_breakpoint + 1, min) { + display: none; + } + } + + &__link { + padding-right: functions.header(nav-link-padding-lateral); + padding-left: functions.header(nav-link-padding-lateral); + text-decoration: none; + cursor: pointer; + background-color: functions.palette(turquoise60); + transition: background-color 0.2s; + + &:hover, + &:focus-visible, + &--is-active { + background-color: functions.palette(turquoise50); + } + + &--desktop { + display: flex; + flex-direction: row-reverse; + align-items: center; + height: var(--header-height); + white-space: nowrap; + } + + &--mobile { + display: grid; + grid-template: "icon text" auto / 24px auto; + gap: $_link-gap; + width: functions.header(nav-width-mobile); + max-width: 53vw; + padding: functions.header(nav-link-padding-lateral); + } + } + + &__link-icon { + grid-area: icon; + align-self: center; + justify-self: end; + + .c-nav-list__link--desktop & { + margin-left: 8px; + transform: rotate(-90deg) scale(0.9); + } + } + + &__link-text { + grid-area: text; + align-self: center; + text-align: left; + } +} diff --git a/components/global/Header/_subnavigation.scss b/components/organisms/Header/_subnavigation.scss similarity index 85% rename from components/global/Header/_subnavigation.scss rename to components/organisms/Header/_subnavigation.scss index de63dc5f..2a806167 100644 --- a/components/global/Header/_subnavigation.scss +++ b/components/organisms/Header/_subnavigation.scss @@ -1,15 +1,12 @@ @use "abstracts/functions"; -$_color: functions.sass-palette(neutral80); -$_bg-color: functions.sass-palette(neutral02); - .c-subnav-list { position: absolute; z-index: -5; max-width: 47vw; - max-height: calc(100vh - var(--header-height)); + max-height: calc(100vh - calc(var(--header-height) * 2)); overflow: auto; - color: $_color; + color: var(--color-font-primary); visibility: hidden; transition: transform 0.25s ease-in-out, visibility 0s 0.25s; @@ -54,10 +51,11 @@ $_bg-color: functions.sass-palette(neutral02); .c-nav-list--mobile & { left: functions.header(nav-link-padding-lateral) / 2; - width: calc(100% - #{2 * functions.header(nav-link-padding-lateral) / 2}); + width: calc( + 100% - #{2 * functions.header(nav-link-padding-lateral) / 2} + ); } } - } &__link { @@ -65,7 +63,7 @@ $_bg-color: functions.sass-palette(neutral02); align-items: center; padding: functions.header(nav-link-padding-lateral); text-decoration: none; - background-color: $_bg-color; + background-color: var(--color-background-tile-light); transition: color 0.2s, background-color 0.2s; &:hover, diff --git a/components/global/Header/_subsubnavigation.scss b/components/organisms/Header/_subsubnavigation.scss similarity index 77% rename from components/global/Header/_subsubnavigation.scss rename to components/organisms/Header/_subsubnavigation.scss index 09470a14..e1f9d7ba 100644 --- a/components/global/Header/_subsubnavigation.scss +++ b/components/organisms/Header/_subsubnavigation.scss @@ -1,20 +1,17 @@ @use "abstracts/functions"; -$_color: functions.sass-palette(neutral80); -$_bg-color: #E8E8E8; -$_button-bg-color: functions.sass-palette(neutral02); - .c-sub-subnav-list { height: 100%; max-height: 0; margin-left: functions.header(nav-link-padding-lateral) / 2; overflow: hidden; - color: $_color; + color: var(--color-font-primary); visibility: hidden; transition: max-height 0.25s ease-out, visibility 0s 0.25s; @keyframes hide-scroll { - from, to { + from, + to { overflow: hidden; } } @@ -42,7 +39,9 @@ $_button-bg-color: functions.sass-palette(neutral02); .c-nav-list--mobile & { left: functions.header(nav-link-padding-lateral) / 2; - width: calc(100% - #{2 * functions.header(nav-link-padding-lateral) / 2}); + width: calc( + 100% - #{2 * functions.header(nav-link-padding-lateral) / 2} + ); } } } @@ -52,13 +51,13 @@ $_button-bg-color: functions.sass-palette(neutral02); align-items: center; width: 100%; text-decoration: none; - background-color: $_bg-color; + background-color: var(--color-rubin-gray-100); transition: color 0.2s, background-color 0.2s; &:hover, &:focus-visible, &--is-active { - color: functions.palette(white); + color: var(--color-font-invert); background-color: functions.palette(turquoise50); } @@ -74,7 +73,8 @@ $_button-bg-color: functions.sass-palette(neutral02); .c-nav-list--mobile & { height: functions.header(height); - padding: functions.header(nav-link-padding-lateral) / 3 functions.header(nav-link-padding-lateral) / 2; + padding: functions.header(nav-link-padding-lateral) / 3 + functions.header(nav-link-padding-lateral) / 2; } .c-nav-list--desktop & { @@ -84,17 +84,18 @@ $_button-bg-color: functions.sass-palette(neutral02); .c-sub-subnav-list__item-inner > & { color: #333; - background-color: $_button-bg-color; + background-color: var(--color-background-tile-light); &:hover, &:focus-visible, &--is-active { - color: functions.palette(white); + color: var(--color-font-invert); background-color: functions.palette(turquoise50); } .c-nav-list--mobile & { - padding: functions.header(nav-link-padding-lateral) / 2 functions.header(nav-link-padding-lateral); + padding: functions.header(nav-link-padding-lateral) / 2 + functions.header(nav-link-padding-lateral); } } diff --git a/components/organisms/Header/index.js b/components/organisms/Header/index.js new file mode 100644 index 00000000..fda6a95a --- /dev/null +++ b/components/organisms/Header/index.js @@ -0,0 +1,19 @@ +import PropTypes from "prop-types"; +import CollapsibleHeader from "@/components/molecules/CollapsibleHeader"; +import UpperHeader from "./Upper"; +import LowerHeader from "./Lower"; + +export default function Header({ locale }) { + return ( + + + + + ); +} + +Header.displayName = "Organism.Header"; + +Header.propTypes = { + locale: PropTypes.string, +}; diff --git a/components/organisms/Header/navigation/Horizontal/index.tsx b/components/organisms/Header/navigation/Horizontal/index.tsx new file mode 100644 index 00000000..06711488 --- /dev/null +++ b/components/organisms/Header/navigation/Horizontal/index.tsx @@ -0,0 +1,88 @@ +"use client"; +import { FunctionComponent, useRef, useState } from "react"; +import classNames from "classnames"; +import useHeadroom from "@/contexts/Headroom"; +import NavItemWithChildren from "../../NavItemWithChildren"; +import NavItem from "../../NavItem"; +import { fallbackLng } from "@/lib/i18n/settings"; +import { isDefaultLocale } from "@/lib/i18n"; +import styles from "./styles.module.scss"; +import { useOnClickOutside } from "@/hooks/listeners"; + +interface NavigationProps { + items: Array; + locale?: string; + className?: string; +} + +const NavigationHorizontal: FunctionComponent = ({ + items, + locale = fallbackLng, + className, +}) => { + const ref = useRef(null); + const [active, setActive] = useState(null); + const { setPinned } = useHeadroom(); + + function handleToggleClick(id: string) { + if (id === active) { + close(); + } else { + open(id); + } + } + + const open = (id: string) => { + setActive(id); + setPinned(true); + }; + + const close = () => { + setActive(null); + setPinned(false); + }; + + useOnClickOutside(ref, close); + + return ( + + ); +}; + +NavigationHorizontal.displayName = "Organisms.Header.Navigation.Horizontal"; + +export default NavigationHorizontal; diff --git a/components/organisms/Header/navigation/Horizontal/styles.module.scss b/components/organisms/Header/navigation/Horizontal/styles.module.scss new file mode 100644 index 00000000..89f16ef4 --- /dev/null +++ b/components/organisms/Header/navigation/Horizontal/styles.module.scss @@ -0,0 +1,22 @@ +@use "abstracts/functions"; +@use "abstracts/mixins/base"; + +.horizontalNavigation { + @include base.fluid-scale( + font-size, + 20px, + 14px, + functions.header(layout-breakpoint), + functions.break(mobile) + ); + + font-weight: functions.font-weight(bold); +} + +.navigationList { + display: flex; +} + +.navigationItem { + position: relative; +} diff --git a/components/organisms/Header/navigation/Vertical/index.tsx b/components/organisms/Header/navigation/Vertical/index.tsx new file mode 100644 index 00000000..f0a27f9b --- /dev/null +++ b/components/organisms/Header/navigation/Vertical/index.tsx @@ -0,0 +1,97 @@ +"use client"; +import { FunctionComponent, useState } from "react"; +import classNames from "classnames"; +import Slideout from "@rubin-epo/epo-react-lib/Slideout"; +import { isDefaultLocale } from "@/lib/i18n"; +import { fallbackLng } from "@/lib/i18n/settings"; +import Hamburger from "@/components/atomic/Button/Hamburger"; +import useHeadroom from "@/contexts/Headroom"; +import NavItemWithChildren from "../../NavItemWithChildren"; +import NavItem from "../../NavItem"; +import styles from "./styles.module.scss"; + +interface NavigationProps { + items: Array; + locale?: string; + className?: string; +} + +const NavigationVertical: FunctionComponent = ({ + items, + locale = fallbackLng, + className, +}) => { + const [open, setOpen] = useState(false); + const [active, setActive] = useState(null); + const { setPinned } = useHeadroom(); + + const handleOpenToggle = () => { + if (open) { + setOpen(false); + setPinned(false); + } else { + setOpen(true); + setPinned(true); + } + }; + + function handleToggleClick(id: string) { + setActive((prevActive) => (prevActive === id ? null : id)); + } + + return ( + <> + + setActive(null)} + > + + + + ); +}; + +NavigationVertical.displayName = "Organisms.Header.Navigation.Vertical"; + +export default NavigationVertical; diff --git a/components/organisms/Header/navigation/Vertical/styles.module.scss b/components/organisms/Header/navigation/Vertical/styles.module.scss new file mode 100644 index 00000000..6a86c652 --- /dev/null +++ b/components/organisms/Header/navigation/Vertical/styles.module.scss @@ -0,0 +1,43 @@ +/* stylelint-disable declaration-no-important */ +@use "abstracts/functions"; +@use "abstracts/mixins/base"; + +.verticalNavigation { + @include base.fluid-scale( + font-size, + 20px, + 14px, + functions.header(layout-breakpoint), + functions.break(mobile) + ); + + background-color: var(--color-rubin-teal-400, #058b8c); + font-weight: functions.font-weight(bold); + height: 100%; +} + +.navigationList { + display: flex; + flex-direction: column; + overflow-y: auto; +} + +.slideOut { + --offset-slideout: calc(var(--header-height) * 2); + + height: calc(100vh - var(--offset-slideout)) !important; + top: var(--offset-slideout) !important; + right: 0; +} + +.icon { + svg { + @include base.fluid-scale( + width height, + 41.85px, + 26px, + functions.break(tablet), + functions.break(mobile) + ); + } +} diff --git a/components/organisms/PageWrapper/index.tsx b/components/organisms/PageWrapper/index.tsx index 353da138..17f9a9f1 100644 --- a/components/organisms/PageWrapper/index.tsx +++ b/components/organisms/PageWrapper/index.tsx @@ -1,7 +1,15 @@ -import { FunctionComponent, PropsWithChildren } from "react"; +import { FunctionComponent, PropsWithChildren, Suspense } from "react"; +import { + SignInModal, + RegisterModal, + SSOModal, + ForgotPasswordModal, + SetPasswordModal, + ActivateModal, +} from "@/components/modal"; import { getGlobalData } from "@/lib/api/globals"; import { GlobalDataProvider } from "@/contexts/GlobalData"; -import Header from "@/components/global/Header"; +import Header from "@/components/organisms/Header"; import Footer from "@/components/global/Footer"; import Center from "@rubin-epo/epo-react-lib/Center"; @@ -10,8 +18,6 @@ const PageWrapper: FunctionComponent< > = async ({ locale, children }) => { const globalData = await getGlobalData(locale); const { - headerNavItems, - userProfilePage, footerContent, contactForm, siteInfo: { email, facebook, instagram, linkedIn, twitter, youTube }, @@ -20,11 +26,7 @@ const PageWrapper: FunctionComponent< return (
-
+
{children}
+ + + + + + + +
); diff --git a/contexts/GlobalData.js b/contexts/GlobalData.js index f43109f7..88919eb5 100644 --- a/contexts/GlobalData.js +++ b/contexts/GlobalData.js @@ -28,7 +28,6 @@ GlobalDataProvider.propTypes = { metadata: PropTypes.arrayOf(PropTypes.object), rootPages: rootPagesShape, siteInfo: siteInfoShape, - userProfilePage: PropTypes.object, investigation: PropTypes.object, contactForm: PropTypes.object, }), diff --git a/contexts/Headroom.tsx b/contexts/Headroom.tsx new file mode 100644 index 00000000..6948cae6 --- /dev/null +++ b/contexts/Headroom.tsx @@ -0,0 +1,38 @@ +"use client"; +import { + createContext, + FunctionComponent, + PropsWithChildren, + useContext, + Dispatch, + SetStateAction, +} from "react"; + +interface HeadroomState { + pinned: boolean; + setPinned: Dispatch>; +} + +const HeadroomContext = createContext(undefined); + +export const HeadroomProvider: FunctionComponent< + PropsWithChildren +> = ({ children, ...props }) => { + return ( + + {children} + + ); +}; + +HeadroomProvider.displayName = "Provider.Headroom"; + +const useHeadroom = () => { + const context = useContext(HeadroomContext); + if (context === undefined) { + throw new Error("useHeadroom must be used within a HeadroomProvider"); + } + return context; +}; + +export default useHeadroom; diff --git a/hooks/useAuthModal.js b/hooks/useAuthModal.js index 2bd554ba..081be56d 100644 --- a/hooks/useAuthModal.js +++ b/hooks/useAuthModal.js @@ -1,3 +1,4 @@ +"use client"; import { useCallback } from "react"; import { usePathname, useRouter } from "next/navigation"; diff --git a/lib/api/canto/index.ts b/lib/api/canto/index.ts index 8ca9d868..1171ffad 100644 --- a/lib/api/canto/index.ts +++ b/lib/api/canto/index.ts @@ -1,5 +1,6 @@ import { fallbackLng } from "@/lib/i18n/settings"; import { resizeCantoImage, ValidCantoSizes } from "./resize"; +import { ImageProps } from "next/image"; const responsiveCantoSrc = ( previewUrl: string, @@ -65,3 +66,21 @@ export const damAssetToImage = (locale: string, data: CantoImage) => { ...assetMetadata, }; }; + +export const cantoToImageProps = ( + data: CantoImage, + options: { locale?: string; usePreviewUrl?: boolean } = {} +): ImageProps => { + const { locale = fallbackLng, usePreviewUrl = false } = options; + const { metadata, url, width, height } = data; + const { directUrlOriginal, directUrlPreview } = url; + + const { altText: alt = "" } = getAssetMetadata(metadata, locale) || {}; + + return { + src: usePreviewUrl ? directUrlPreview : directUrlOriginal, + alt, + width: parseFloat(width), + height: parseFloat(height), + }; +}; diff --git a/lib/api/entry/index.ts b/lib/api/entry/index.ts index c251a671..d5858e55 100644 --- a/lib/api/entry/index.ts +++ b/lib/api/entry/index.ts @@ -11,7 +11,6 @@ import { newsPostFragmentFull } from "@/lib/api/fragments/news-post"; import { pageFragmentFull, searchFragmentFull, - userProfileFragmentFull, redirectFragment, } from "@/lib/api/fragments/page"; import { staffProfileFragmentFull } from "@/lib/api/fragments/staff-profile"; @@ -127,11 +126,6 @@ function getQueryFragments( ${glossaryTermFragmentFull} ${entryQueryify(`...glossaryTermFragmentFull`)} `; - case "userProfilePage": - return gql` - ${userProfileFragmentFull} - ${entryQueryify(`...userProfileFragmentFull`)} - `; case "searchResults": return gql` ${searchFragmentFull} @@ -150,7 +144,6 @@ function getQueryFragments( ${staffProfileFragmentFull} ${glossaryTermFragmentFull} ${educatorPageFragmentFull} - ${userProfileFragmentFull} ${investigationLandingPageFragmentFull} ${redirectFragment} ${entryQueryify(` @@ -162,7 +155,6 @@ function getQueryFragments( ...searchFragmentFull ...staffProfileFragmentFull ...glossaryTermFragmentFull - ...userProfileFragmentFull ...investigationLandingPageFragmentFull ...redirectFragment `)} diff --git a/lib/api/fragments/page.js b/lib/api/fragments/page.js index 17852296..c0e79eee 100644 --- a/lib/api/fragments/page.js +++ b/lib/api/fragments/page.js @@ -133,19 +133,6 @@ fragment searchFragmentFull on searchResults_searchResults_Entry { } `; -export const userProfileFragmentFull = ` -fragment userProfileFragmentFull on userProfilePage_userProfilePage_Entry { - id -} -`; - -export const userProfileFragment = ` -fragment userProfileFragment on EntryInterface { - title - uri -} -`; - export const redirectFragment = ` fragment redirectFragment on pages_redirectPage_Entry { linkTo { diff --git a/lib/api/globals/index.ts b/lib/api/globals/index.ts index 97b69d5d..2c6f4776 100644 --- a/lib/api/globals/index.ts +++ b/lib/api/globals/index.ts @@ -9,22 +9,81 @@ import { contactFormFragment, } from "@/lib/api/fragments/global"; import { categoriesFragment } from "@/lib/api/fragments/categories"; -import { userProfileFragment } from "@/lib/api/fragments/page"; import { getSiteFromLocale } from "@/lib/helpers/site"; import tags from "../client/tags"; +import { getImageProps } from "next/image"; +import { cantoToImageProps } from "../canto"; -export async function getGlobalData(locale = fallbackLng) { +export async function getLogos() { + const query = gql` + query getLogos($set: [String]) { + siteInfo: globalSet(handle: $set) { + ... on siteInfo_GlobalSet { + logoLarge { + url { + directUrlPreview + } + width + height + } + logoSmall { + url { + directUrlPreview + } + width + height + } + } + } + } + `; + + const { data } = await queryAPI({ + query, + variables: { set: "siteInfo" }, + fetchOptions: { next: { tags: [tags.globals] } }, + }); + + if (!data || !data.siteInfo) { + return undefined; + } + + const { + siteInfo: { logoLarge, logoSmall }, + } = data; + + const { props: large } = getImageProps({ + ...cantoToImageProps(logoLarge[0], { usePreviewUrl: true }), + priority: true, + quality: 90, + }); + + if (logoSmall[0]) { + const { props: small } = getImageProps({ + ...cantoToImageProps(logoSmall[0], { usePreviewUrl: true }), + priority: true, + quality: 90, + }); + + return { + large, + small, + }; + } + + return { + large, + }; +} + +export async function getNavigationItems( + locale = fallbackLng +): Promise> { const site = getSiteFromLocale(locale); + const query = gql` - ${linkFragment} - ${siteInfoFragment} - ${footerFragment} - ${rootPageInfoFragment} - ${contactFormFragment} - ${categoriesFragment} - ${userProfileFragment} - query getGlobalData($site: [String]) { - pageTree: entries( + query getNavigationItems($site: [String]) { + navigationItems: entries( section: "pages" site: $site level: 1 @@ -44,6 +103,28 @@ export async function getGlobalData(locale = fallbackLng) { } } } + } + `; + + const { data } = await queryAPI({ + query, + variables: { site }, + fetchOptions: { next: { tags: [tags.globals] } }, + }); + + return data?.navigationItems || []; +} + +export async function getGlobalData(locale = fallbackLng) { + const site = getSiteFromLocale(locale); + const query = gql` + ${linkFragment} + ${siteInfoFragment} + ${footerFragment} + ${rootPageInfoFragment} + ${contactFormFragment} + ${categoriesFragment} + query getGlobalData($site: [String]) { globals: globalSets(site: $site) { ...rootPageInfoFragment ...siteInfoFragment @@ -53,9 +134,6 @@ export async function getGlobalData(locale = fallbackLng) { allCategories: categories(site: $site) { ...categoriesFragment } - userProfilePage: entry(site: $site, type: "userProfilePage") { - ...userProfileFragment - } } `; const { data } = await queryAPI({ @@ -79,10 +157,8 @@ export async function getGlobalData(locale = fallbackLng) { categories: data?.allCategories || [], footerContent: globals?.footer || {}, contactForm: globals?.contactForm || {}, - headerNavItems: data?.pageTree || [], rootPages: globals?.rootPageInformation?.customBreadcrumbs || [], siteInfo: globals?.siteInfo || {}, - userProfilePage: data?.userProfilePage || {}, localeInfo: { language: locale, locale, diff --git a/lib/i18n/index.ts b/lib/i18n/index.ts index 3a12be56..8cddc8ec 100644 --- a/lib/i18n/index.ts +++ b/lib/i18n/index.ts @@ -1,7 +1,7 @@ import { createInstance } from "i18next"; import resourcesToBackend from "i18next-resources-to-backend"; import { initReactI18next } from "react-i18next/initReactI18next"; -import { getOptions } from "./settings"; +import { fallbackLng, getOptions } from "./settings"; export const loadResources = resourcesToBackend( (language: string, namespace: string, callback) => { @@ -42,7 +42,7 @@ const initI18next = async (lng: string, ns: string | string[]) => { async function useTranslation( lng: string, - ns: string | string[], + ns: string | string[] = "translation", options: any = {} ) { const i18nextInstance = await initI18next(lng, ns); @@ -57,4 +57,6 @@ async function useTranslation( }; } +export const isDefaultLocale = (locale: string) => locale === fallbackLng; + export { useTranslation, useTranslation as serverTranslation }; diff --git a/lib/utils.js b/lib/utils.js index 32f4b8aa..853baf26 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,8 +1,7 @@ -import { useContext, useEffect } from "react"; +import { useContext } from "react"; import GlobalDataContext from "@/contexts/GlobalData"; import { useDataList } from "@/api/entries"; import { useReleases } from "@/lib/api/noirlabReleases"; -import debounce from "lodash/debounce"; import { useFilterParams } from "@/contexts/FilterParams"; import { fallbackLng } from "./i18n/settings"; import { usePathname } from "next/navigation"; @@ -207,36 +206,6 @@ export const useTimeZone = (timezone) => { return localizedTimezone; }; -export const useNavHider = ( - prevScrollPos, - setPrevScrollPos, - visible, - setVisible -) => { - const handleScroll = debounce(() => { - const currentScroll = window.pageYOffset; - if (currentScroll <= 0) { - setVisible(true); - return; - } - - if (currentScroll > prevScrollPos && visible === true) { - // down - setVisible(false); - } else if (currentScroll < prevScrollPos && visible === false) { - // up - setVisible(true); - } - setPrevScrollPos(currentScroll); - }, 30); - - useEffect(() => { - window.addEventListener("scroll", handleScroll); - - return () => window.removeEventListener("scroll", handleScroll); - }, [prevScrollPos, visible, handleScroll]); -}; - // CONTENT MUNGERS export const makeBreadcrumbs = (uri) => { return uri.split("/").map((crumb, i) => { diff --git a/package.json b/package.json index c7913792..8b0af11c 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "@influxdata/influxdb-client": "^1.33.2", "@popperjs/core": "^2.11.5", "@react-oauth/google": "^0.12.1", - "@rubin-epo/epo-react-lib": "^2.5.1", + "@rubin-epo/epo-react-lib": "^2.5.3", "@urql/core": "^5.0.6", "@urql/next": "^1.1.2", "add": "^2.0.6", @@ -88,6 +88,7 @@ "npm-run-all": "^4.1.5", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-headroom": "^3.2.1", "react-hook-form": "^7.33.1", "react-i18next": "13", "react-is": "^18.2.0", @@ -115,6 +116,7 @@ "@storybook/react": "^7.0.6", "@types/node": "^22.6.1", "@types/react": "^18.3.8", + "@types/react-headroom": "^3.2.3", "@types/sanitize-html": "^2.13.0", "babel-plugin-styled-components": "^2.1.4", "browserslist": "^4.21.2", diff --git a/theme/styles/abstracts/_variables.scss b/theme/styles/abstracts/_variables.scss index 2c51b411..c06edae3 100644 --- a/theme/styles/abstracts/_variables.scss +++ b/theme/styles/abstracts/_variables.scss @@ -61,12 +61,12 @@ $font-weights: ( // Cross-component variables // -------------------------------------------------------- $header: ( - height-tablet: 110px, + height-tablet: 95px, height-mobile: 60px, gap: 0, - layout-breakpoint: 1625px, + layout-breakpoint: 1500px, breakpoint-for-search-bar: 850px, - nav-link-padding-lateral: 1em, + nav-link-padding-lateral: 0.75em, nav-width-desktop: 450px, nav-width-mobile: 300px, button-width-tablet: 114px, diff --git a/theme/styles/components/_index.scss b/theme/styles/components/_index.scss index 4f014753..347bcd6d 100644 --- a/theme/styles/components/_index.scss +++ b/theme/styles/components/_index.scss @@ -2,11 +2,9 @@ @forward "page-header"; // Globals -@forward "components/global/Header/header"; -@forward "components/global/Header/navigation.scss"; -@forward "components/global/Header/subnavigation.scss"; -@forward "components/global/Header/subsubnavigation.scss"; -@forward "components/global/Header/search-bar.scss"; +@forward "components/organisms/Header/navigation.scss"; +@forward "components/organisms/Header/subnavigation.scss"; +@forward "components/organisms/Header/subsubnavigation.scss"; @forward "components/global/Footer/footer"; @forward "components/global/Footer/social.scss"; @forward "components/global/Footer/nav.scss"; diff --git a/types/link.d.ts b/types/link.d.ts new file mode 100644 index 00000000..653011d0 --- /dev/null +++ b/types/link.d.ts @@ -0,0 +1,9 @@ +interface InternalLink { + id: string; + uri?: string; + title: string; +} + +interface InternalLinkWithChildren extends InternalLink { + children: Array; +} diff --git a/yarn.lock b/yarn.lock index de8d9ea2..15e39503 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2492,10 +2492,10 @@ resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@rubin-epo/epo-react-lib@^2.5.1": - version "2.5.1" - resolved "https://registry.yarnpkg.com/@rubin-epo/epo-react-lib/-/epo-react-lib-2.5.1.tgz#86acf0aa748a7b89e5698a412d690f318bd621f2" - integrity sha512-vXuQ5P9Dwz0pdLQOFEiQx5GruzkHohYBy/D/3oEBzVF7BA1K68P0Zr2CSkhfvaOdORFwmyLVnOR2RvPxqzhDyQ== +"@rubin-epo/epo-react-lib@^2.5.3": + version "2.5.3" + resolved "https://registry.yarnpkg.com/@rubin-epo/epo-react-lib/-/epo-react-lib-2.5.3.tgz#e36eecd6562b3a8f652afd82e3ec215f60f095a5" + integrity sha512-nSmmf4GnYv8j7i8uPhNQDUov7znmscHYBstF9nxQiQLB/vVTN4w2Mr/Dhq65t7rRL7Miba5PfAP8ONrDHqzfNg== dependencies: "@castiron/style-mixins" "^1.0.6" "@headlessui/react" "2.0.4" @@ -3817,6 +3817,21 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== +"@types/react-headroom@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@types/react-headroom/-/react-headroom-3.2.3.tgz#5756fe089146d52e45bbb527cbe9443352107535" + integrity sha512-rdgcMfoxN6wnGm8mSlwA7vuuK8ojXUI/tWHX1qJSAS7DNuvt7zv3zUtYs15UETEtE0Wc2YJCq0PZlHhZ+lRP4Q== + dependencies: + "@types/react" "*" + +"@types/react@*": + version "18.3.12" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.12.tgz#99419f182ccd69151813b7ee24b792fe08774f60" + integrity sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/react@>=16": version "18.0.37" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.37.tgz#7a784e2a8b8f83abb04dc6b9ed9c9b4c0aee9be7" @@ -10906,7 +10921,7 @@ prompts@^2.4.0: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.5.8, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -11037,6 +11052,13 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== +raf@^3.3.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + ramda@^0.28.0: version "0.28.0" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.28.0.tgz#acd785690100337e8b063cab3470019be427cc97" @@ -11120,6 +11142,15 @@ react-fast-compare@^3.0.1: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== +react-headroom@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/react-headroom/-/react-headroom-3.2.1.tgz#30a3e27cc6d21d464665ccedb94f62a698031de3" + integrity sha512-C+7bmTwmHWIOjU1r5aSLm4tAQKjmqW4PB65tHllE/D6h+If1+V3gy83BOvp46OCo25LtJJlLa/HDs7Eq5OPFgA== + dependencies: + prop-types "^15.5.8" + raf "^3.3.0" + shallowequal "^1.1.0" + react-hook-form@^7.33.1: version "7.39.1" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.39.1.tgz#ded87d4b3f6692d1f9219515f78ca282b6e1ebf7" @@ -11902,7 +11933,7 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" -shallowequal@1.1.0: +shallowequal@1.1.0, shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== From bdf1022bb6814ff44ec585a3b5b9d6b161222724 Mon Sep 17 00:00:00 2001 From: Alexandra Goff Date: Thu, 19 Dec 2024 17:01:05 -0500 Subject: [PATCH 5/5] fix: set revalidation time, fix subnav height --- components/molecules/Logo/styles.module.css | 1 + .../organisms/Header/_subnavigation.scss | 1 - components/svg/unique/index.js | 4 - components/svg/unique/site/Logo.js | 35 -------- components/svg/unique/site/LogoFullSize.js | 79 ------------------- lib/api/globals/index.ts | 2 +- yarn.lock | 42 +++------- 7 files changed, 15 insertions(+), 149 deletions(-) delete mode 100644 components/svg/unique/site/Logo.js delete mode 100644 components/svg/unique/site/LogoFullSize.js diff --git a/components/molecules/Logo/styles.module.css b/components/molecules/Logo/styles.module.css index 078e71f5..9c3286c8 100644 --- a/components/molecules/Logo/styles.module.css +++ b/components/molecules/Logo/styles.module.css @@ -8,6 +8,7 @@ width: auto; & > img { + height: 100%; max-height: 100%; } } diff --git a/components/organisms/Header/_subnavigation.scss b/components/organisms/Header/_subnavigation.scss index 2a806167..553d786b 100644 --- a/components/organisms/Header/_subnavigation.scss +++ b/components/organisms/Header/_subnavigation.scss @@ -14,7 +14,6 @@ top: 0; left: 0; width: functions.header(nav-width-mobile); - height: 100%; &--is-active { transform: translateX(-100%); diff --git a/components/svg/unique/index.js b/components/svg/unique/index.js index e5b9f0dd..d9b3e2bf 100644 --- a/components/svg/unique/index.js +++ b/components/svg/unique/index.js @@ -2,8 +2,6 @@ import Arrow from "./Arrow"; import Cloudy from "./weather/Cloudy"; import CloudyPartial from "./weather/CloudyPartial"; import Gear from "./Gear"; -import Logo from "./site/Logo"; -import LogoFullSize from "./site/LogoFullSize"; import Rain from "./weather/Rain"; import RainHeavy from "./weather/RainHeavy"; import RainLight from "./weather/RainLight"; @@ -17,8 +15,6 @@ export default { Cloudy, CloudyPartial, Gear, - Logo, - LogoFullSize, Rain, RainHeavy, RainLight, diff --git a/components/svg/unique/site/Logo.js b/components/svg/unique/site/Logo.js deleted file mode 100644 index e21312b8..00000000 --- a/components/svg/unique/site/Logo.js +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from "prop-types"; -import classNames from "classnames"; -export default function Logo({ className }) { - return ( - - - - - - - - - - - - - - - - - - ); -} - -Logo.displayName = "SVG.Logo"; - -Logo.propTypes = { - className: PropTypes.string, -}; diff --git a/components/svg/unique/site/LogoFullSize.js b/components/svg/unique/site/LogoFullSize.js deleted file mode 100644 index 12fdfd07..00000000 --- a/components/svg/unique/site/LogoFullSize.js +++ /dev/null @@ -1,79 +0,0 @@ -import PropTypes from "prop-types"; -import classNames from "classnames"; -export default function LogoFullSize({ className }) { - return ( - - - - ); -} - -LogoFullSize.displayName = "SVG.LogoFullSize"; - -LogoFullSize.propTypes = { - className: PropTypes.string, -}; diff --git a/lib/api/globals/index.ts b/lib/api/globals/index.ts index 2c6f4776..d091f5c3 100644 --- a/lib/api/globals/index.ts +++ b/lib/api/globals/index.ts @@ -41,7 +41,7 @@ export async function getLogos() { const { data } = await queryAPI({ query, variables: { set: "siteInfo" }, - fetchOptions: { next: { tags: [tags.globals] } }, + fetchOptions: { next: { tags: [tags.globals], revalidate: 60 * 60 } }, }); if (!data || !data.siteInfo) { diff --git a/yarn.lock b/yarn.lock index 15e39503..80f414b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10851,7 +10851,16 @@ postcss@8.4.38: picocolors "^1.0.0" source-map-js "^1.2.0" -postcss@^8.2.14, postcss@^8.3.11, postcss@^8.4.14, postcss@^8.4.19, postcss@^8.4.21, postcss@^8.4.24: +postcss@^8.0.0, postcss@^8.4.28, postcss@^8.4.35: + version "8.4.49" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.49.tgz#4ea479048ab059ab3ae61d082190fabfd994fe19" + integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== + dependencies: + nanoid "^3.3.7" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +postcss@^8.2.14, postcss@^8.3.11, postcss@^8.4.14, postcss@^8.4.19, postcss@^8.4.21: version "8.4.47" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365" integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ== @@ -12234,16 +12243,7 @@ string-argv@^0.3.1: resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -12390,7 +12390,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -12411,13 +12411,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -13601,7 +13594,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -13628,15 +13621,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"