Skip to content

Commit

Permalink
feat(FOROME-97): add final scroll shadowier component
Browse files Browse the repository at this point in the history
  • Loading branch information
QSdmitrioul committed Apr 26, 2022
1 parent 0fd91d6 commit 10c54f6
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 105 deletions.
12 changes: 11 additions & 1 deletion src/components/variant/drawer.utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'

export enum DrawerClass {
normClass = 'norm',
Expand Down Expand Up @@ -63,5 +63,15 @@ export const useScrollShadow = (
}
}

useEffect(() => {
if (element) {
console.log(element.childNodes[0].childNodes[0])
/*const topShadow = createShadow(element, 'top')
const rightShadow = createShadow(element, 'right')
const bottomShadow = createShadow(element, 'bottom')
const leftShadow = createShadow(element, 'left')*/
}
}, [element])

return { shouldAddShadow, handleScroll, handleStartScroll }
}
222 changes: 118 additions & 104 deletions src/ui/scroll-shadower/scroll-shadowier.tsx
Original file line number Diff line number Diff line change
@@ -1,125 +1,139 @@
import React, { FC, useEffect, useRef } from 'react'
import cn, { Argument } from 'classnames'

import {
Container,
RootContainer,
ShadowBottom,
ShadowLeft,
ShadowRight,
ShadowTop,
} from '@ui/scroll-shadower/scroll-shadowier.styles'
createShadow,
createTrigger,
DisplayValue,
Placement,
} from '@ui/scroll-shadower/scroll-shadowier.utils'

interface Prop {
classname: Argument
}

