Skip to content

Commit a451b86

Browse files
committed
chore: remove topOffset & handle calc in top
1 parent abcaeba commit a451b86

File tree

4 files changed

+46
-38
lines changed

4 files changed

+46
-38
lines changed

src/Shared/Helpers.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,5 +1063,5 @@ export const deriveBorderRadiusAndBorderClassFromConfig = ({
10631063
return `${deriveBorderClassFromConfig(borderConfig)} ${deriveBorderRadiusClassFromConfig({ top, right, bottom, left })}`
10641064
}
10651065

1066-
export const getClassNameForStickyHeaderWithShadow = (isStuck: boolean) =>
1067-
`dc__position-sticky dc__top-0 dc__transition--box-shadow ${isStuck ? 'dc__box-shadow--header' : ''}`
1066+
export const getClassNameForStickyHeaderWithShadow = (isStuck: boolean, topClassName = 'dc__top-0') =>
1067+
`dc__position-sticky ${topClassName} dc__transition--box-shadow ${isStuck ? 'dc__box-shadow--header' : ''}`
Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
1-
import { Dispatch, SetStateAction, MutableRefObject } from 'react'
1+
import { MutableRefObject } from 'react'
22

33
export type UseStickyEventProps<T extends HTMLElement = HTMLDivElement> = {
4-
/**
5-
* Callback function that is called when the sticky element is 'stuck' or 'unstuck'
6-
*/
7-
callback: (isStuck: boolean) => void | Dispatch<SetStateAction<boolean>>
84
/**
95
* Unique identifier used to create the id of the sentinel element
106
*
@@ -19,14 +15,6 @@ export type UseStickyEventProps<T extends HTMLElement = HTMLDivElement> = {
1915
* - If the sticky element is always rendered, this flag can be ignored.
2016
*/
2117
isStickyElementMounted?: boolean
22-
/**
23-
* The top offset value of the sticky element.
24-
* This can be a CSS value such as '10px', '1rem', or 'calc(100% + 10px)'.
25-
*
26-
* If the top value is specified in 'px' or 'rem', this value can be ignored.
27-
* Use this only if the top value is specified in percentage or uses 'calc'.
28-
*/
29-
topOffset?: string
3018
} & (
3119
| {
3220
/**
@@ -39,3 +27,8 @@ export type UseStickyEventProps<T extends HTMLElement = HTMLDivElement> = {
3927
}
4028
| { containerSelector: string; containerRef?: never }
4129
)
30+
31+
export interface UseStickyEventReturnType<T extends HTMLElement = HTMLDivElement> {
32+
isStuck: boolean
33+
stickyElementRef: MutableRefObject<T>
34+
}

src/Shared/Hooks/useStickyEvent/useStickyEvent.ts

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { useEffect, useRef } from 'react'
1+
import { useEffect, useRef, useState } from 'react'
22
import { isNullOrUndefined } from '@Shared/Helpers'
33
import { noop } from '@Common/Helper'
4-
import { UseStickyEventProps } from './types'
5-
import { FALLBACK_SENTINEL_HEIGHT, OBSERVER_ROOT_MARGIN, OBSERVER_THRESHOLD } from './constants'
4+
import { UseStickyEventProps, UseStickyEventReturnType } from './types'
5+
import { OBSERVER_ROOT_MARGIN, OBSERVER_THRESHOLD } from './constants'
6+
import { getHeightForStickyElementTopOffset } from './utils'
67

78
import './styles.scss'
89

@@ -12,14 +13,13 @@ import './styles.scss'
1213
* as a reference for the implementation
1314
*/
1415
const useStickyEvent = <T extends HTMLElement = HTMLDivElement>({
15-
callback,
1616
containerSelector,
1717
containerRef,
1818
identifier,
19-
topOffset,
2019
isStickyElementMounted = true,
21-
}: UseStickyEventProps<T>) => {
20+
}: UseStickyEventProps<T>): UseStickyEventReturnType<T> => {
2221
const stickyElementRef = useRef<T>(null)
22+
const [isStuck, setIsStuck] = useState(false)
2323

2424
useEffect(
2525
() => {
@@ -43,7 +43,6 @@ const useStickyEvent = <T extends HTMLElement = HTMLDivElement>({
4343
// Using IntersectionObserver, we observe both the sticky element and the sentinel.
4444
// When the sentinel is not fully in view, isIntersecting is false,
4545
// indicating the sticky element is stuck given it is fully in view.
46-
let previousStuckState: boolean
4746
let hasSentinelLeftView: boolean
4847
let hasHeaderLeftView: boolean
4948

@@ -65,12 +64,7 @@ const useStickyEvent = <T extends HTMLElement = HTMLDivElement>({
6564
}
6665
})
6766

68-
const isStuck = hasSentinelLeftView && !hasHeaderLeftView
69-
70-
if (isNullOrUndefined(previousStuckState) || isStuck !== previousStuckState) {
71-
callback(isStuck)
72-
previousStuckState = isStuck
73-
}
67+
setIsStuck(hasSentinelLeftView && !hasHeaderLeftView)
7468
},
7569
{ root: stickyElementParent, threshold: OBSERVER_THRESHOLD, rootMargin: OBSERVER_ROOT_MARGIN },
7670
)
@@ -86,15 +80,7 @@ const useStickyEvent = <T extends HTMLElement = HTMLDivElement>({
8680
// The sentinel element's height must exceed the sticky element's top CSS value.
8781
// This guarantees that when the sticky element sticks to the container's edge,
8882
// the sentinel element will extend beyond the scroll container.
89-
sentinelElement.style.height = topOffset
90-
? `calc(${topOffset} + 1px)`
91-
: (window.getComputedStyle(stickyElementRef.current).top?.replace(/[0-9]+/g, (match) => {
92-
const nMatch = Number(match)
93-
if (Number.isNaN(nMatch)) {
94-
return FALLBACK_SENTINEL_HEIGHT
95-
}
96-
return `${nMatch + 1}`
97-
}) ?? FALLBACK_SENTINEL_HEIGHT)
83+
sentinelElement.style.height = getHeightForStickyElementTopOffset<T>({ stickyElementRef })
9884

9985
stickyElementRef.current.appendChild(sentinelElement)
10086

@@ -114,7 +100,7 @@ const useStickyEvent = <T extends HTMLElement = HTMLDivElement>({
114100
isNullOrUndefined(isStickyElementMounted) ? [] : [isStickyElementMounted],
115101
)
116102

117-
return { stickyElementRef }
103+
return { stickyElementRef, isStuck }
118104
}
119105

120106
export default useStickyEvent
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { FALLBACK_SENTINEL_HEIGHT } from './constants'
2+
import { UseStickyEventReturnType } from './types'
3+
4+
export const getHeightForStickyElementTopOffset = <T extends HTMLElement>({
5+
stickyElementRef,
6+
}: Pick<UseStickyEventReturnType<T>, 'stickyElementRef'>) => {
7+
const topValue = window.getComputedStyle(stickyElementRef.current).top
8+
9+
if (!topValue) {
10+
return FALLBACK_SENTINEL_HEIGHT
11+
}
12+
13+
const calcRegex = /calc\(.*\)/g
14+
const doesTopOffsetContainCalc = calcRegex.test(topValue)
15+
16+
if (doesTopOffsetContainCalc) {
17+
return topValue.replace(calcRegex, (match) => `calc(${match} + 1px)`)
18+
}
19+
20+
return topValue.replace(/\d+(\.\d+)?/g, (match) => {
21+
const nMatch = Number(match)
22+
23+
if (Number.isNaN(nMatch)) {
24+
return FALLBACK_SENTINEL_HEIGHT
25+
}
26+
27+
return `${nMatch + 1}`
28+
})
29+
}

0 commit comments

Comments
 (0)