Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎨 Minor adjustments and refactoring to sticky menu #2769 #2776

Merged
merged 4 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions sanityv3/schemas/documents/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,19 @@ export default {
description: 'You can override the hero image as the SoMe image by uploading another image here.',
fieldset: 'metadata',
},
{
name: 'stickyMenu',
title: 'Sticky Menu',
type: 'stickyMenu',
fieldset: 'stickymenu',
},
...sharedHeroFields,
{
title: 'Is Campain',
name: 'isCampaign',
description: 'Set this to true if the page should be treated as campaign. the header title h1 will be hidden.',
type: 'boolean',
},
{
name: 'stickyMenu',
title: 'Sticky Menu',
type: 'stickyMenu',
fieldset: 'stickymenu',
},
{
name: 'content',
type: 'array',
Expand Down
10 changes: 10 additions & 0 deletions sanityv3/schemas/textSnippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,16 @@ const snippets: textSnippet = {
defaultValue: 'Featured content',
group: groups.common,
},
local: {
title: 'Local',
defaultValue: 'Local',
group: groups.common,
},
global: {
title: 'Global',
defaultValue: 'Global',
group: groups.common,
},
}

type textSnippetGroup = { title: string; hidden?: boolean }
Expand Down
2 changes: 1 addition & 1 deletion web/core/Link/LogoLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const LogoLink = ({ className, ...rest }: LogoLinkProps) => {
)}
prefetch={false}
>
<LogoSecondary className="-mt-[12%]" />
<LogoSecondary className="-mt-[5%]" />
</NextLink>
)
}
Expand Down
104 changes: 52 additions & 52 deletions web/core/Link/ResourceLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,58 @@ export type ResourceLinkProps = {
showExtensionIcon?: boolean
} & Omit<BaseLinkProps, 'type'>

export const iconRotation: Record<string, string> = {
externalUrl: '-rotate-45',
downloadableFile: 'rotate-90',
downloadableImage: 'rotate-90',
internalUrl: '',
}

export const getArrowAnimation = (type: LinkType) => {
switch (type) {
case 'downloadableFile':
case 'downloadableImage':
return 'group-hover:translate-y-0.5'
case 'anchorLink':
return 'translate-y-0.5 group-hover:translate-y-2'
case 'icsLink':
return 'translate-y-0.5'
default:
return 'translate-y-0.5 group-hover:translate-x-2'
}
}

export const getArrowElement = (type: LinkType, iconClassName: string) => {
const iconClassNames = envisTwMerge(
`size-arrow-right
text-energy-red-100
dark:text-white-100
justify-self-end
${iconRotation[type]}
${getArrowAnimation(type)}
transition-all
duration-300
`,
iconClassName,
)
const marginClassName = `ml-6 xl:ml-8`

switch (type) {
case 'downloadableFile':
case 'downloadableImage':
return (
<div className={`flex flex-col px-1 ${marginClassName} translate-y-[1px]`}>
<ArrowRight className={iconClassNames} />
<div className="bg-energy-red-100 h-[2px] w-full" />
</div>
)
case 'icsLink':
return <TransformableIcon iconData={add} className={`${marginClassName} ${iconClassNames}`} />
default:
return <ArrowRight className={`${marginClassName} ${iconClassNames}`} />
}
}