export const ScrollShadowier: FC = ({ children, ...props }) => {
const ref = useRef<HTMLDivElement>(null)
const left = useRef<HTMLDivElement>(null)
const right = useRef<HTMLDivElement>(null)
const bottom = useRef<HTMLDivElement>(null)
const top = useRef<HTMLDivElement>(null)
export const ScrollShadowier: FC<Prop> = ({ classname, children }) => {
const shadowsRef = useRef<HTMLDivElement>(null)
const scrollableRef = useRef<HTMLDivElement>(null)

useEffect(() => {
const resizeHandler = () => {
if (
!ref.current ||
!top.current ||
!bottom.current ||
!left.current ||
!right.current
) {
return
}

const {
offsetWidth = 0,
offsetHeight = 0,
clientHeight = 0,
clientWidth = 0,
} = ref.current || {}

const verticalOffset = offsetHeight - clientHeight
const horizontalOffset = offsetWidth - clientWidth

bottom.current.style.bottom = verticalOffset + 'px'
bottom.current.style.right = horizontalOffset + 'px'

top.current.style.right = horizontalOffset + 'px'
const scrollable = scrollableRef.current
const shadows = shadowsRef.current
if (!scrollable || !shadows) {
return
}

right.current.style.right = horizontalOffset + 'px'
right.current.style.bottom = verticalOffset + 'px'
const area = scrollable?.firstElementChild as HTMLDivElement

left.current.style.bottom = verticalOffset + 'px'
if (!area) {
return
}

const scrollHandler = () => {
if (
!ref.current ||
!top.current ||
!bottom.current ||
!left.current ||
!right.current
) {
return
}
const topTrigger = createTrigger(area, Placement.top)
const rightTrigger = createTrigger(area, Placement.right)
const bottomTrigger = createTrigger(area, Placement.bottom)
const leftTrigger = createTrigger(area, Placement.left)

const none = 'none'
const initial = 'initial'
const topShadow = createShadow(shadows, Placement.top)
const rightShadow = createShadow(shadows, Placement.right)
const bottomShadow = createShadow(shadows, Placement.bottom)
const leftShadow = createShadow(shadows, Placement.left)

const resizeObserver = new ResizeObserver(entries => {
const {
scrollWidth = 0,
scrollLeft = 0,
offsetWidth = 0,
scrollHeight = 0,
scrollTop = 0,
offsetHeight = 0,
clientHeight = 0,
clientWidth = 0,
} = ref.current || {}

const verticalOffset = offsetHeight - clientHeight
const horizontalOffset = offsetWidth - clientWidth

const onLeft = scrollLeft != 0
const onRight = scrollLeft + offsetWidth + horizontalOffset < scrollWidth
const onTop = scrollTop > 0
const onBottom = scrollTop + offsetHeight + verticalOffset < scrollHeight

left.current.style.display = onLeft ? initial : none
right.current.style.display = onRight ? initial : none
top.current.style.display = onTop ? initial : none
bottom.current.style.display = onBottom ? initial : none
}
/* const options = {
root: ref.current,
rootMargin: '0px',
threshold: [0.0, 1.0],
}*/
const resizeObserver = new ResizeObserver(resizeHandler)
//const scrollObserver = new IntersectionObserver(scrollHandler, options)

scrollHandler()
resizeHandler()

const node = ref.current
if (!node) return
contentRect: { width, height },
} = entries[0]
shadows.style.width = `${width}px`
shadows.style.height = `${height}px`
})

resizeObserver.observe(scrollable)

const intersectionObserver = new IntersectionObserver(
entries => {
for (const entry of entries) {
switch (entry.target) {
case topTrigger:
topShadow.style.display = entry.isIntersecting
? DisplayValue.none
: DisplayValue.block
break
case rightTrigger:
rightShadow.style.display = entry.isIntersecting
? DisplayValue.none
: DisplayValue.block
break
case bottomTrigger:
bottomShadow.style.display = entry.isIntersecting
? DisplayValue.none
: DisplayValue.block
break
case leftTrigger:
leftShadow.style.display = entry.isIntersecting
? DisplayValue.none
: DisplayValue.block
break
}
}
},
{
root: scrollable,
},
)
intersectionObserver.observe(topTrigger)
intersectionObserver.observe(rightTrigger)
intersectionObserver.observe(bottomTrigger)
intersectionObserver.observe(leftTrigger)

//scrollObserver.observe(ref.current)
node.addEventListener('scroll', scrollHandler)
resizeObserver.observe(node)
return () => {
resizeObserver.unobserve(node)
node.removeEventListener('scroll', scrollHandler)
//scrollObserver.unobserve(ref.current)
;[
topTrigger,
rightTrigger,
bottomTrigger,
leftTrigger,
topShadow,
rightShadow,
bottomShadow,
leftShadow,
].forEach(target => target.remove())
intersectionObserver.disconnect()
resizeObserver.disconnect()
}
}, [])

return (
<RootContainer>
<ShadowBottom ref={bottom} />
<ShadowTop ref={top} />
<ShadowLeft ref={left} />
<ShadowRight ref={right} />
<Container ref={ref} {...props}>
{children}
</Container>
</RootContainer>
<div
className={cn(classname)}
style={{
position: 'relative',
}}
>
<div
ref={shadowsRef}
style={{ position: 'absolute', left: 0, top: 0 }}
></div>
<div
ref={scrollableRef}
style={{
position: 'absolute',
zIndex: 1,
top: 0,
right: 0,
bottom: 0,
left: 0,
overflow: 'auto',
}}
>
<div
style={{
position: 'relative',
width: 'fit-content',
height: 'fit-content',
}}
>
{children}
</div>
</div>
</div>
)
}
70 changes: 70 additions & 0 deletions src/ui/scroll-shadower/scroll-shadowier.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
export enum Placement {
top = 'top',
bottom = 'bottom',
left = 'left',
right = 'right',
}

export enum DisplayValue {
initial = 'initial',
none = 'none',
block = 'block',
}

export const getBackground = (placement: Placement) => {
return `linear-gradient(to ${placement}, rgba(255, 255, 255, 0), rgba(0, 0, 0, 0.25))`
}

export const createShadow = (
container: HTMLDivElement,
placement: Placement,
) => {
const size = '20px'
const shadow = document.createElement('div')

Object.assign(shadow.style, {
position: 'absolute',
display: DisplayValue.initial,
zIndex: 2,
top: placement === Placement.bottom ? undefined : 0,
right: placement === Placement.left ? undefined : 0,
bottom: placement === Placement.top ? undefined : 0,
left: placement === Placement.right ? undefined : 0,
width:
placement === Placement.left || placement === Placement.right
? size
: undefined,
height:
placement === Placement.top || placement === Placement.bottom
? size
: undefined,
background: getBackground(placement),
})

container.appendChild(shadow)

return shadow
}

export const createTrigger = (
container: HTMLDivElement,
placement: Placement,
): HTMLElement => {
const trigger = document.createElement('div')

Object.assign(trigger.style, {
position: 'absolute',
zIndex: 10,
left: placement === Placement.right ? '100%' : 0,
right: placement === Placement.left ? '100%' : 0,
top: placement === Placement.bottom ? '100%' : 0,
bottom: placement === Placement.top ? '100%' : 0,
})

trigger.style.position = 'absolute'
trigger.style.zIndex

container.appendChild(trigger)

return trigger
}

0 comments on commit 10c54f6

Please sign in to comment.