Skip to content

Commit

Permalink
Merge faa4078 into 131000f
Browse files Browse the repository at this point in the history
  • Loading branch information
kaisermann committed Jan 29, 2020
2 parents 131000f + faa4078 commit 5151a89
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 139 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- `--fixed` modification class to the `StickyRow` component when stuck at the top.

### Changed
- Non `sticky` rows are not wrapped in `StickyRow` anymore.

## [2.25.1] - 2020-01-24
### Fixed
Expand Down
31 changes: 16 additions & 15 deletions docs/README.md
Expand Up @@ -162,21 +162,22 @@ To use this CSS API, you must add the `styles` builder and create an app styling

Below, we describe the namespaces that are defined in the header.

| Class name | Description | Component Source |
| -------------------- | ----------------------------------------------- | ------------------------------------------------- |
| `container` | The main container of header | [index](/react/index.js) |
| `leanMode` | The main container of header on lean mode | [index](/react/index.js) |
| `topMenuContainer` | The container of `fixed` top menu | [FixedContent](/react/components/FixedContent.js) |
| `topMenuLogo` | The container of logo in `fixed` top menu | [Logo](/react/components/Logo.js) |
| `topMenuSearchBar` | The container of search bar in `fixed` top menu | [SearchBar](/react/components/SearchBar.js) |
| `topMenuIcons` | The container of icons on `fixed` top menu | [Icons](/react/components/Icons.js) |
| `topMenuCollapsible` | The container of `collapsible` top menu | [Collapsible](/react/components/Collapsible.js) |
| `forceCenter` | The container of `ForceCenter` component | [ForceCenter](/react/ForceCenter.js) |
| `forceCenterInnerContainer` | The innermost container of `ForceCenter`'s children | [ForceCenter](/react/ForceCenter.js) |
| `headerBorder` | The `Border` component | [Border](/react/components/Border.tsx) |
| `headerSpacer` | The `Spacer` component | [Spacer](/react/components/Spacer.tsx) |
| `headerStickyRow` | The `StickyRow` component | [StickyRow](/react/components/StickyRow.tsx) |
| `headerRowContentContainer` | The container for the content inside of `Row` component | [Row](/react/components/Row.tsx) |
| Class name | Description | Component Source |
| --------------------------- | --------------------------------------------------------- | ------------------------------------------------- |
| `container` | The main container of header | [index](/react/index.js) |
| `leanMode` | The main container of header on lean mode | [index](/react/index.js) |
| `topMenuContainer` | The container of `fixed` top menu | [FixedContent](/react/components/FixedContent.js) |
| `topMenuLogo` | The container of logo in `fixed` top menu | [Logo](/react/components/Logo.js) |
| `topMenuSearchBar` | The container of search bar in `fixed` top menu | [SearchBar](/react/components/SearchBar.js) |
| `topMenuIcons` | The container of icons on `fixed` top menu | [Icons](/react/components/Icons.js) |
| `topMenuCollapsible` | The container of `collapsible` top menu | [Collapsible](/react/components/Collapsible.js) |
| `forceCenter` | The container of `ForceCenter` component | [ForceCenter](/react/ForceCenter.js) |
| `forceCenterInnerContainer` | The innermost container of `ForceCenter`'s children | [ForceCenter](/react/ForceCenter.js) |
| `headerBorder` | The `Border` component | [Border](/react/components/Border.tsx) |
| `headerSpacer` | The `Spacer` component | [Spacer](/react/components/Spacer.tsx) |
| `headerStickyRow` | The `StickyRow` component | [StickyRow](/react/components/StickyRow.tsx) |
| `headerStickyRow--fixed` | The `StickyRow` component when stuck at the top of window | [StickyRow](/react/components/StickyRow.tsx) |
| `headerRowContentContainer` | The container for the content inside of `Row` component | [Row](/react/components/Row.tsx) |

## Troubleshooting

Expand Down
12 changes: 7 additions & 5 deletions react/components/Border.tsx
Expand Up @@ -11,11 +11,13 @@ const CSS_HANDLES = ['headerBorder'] as const

const Border: FunctionComponent<Props> = ({ sticky }) => {
const handles = useCssHandles(CSS_HANDLES)
return (
<StickyRow sticky={sticky}>
<div className={`${handles.headerBorder} bb b--muted-3`} />
</StickyRow>
)
const border = <div className={`${handles.headerBorder} bb b--muted-3`} />

if (sticky) {
return <StickyRow>{border}</StickyRow>
}

return border
}