export const ResourceLink = forwardRef<HTMLAnchorElement, ResourceLinkProps>(function ResourceLink(
{
variant = 'default',
Expand All @@ -41,58 +93,6 @@ export const ResourceLink = forwardRef<HTMLAnchorElement, ResourceLinkProps>(fun
},
ref,
) {
const iconRotation: Record<string, string> = {
externalUrl: '-rotate-45',
downloadableFile: 'rotate-90',
downloadableImage: 'rotate-90',
internalUrl: '',
}

const getArrowAnimation = (type: LinkType) => {
switch (type) {
case 'downloadableFile':
case 'downloadableImage':
return 'group-hover:translate-y-0.5'
case 'anchorLink':
return 'translate-y-0.5 group-hover:translate-y-2'
case 'icsLink':
return 'translate-y-0.5'
default:
return 'translate-y-0.5 group-hover:translate-x-2'
}
}

const getArrowElement = (type: LinkType, iconClassName: string) => {
const iconClassNames = envisTwMerge(
`size-arrow-right
text-energy-red-100
dark:text-white-100
justify-self-end
${iconRotation[type]}
${getArrowAnimation(type)}
transition-all
duration-300
`,
iconClassName,
)
const marginClassName = `ml-6 xl:ml-8`

switch (type) {
case 'downloadableFile':
case 'downloadableImage':
return (
<div className={`flex flex-col px-1 ${marginClassName} translate-y-[1px]`}>
<ArrowRight className={iconClassNames} />
<div className="bg-energy-red-100 h-[2px] w-full" />
</div>
)
case 'icsLink':
return <TransformableIcon iconData={add} className={`${marginClassName} ${iconClassNames}`} />
default:
return <ArrowRight className={`${marginClassName} ${iconClassNames}`} />
}
}

const variantClassName: Partial<Record<Variants, string>> = {
default: 'w-full pt-3',
fit: 'w-fit pt-3',
Expand Down
56 changes: 19 additions & 37 deletions web/core/Link/StickyMenuLink.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { forwardRef } from 'react'
import { twMerge } from 'tailwind-merge'
import { BaseLink, BaseLinkProps } from './BaseLink'
import { TransformableIcon } from '../../icons/TransformableIcon'
import { arrow_down, library_pdf } from '@equinor/eds-icons'
import { PiFilePdfThin } from 'react-icons/pi'

export type StickMenuLinkProps = {
isDownloadable?: boolean
Expand All @@ -13,43 +12,26 @@ export const StickyMenuLink = forwardRef<HTMLAnchorElement, StickMenuLinkProps>(
{ children, type = 'internalUrl', className = '', href = '', isDownloadable = false, ...rest },
ref,
) {
const classNames = twMerge(
`
group
inline-flex
align-baseline
w-fit
text-slate-80
leading-0
`,
className,
)
const contentClassNames = `
relative
w-fit
hover:underline
no-underline
`

return (
<BaseLink className={classNames} ref={ref} href={href} {...rest}>
{isDownloadable && <TransformableIcon iconData={library_pdf} className="mr-1" />}
<div className={contentClassNames}>{children}</div>
{isDownloadable && (
<TransformableIcon
iconData={arrow_down}
className="text-energy-red-100
dark:text-white-100 h-[22px] border-energy-red-100 border-b-2 ml-4"
/>
)}
{!isDownloadable && (
<TransformableIcon
iconData={arrow_down}
className="text-energy-red-100
dark:text-white-100
ml-4"
/>
<BaseLink
type={type}
className={twMerge(
`group
relative
flex
justify-center
w-fit
underline-offset-2
text-slate-80
text-sm`,
className,
)}
ref={ref}
href={href}
{...rest}
>
{isDownloadable && <PiFilePdfThin className="mr-2" />}
<div className={`w-fit group-hover:underline no-underline leading-none align-middle`}>{children}</div>
</BaseLink>
)
})
Expand Down
2 changes: 1 addition & 1 deletion web/core/MenuAccordion/MenuButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const MenuButton = forwardRef<HTMLButtonElement, MenuButtonProps>(functio
outline-none
text-slate-80
text-base
font-medium
font-normal
leading-[1em]
md:grid-cols-[min-content_1fr]
hover:bg-moss-green-60
Expand Down
118 changes: 118 additions & 0 deletions web/core/Topbar/Topbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { forwardRef, HTMLAttributes, useEffect, useMemo, useRef, useState } from 'react'
import { mergeRefs } from '@equinor/eds-utils'
import { StickyMenuData } from '../../types/index'
import StickyMenu from '@sections/StickyMenu/StickyMenu'
import { useIntl } from 'react-intl'

export type TopbarProps = {
stickyMenuData?: StickyMenuData
} & HTMLAttributes<HTMLDivElement>

export const Topbar = forwardRef<HTMLDivElement, TopbarProps>(function Topbar(
{ stickyMenuData, children, ...rest },
ref,
) {
const topbarRef = useRef<HTMLDivElement>(null)
const combinedTopbarRef = useMemo(() => mergeRefs<HTMLDivElement>(topbarRef, ref), [topbarRef, ref])
const intl = useIntl()
const [height, setHeight] = useState(0)
const [prevScrollPos, setPrevScrollPos] = useState(0)
const [isVisible, setIsVisible] = useState(true)
const [hasDropShadow, setHasDropShadow] = useState(false)
const showSticky = stickyMenuData && stickyMenuData?.links && stickyMenuData?.links?.length > 0

useEffect(() => {
if (topbarRef && topbarRef?.current) {
setHeight(topbarRef.current.getBoundingClientRect().height)
}
}, [setHeight, topbarRef])

useEffect(() => {
const handleScroll = () => {
let currentScrollPos = window.scrollY
// Fix for iOS to avoid negative scroll positions
if (currentScrollPos < 0) currentScrollPos = 0

setIsVisible(
(prevScrollPos > currentScrollPos && prevScrollPos - currentScrollPos > height) ||
currentScrollPos < prevScrollPos ||
(currentScrollPos === 0 && prevScrollPos === 0),
)

if (!stickyMenuData) {
// Why so complicated? To attempt to limit the amount of calls to setHasDropShadow
if (currentScrollPos < 50) {
if (hasDropShadow) {
setHasDropShadow(false)
}
} else {
if (prevScrollPos > currentScrollPos) {
if (!hasDropShadow) {
setHasDropShadow(true)
}
} else if (prevScrollPos < currentScrollPos && hasDropShadow) {
setHasDropShadow(false)
}
}
}

setPrevScrollPos(currentScrollPos)
}

window.addEventListener('scroll', handleScroll)

return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [prevScrollPos, isVisible, height, hasDropShadow, stickyMenuData])

useEffect(() => {
if (topbarRef && topbarRef?.current) {
const topbar = topbarRef.current

const handleFocus = (event: FocusEvent) => {
if (!isVisible) {
topbar.contains(event.target as Node) && setIsVisible(true)
}
}

topbar.addEventListener('focusin', handleFocus)

return () => topbar.removeEventListener('focusin', handleFocus)
}
}, [isVisible, topbarRef])

return (
<>
<nav
ref={combinedTopbarRef}
aria-label={intl.formatMessage({
id: 'global',
defaultMessage: 'Global',
})}
role="navigation"
className={`
w-screen
${showSticky ? 'sticky' : 'fixed'}
bg-white-100
z-40
animate-height
[transition-property:top]
ease-in-out
duration-300
${isVisible ? 'top-0' : '-top-[var(--topbar-height)]'}
${hasDropShadow ? 'shadow-md' : ''}`}
{...rest}
>
<div className="px-layout-sm max-w-viewport mx-auto flex items-center justify-between py-4">{children}</div>
</nav>
{showSticky && (
<StickyMenu
stickyMenuData={stickyMenuData}
className={`${isVisible ? 'top-[calc(var(--topbar-height)-1px)] pt-2' : 'top-0'}`}
/>
)}
</>
)
})
export default Topbar
Loading
Loading