Skip to content

Commit

Permalink
Merge pull request #146 from ensdomains/add-scrollbox-divider-always-…
Browse files Browse the repository at this point in the history
…show

Maintain modal consistency with Dialog.Content component
  • Loading branch information
storywithoutend authored May 23, 2024
2 parents f3e536d + ea10184 commit 16e673e
Show file tree
Hide file tree
Showing 6 changed files with 366 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const mockIntersectionObserver = makeMockIntersectionObserver(
)

const expectLine = (e: 'top' | 'bottom', visible: boolean) =>
expect(screen.getByTestId('scroll-box')).toHaveAttribute(
expect(screen.getByTestId(`scrollbox-${e}-line`)).toHaveAttribute(
`data-${e}-line`,
visible ? 'true' : 'false',
)
Expand Down
165 changes: 99 additions & 66 deletions components/src/components/atoms/ScrollBox/ScrollBox.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
import * as React from 'react'
import styled, { css } from 'styled-components'

const StyledScrollBox = styled.div(
import { Space } from '../../../tokens/index'

const Container = styled.div(
({ theme }) => css`
position: relative;
border: solid ${theme.space.px} transparent;
width: 100%;
height: 100%;
border-left-width: 0;
border-right-width: 0;
`,
)

const StyledScrollBox = styled.div<{ $horizontalPadding?: Space }>(
({ theme, $horizontalPadding }) => css`
overflow: auto;
position: relative;
width: 100%;
height: 100%;
${$horizontalPadding &&
css`
padding: 0 ${theme.space[$horizontalPadding]};
`}
@property --scrollbar {
syntax: '<color>';
inherits: true;
initial-value: ${theme.colors.greyLight};
}
@property --top-line-color {
syntax: '<color>';
inherits: true;
initial-value: transparent;
}
@property --bottom-line-color {
syntax: '<color>';
inherits: true;
initial-value: transparent;
}
/* stylelint-disable custom-property-no-missing-var-function */
transition: --scrollbar 0.15s ease-in-out,
height 0.15s ${theme.transitionTimingFunction.popIn},
Expand Down Expand Up @@ -57,68 +65,74 @@ const StyledScrollBox = styled.div(
&:hover {
--scrollbar: ${theme.colors.greyBright};
}
`,
)

&[data-top-line='true'] {
--top-line-color: ${theme.colors.greyLight};
&::before {
z-index: 100;
}
}
const IntersectElement = styled.div(
() => css`
display: block;
height: 0px;
`,
)

&[data-bottom-line='true'] {
--bottom-line-color: ${theme.colors.greyLight};
&::after {
z-index: 100;
}
}
const Divider = styled.div<{ $horizontalPadding?: Space }>(
({ theme, $horizontalPadding }) => css`
position: absolute;
left: 0;
height: 1px;
width: ${theme.space.full};
background: transparent;
transition: background-color 0.15s ease-in-out;
::-webkit-scrollbar-track {
border-top: solid ${theme.space['px']} var(--top-line-color);
border-bottom: solid ${theme.space['px']} var(--bottom-line-color);
}
${$horizontalPadding &&
css`
left: ${theme.space[$horizontalPadding]};
width: calc(100% - 2 * ${theme.space[$horizontalPadding]});
`}
&::before,
&::after {
content: '';
position: sticky;
left: 0;
width: 100%;
display: block;
height: ${theme.space.px};
&[data-top-line] {
top: -${theme.space.px};
}
&::before {
top: 0;
background-color: var(--top-line-color);
&[data-top-line='true'] {
background: ${theme.colors.border};
}
&::after {
bottom: 0;
background-color: var(--bottom-line-color);
&[data-bottom-line] {
bottom: -${theme.space.px};
}
`,
)
const IntersectElement = styled.div(
() => css`
display: block;
height: 0px;
&[data-bottom-line='true'] {
background: ${theme.colors.border};
}
`,
)

type Props = {
/** If true, the dividers will be hidden */
hideDividers?: boolean | { top?: boolean; bottom?: boolean }
/** If true, the dividers will always be shown */
alwaysShowDividers?: boolean | { top?: boolean; bottom?: boolean }
/** The number of pixels below the top of the content where events such as showing/hiding dividers and onReachedTop will be executed */
topTriggerPx?: number
/** The number of pixels above the bottom of the content where events such as showing/hiding dividers and onReachedTop will be executed */
bottomTriggerPx?: number
/** A callback function that is fired when the content reaches topTriggerPx */
onReachedTop?: () => void
/** A callback function that is fired when the content reaches bottomTriggerPx */
onReachedBottom?: () => void
/** The amount of horizontal padding to apply to the scrollbox. This will decrease the content area as well as the width of the overflow indicator dividers*/
horizontalPadding?: Space
} & React.HTMLAttributes<HTMLDivElement>

export const ScrollBox = ({
hideDividers = false,
alwaysShowDividers = false,
topTriggerPx = 16,
bottomTriggerPx = 16,
onReachedTop,
onReachedBottom,
horizontalPadding,
children,
...props
}: Props) => {
Expand All @@ -130,18 +144,26 @@ export const ScrollBox = ({
typeof hideDividers === 'boolean' ? hideDividers : !!hideDividers?.top
const hideBottom =
typeof hideDividers === 'boolean' ? hideDividers : !!hideDividers?.bottom
const alwaysShowTop =
typeof alwaysShowDividers === 'boolean'
? alwaysShowDividers
: !!alwaysShowDividers?.top
const alwaysShowBottom =
typeof alwaysShowDividers === 'boolean'
? alwaysShowDividers
: !!alwaysShowDividers?.bottom

const funcRef = React.useRef<{
onReachedTop?: () => void
onReachedBottom?: () => void
}>({ onReachedTop, onReachedBottom })

const [showTop, setShowTop] = React.useState(false)
const [showBottom, setShowBottom] = React.useState(false)
const [showTop, setShowTop] = React.useState(alwaysShowTop)
const [showBottom, setShowBottom] = React.useState(alwaysShowBottom)

const handleIntersect: IntersectionObserverCallback = (entries) => {
const intersectingTop = [false, -1]
const intersectingBottom = [false, -1]
const intersectingTop: [boolean, number] = [false, -1]
const intersectingBottom: [boolean, number] = [false, -1]
for (let i = 0; i < entries.length; i += 1) {
const entry = entries[i]
const iref =
Expand All @@ -151,9 +173,13 @@ export const ScrollBox = ({
iref[1] = entry.time
}
}
intersectingTop[1] !== -1 && !hideTop && setShowTop(!intersectingTop[0])
intersectingTop[1] !== -1 &&
!hideTop &&
!alwaysShowTop &&
setShowTop(!intersectingTop[0])
intersectingBottom[1] !== -1 &&
!hideBottom &&
!alwaysShowBottom &&
setShowBottom(!intersectingBottom[0])
intersectingTop[0] && funcRef.current.onReachedTop?.()
intersectingBottom[0] && funcRef.current.onReachedBottom?.()
Expand Down Expand Up @@ -184,18 +210,25 @@ export const ScrollBox = ({
}, [onReachedTop, onReachedBottom])

return (
<StyledScrollBox
data-bottom-line={showBottom}
data-top-line={showTop}
ref={ref}
{...props}
>
<IntersectElement data-testid="scrollbox-top-intersect" ref={topRef} />
{children}
<IntersectElement
data-testid="scrollbox-bottom-intersect"
ref={bottomRef}
<Container {...props}>
<StyledScrollBox $horizontalPadding={horizontalPadding} ref={ref}>
<IntersectElement data-testid="scrollbox-top-intersect" ref={topRef} />
{children}
<IntersectElement
data-testid="scrollbox-bottom-intersect"
ref={bottomRef}
/>
</StyledScrollBox>
<Divider
$horizontalPadding={horizontalPadding}
data-testid="scrollbox-top-line"
data-top-line={showTop}
/>
<Divider
$horizontalPadding={horizontalPadding}
data-bottom-line={showBottom}
data-testid="scrollbox-bottom-line"
/>
</StyledScrollBox>
</Container>
)
}
12 changes: 11 additions & 1 deletion components/src/components/organisms/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { mq } from '@/src/utils/responsiveHelpers'

import { WithAlert } from '@/src/types'

import { FontSize } from '@/src/tokens/typography'

import { Modal, Typography } from '../..'
import { DialogContent } from './DialogContent'

const IconCloseContainer = styled.button(
({ theme }) => css`
Expand Down Expand Up @@ -44,6 +47,7 @@ const StyledCard = styled.div(
display: flex;
flex-direction: column;
align-items: center;
overflow: hidden;
gap: ${theme.space['4']};
padding: ${theme.space['4']};
border-radius: ${theme.radii['3xLarge']};
Expand All @@ -52,12 +56,15 @@ const StyledCard = styled.div(
background-color: ${theme.colors.background};
position: relative;
width: 100%;
max-height: 80vh;
${mq.sm.min(css`
min-width: ${theme.space['64']};
max-width: 80vw;
border-radius: ${theme.radii['3xLarge']};
padding: ${theme.space['6']};
gap: ${theme.space['6']};
max-height: min(90vh, ${theme.space['144']});
`)}
`,
)
Expand Down Expand Up @@ -201,6 +208,7 @@ const StepItem = styled.div<{ $type: StepType }>(
type TitleProps = {
title?: string | React.ReactNode
subtitle?: string | React.ReactNode
fontVariant?: FontSize
} & WithAlert

type StepProps = {
Expand Down Expand Up @@ -241,13 +249,14 @@ const Heading = ({
title,
subtitle,
alert,
fontVariant = 'headingFour',
}: TitleProps & StepProps & WithAlert) => {
return (
<TitleContainer>
{alert && <Icon alert={alert} />}
{title &&
((typeof title !== 'string' && title) || (
<Title fontVariant="headingFour">{title}</Title>
<Title fontVariant={fontVariant}>{title}</Title>
))}
{subtitle &&
((typeof subtitle !== 'string' && subtitle) || (
Expand Down Expand Up @@ -415,4 +424,5 @@ export const Dialog = ({
Dialog.displayName = 'Dialog'
Dialog.Footer = Footer
Dialog.Heading = Heading
Dialog.Content = DialogContent
Dialog.CloseButton = CloseButton
Loading

0 comments on commit 16e673e

Please sign in to comment.