export default Border
50 changes: 27 additions & 23 deletions react/components/Row.tsx
Expand Up @@ -28,36 +28,40 @@ const Row: FunctionComponent<Props> = ({
}) => {
const handles = useCssHandles(CSS_HANDLES)

const content = (
const rowContent = (
<div className={`${handles.headerRowContainer} w-100 flex items-center`}>
{children}
</div>
)

return (
<StickyRow sticky={sticky} zIndex={zIndex}>
<div className={handles.headerRow}>
<div
className={classNames(
`${handles.headerRowBackground} w-100`,
inverted
? 'bg-base--inverted c-on-base--inverted'
: 'bg-base c-on-base'
)}
>
{fullWidth ? (
content
) : (
<Container
className={`${handles.headerRowContentContainer} w-100 flex`}
>
{content}
</Container>
)}
</div>
const row = (
<div className={handles.headerRow}>
<div
className={classNames(
`${handles.headerRowBackground} w-100`,
inverted
? 'bg-base--inverted c-on-base--inverted'
: 'bg-base c-on-base'
)}
>
{fullWidth ? (
rowContent
) : (
<Container
className={`${handles.headerRowContentContainer} w-100 flex`}
>
{rowContent}
</Container>
)}
</div>
</StickyRow>
</div>
)

if (sticky) {
return <StickyRow zIndex={zIndex}>{row}</StickyRow>
}

return row
}

export default Row
40 changes: 19 additions & 21 deletions react/components/StickyRow.tsx
@@ -1,42 +1,40 @@
import React, { FunctionComponent, useContext, CSSProperties } from 'react'
import React, { FunctionComponent, useContext } from 'react'
import ReactResizeDetector from 'react-resize-detector'
import { useCssHandles } from 'vtex.css-handles'
import { useCssHandles, applyModifiers } from 'vtex.css-handles'

import { RowContext } from './StickyRows'
import { useScrollThreshold } from '../hooks/useScrollThreshold'

interface Props {
sticky?: boolean
zIndex?: number
}

const CSS_HANDLES = ['headerStickyRow'] as const

const StickyRow: FunctionComponent<Props> = ({ children, sticky, zIndex }) => {
const StickyRow: FunctionComponent<Props> = ({ children, zIndex }) => {
const handles = useCssHandles(CSS_HANDLES)
const { offset, onResize } = useContext(RowContext)

const stickyStyle: CSSProperties = {
top: offset,
zIndex,
}
const { ref, hasReachedThreshold } = useScrollThreshold<HTMLDivElement>({
offset,
})
const mainCssHandle = hasReachedThreshold
? applyModifiers(handles.headerStickyRow, 'fixed')
: handles.headerStickyRow

return (
<div
style={sticky ? stickyStyle : undefined}
className={`${handles.headerStickyRow} ${
sticky ? `sticky ${!zIndex ? 'z-999' : ''}` : ''
}`}
ref={ref}
style={{ top: offset, zIndex }}
className={`${mainCssHandle} sticky ${!zIndex ? 'z-999' : ''}`}
>
{children}

{sticky && (
<ReactResizeDetector
handleHeight
onResize={(_, height) => {
onResize(height)
}}
/>
)}
<ReactResizeDetector
handleHeight
onResize={(_, height) => {
onResize(height)
}}
/>
</div>
)
}
Expand Down
37 changes: 37 additions & 0 deletions react/hooks/useCumulativeHeightState.ts
@@ -0,0 +1,37 @@
import { useState } from 'react'

type State = Record<number, number>

const useCumulativeHeightState = () => {
const [state, set] = useState<State>({})

const updateRowHeight = ({
height,
index,
}: {
height: number
index: number
}) => {
if (state[index] === height) {
return
}

set(prev => ({ ...prev, [index]: height }))
}

const getAccumulatedHeight = (index: number) => {
const sortedIndices = Object.keys(state)
.map(key => parseInt(key, 10))
.sort((a, b) => a - b)

const indices = sortedIndices.slice(0, sortedIndices.indexOf(index))
return indices.reduce((acc, cur) => (state[cur] || 0) + acc, 0)
}

return {
getAccumulatedHeight,
updateRowHeight,
}
}

export default useCumulativeHeightState
75 changes: 0 additions & 75 deletions react/hooks/useCumulativeHeightState.tsx

This file was deleted.

76 changes: 76 additions & 0 deletions react/hooks/useScrollThreshold.ts
@@ -0,0 +1,76 @@
import { useState, useLayoutEffect, useCallback } from 'react'
import throttle from 'throttleit'

const listeners = new Set<Function>()

const scrollHandler = throttle(() => {
const scrollOffset = window.pageYOffset
listeners.forEach(fn => fn(scrollOffset))
}, 100)

function detach(fn: Function) {
listeners.delete(fn)
if (listeners.size === 0) {
window.removeEventListener('scroll', scrollHandler)
}
}

function attach(fn: Function) {
if (listeners.size === 0) {
window.addEventListener('scroll', scrollHandler)
}
listeners.add(fn)
return () => detach(fn)
}

/**
* Hook that handles the scroll position.
* It uses one single window scroll listener.
* @returns {Number} - scroll position value
*/
export function useScrollThreshold<T extends HTMLElement>({
offset,
}: {
offset: number
}) {
// Initial element distance to the top of the container
const [initialOffsetTop, setInitialOffsetTop] = useState<number>()
const [hasReachedThreshold, setReachedThreshold] = useState(false)

// We use this callback as a ref to get the instance of the element once its mounted
// Reference: https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node
const mountedDivRef = useCallback(
(node: T) => {
if (node !== null) {
// `offsetTop` can be used for position:sticky, but not for position:fixed
// If we decide to change the css implementation for a js one, this should be revisited
setInitialOffsetTop(node.offsetTop)
}
},
// The rule below is disabled because we want to get a new callback every time
// the `offset` changes, which is a way to listen for possible sticky row size changes
// eslint-disable-next-line react-hooks/exhaustive-deps
[offset]
)

useLayoutEffect(
() =>
attach((scroll: number) => {
const reached =
initialOffsetTop != null && initialOffsetTop - scroll <= offset

// checks if reached state has changed
// if not, do nothing
if (hasReachedThreshold === reached) return

// otherwise, update the state
setReachedThreshold(reached)
}),
[hasReachedThreshold, initialOffsetTop, offset]
)

return {
hasReachedThreshold,
ref: mountedDivRef,
}
}
1 change: 1 addition & 0 deletions react/typings/throttleit.d.ts
@@ -0,0 +1 @@
declare module 'throttleit'

0 comments on commit 5151a89

Please sign in to comment.