Instantly share code, notes, and snippets.
Created
August 18, 2021 16:25
-
Star
1
(1)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save tomaszgil/3a471eb7de8589c269c55440a1762b20 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useRef, useState, useLayoutEffect, useCallback } from "react"; | |
/* | |
* Copyright 2020 Adobe. All rights reserved. | |
* This file is licensed to you under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. You may obtain a copy | |
* of the License at http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software distributed under | |
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | |
* OF ANY KIND, either express or implied. See the License for the specific language | |
* governing permissions and limitations under the License. | |
*/ | |
function useValueEffect(defaultValue) { | |
const [value, setValue] = useState(defaultValue); | |
const effect = useRef(null); | |
// Store the function in a ref so we can always access the current version | |
// which has the proper `value` in scope. | |
const nextRef = useRef(null); | |
nextRef.current = () => { | |
// Run the generator to the next yield. | |
const newValue = effect.current.next(); | |
// If the generator is done, reset the effect. | |
if (newValue.done) { | |
effect.current = null; | |
return; | |
} | |
// If the value is the same as the current value, | |
// then continue to the next yield. Otherwise, | |
// set the value in state and wait for the next layout effect. | |
if (value === newValue.value) { | |
nextRef.current(); | |
} else { | |
setValue(newValue.value); | |
} | |
}; | |
useLayoutEffect(() => { | |
// If there is an effect currently running, continue to the next yield. | |
if (effect.current) { | |
nextRef.current(); | |
} | |
}); | |
const queue = useCallback( | |
(fn) => { | |
effect.current = fn(); | |
nextRef.current(); | |
}, | |
[effect, nextRef] | |
); | |
return [value, queue]; | |
} | |
export function Breadcrumbs({ children }) { | |
const listRef = useRef(null); | |
const childrenCount = React.Children.count(children); | |
const [visibleItemsCount, setVisibleItemsCount] = useValueEffect( | |
childrenCount | |
); | |
const updateOverflow = useCallback(() => { | |
function computeVisibleItems(currentVisibleItemsCount) { | |
const listItems = Array.from(listRef.current.children); | |
const containerWidth = listRef.current.offsetWidth; | |
const isShowingStack = childrenCount > currentVisibleItemsCount; | |
let calculatedWidth = 0; | |
let newItemsNumber = 0; | |
if (isShowingStack) { | |
calculatedWidth += listItems.shift().offsetWidth; | |
} | |
listItems.reverse(); | |
for (const listItem of listItems) { | |
if (calculatedWidth + listItem.offsetWidth < containerWidth) { | |
calculatedWidth += listItem.offsetWidth; | |
newItemsNumber += 1; | |
} else { | |
break; | |
} | |
} | |
return newItemsNumber; | |
} | |
setVisibleItemsCount(function* () { | |
yield childrenCount; | |
const newVisibleItems = computeVisibleItems(childrenCount); | |
yield newVisibleItems; | |
if (newVisibleItems < childrenCount) { | |
yield computeVisibleItems(newVisibleItems); | |
} | |
}); | |
}, [setVisibleItemsCount, childrenCount]); | |
useLayoutEffect(updateOverflow, [children, updateOverflow]); | |
const shouldRenderStack = childrenCount > visibleItemsCount; | |
const visibleChildren = shouldRenderStack | |
? children.slice(-visibleItemsCount) | |
: children; | |
return ( | |
<nav> | |
<ol ref={listRef} className="list"> | |
{shouldRenderStack && <li className="crumb">...</li>} | |
{React.Children.map(visibleChildren, (item) => ( | |
<li key={item.key} className="crumb"> | |
{item} | |
</li> | |
))} | |
</ol> | |
</nav> | |
); | |
} | |
export function BreadcrumbItem({ children, ...props }) { | |
return <a {...props}>{children}</a>; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment