This repository was archived by the owner on Dec 19, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Vulcan: Add TOC (Desktop/ Mobile) and Scroll Indicator #1923
Merged
Merged
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
54c3db0
Troubleshooting: Add a scroll percentage indicator to the page
jarstelfox a85591d
Scroll Indicator: Add some options
jarstelfox 3d211c4
Scroll Indicator: adjust on window resize
jarstelfox 713688b
Minor: Fix ref not working on heading link component
jarstelfox d1e95d6
TOC: Create a tocContext
jarstelfox 0887332
Minor: Use import from react
jarstelfox c5c90d6
Troubleshooting content: Wrap in TOC Context provider
jarstelfox 38c2ab6
Minor: Whitespace
jarstelfox a04dfd5
Sections: Add to toc context
jarstelfox 6f23610
Minor: Add a ugly TOC to view that it is working
jarstelfox 8743850
TOC active: Use intersectionobserver to detect which section is active
jarstelfox ee32410
TOC Context: Determine closet by distance from the cursor
jarstelfox 9a303c0
Cursors: It was a bad idea.
jarstelfox 91dd481
TOC: Consider active to match the distance traveled
jarstelfox f41a5c2
TOC: Fix infinate rerendering
jarstelfox e25e24b
Minor: Move scroll indicator down in dom
jarstelfox e40ca39
Minor: Hide scroll indicator unless it has been passed
jarstelfox 816721a
TOC: Render to the right
jarstelfox b26ee67
TOC: Get looking decent
jarstelfox 00e026e
Closest: Simplify algorithm
jarstelfox ea7227d
WIP
jarstelfox 56d725d
TOC Context: Include scrolling behavior
jarstelfox 6246281
TOC: Start to match figma
jarstelfox f3ab6d5
TOC: Add generic flex scroll gradient component
jarstelfox 621a029
TOC Items: Scroll to them if required
jarstelfox 092c460
Minor: Clean up some styling from the flex gradient work
jarstelfox f76123e
TSC: Make happy
jarstelfox 9ff5b9e
Server side: Render titles
jarstelfox 5e6749f
Refactor: Move some logic into hooks
jarstelfox 79d4e16
TOC: Force server side first
jarstelfox dd172f0
Mobile TOC: Get basic UI working
jarstelfox d9be8b0
Mobile TOC: get layout working
jarstelfox 956d3b8
Mobile TOC: Match figma as close as I can
jarstelfox 65bea61
Minor: Remove console log
jarstelfox 8edafb9
TOC: Add option to highlight an item on click
jarstelfox cd1d5dd
Minor: Desktop TOC Items
jarstelfox f1d4d5b
Remove untitled sections from the TOC
jarstelfox b3e9c19
Minor: Remove unused import
jarstelfox 15cd211
TOC: Add ID to url on scroll by default
jarstelfox d581375
Mobile TOC: Ensure TOC is above other elements
jarstelfox 3dce58b
Minor: Pull mobile toc out of the document flow
jarstelfox 4456b97
Mobile TOC: Limit to height in example
jarstelfox 78774cc
Mobile TOC: Add nice scroll fade effect
jarstelfox 7838586
Flex Gradients: Take border radius into account
jarstelfox 1d2b5b3
Refactor: Pull highlighting out of the shared context
jarstelfox 3d032ef
Minor: Bypass ts error by reversing array
jarstelfox ea11cff
Mobile TOC: Close on hide
jarstelfox 0272f4e
Merge remote-tracking branch 'origin/main' into scroll-add-ui
jarstelfox 53aac01
Mobile TOC: Scroll to active menu item if shown
jarstelfox d26ad46
Minor: Menu let my code handle selection
jarstelfox a3e5190
TSC: Fix type error
jarstelfox f5585e3
TOCRecord: Remove visibility state
jarstelfox 103880f
Minor: Improve scroll performance
jarstelfox d163565
TOC: Hide behind env flag
jarstelfox File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,243 @@ | ||
| import { | ||
| Flex, | ||
| FlexProps, | ||
| SystemStyleObject, | ||
| useBreakpointValue, | ||
| useMergeRefs, | ||
| } from '@chakra-ui/react'; | ||
| import { useRef, useLayoutEffect, forwardRef, useState } from 'react'; | ||
|
|
||
| type InnerFlexStyling = { | ||
| _after: SystemStyleObject; | ||
| _before: SystemStyleObject; | ||
| overflowX?: FlexProps['overflowX']; | ||
| overflowY?: FlexProps['overflowY']; | ||
| }; | ||
|
|
||
| const gradientWidths = { base: 40, sm: 50, md: 70, lg: 100 }; | ||
|
|
||
| const sharedStyle: SystemStyleObject = { | ||
| content: '""', | ||
| position: 'absolute', | ||
| height: '100%', | ||
| pointerEvents: 'none', | ||
| zIndex: 9999, | ||
| }; | ||
|
|
||
| function easeInOutQuad(x: number): number { | ||
| return x < 0.5 ? 2 * x * x : 1 - Math.pow(-2 * x + 2, 2) / 2; | ||
| } | ||
|
|
||
| enum FlexScrollDirection { | ||
| ROW, | ||
| COLUMN, | ||
| } | ||
|
|
||
| function getScrollDirection(el: HTMLElement): FlexScrollDirection { | ||
| const { flexDirection } = window.getComputedStyle(el); | ||
| if (flexDirection === 'column') { | ||
| return FlexScrollDirection.COLUMN; | ||
| } else { | ||
| return FlexScrollDirection.ROW; | ||
| } | ||
| } | ||
|
|
||
| function getGradientsBaseStyle( | ||
| scrollDirection: FlexScrollDirection, | ||
| gradientSizePX: number, | ||
| color = '#f9fafb' | ||
| ) { | ||
| const beforeStart = | ||
| scrollDirection === FlexScrollDirection.ROW ? 'left' : 'top'; | ||
| const afterStart = | ||
| scrollDirection === FlexScrollDirection.ROW ? 'right' : 'bottom'; | ||
|
|
||
| const beforeBackgroundDegrees = | ||
| scrollDirection === FlexScrollDirection.ROW ? 270 : 0; | ||
| const afterBackgroundDegrees = | ||
| scrollDirection === FlexScrollDirection.ROW ? 90 : 180; | ||
|
|
||
| const gradientSizeProp = | ||
| scrollDirection === FlexScrollDirection.ROW ? 'width' : 'height'; | ||
|
|
||
| return { | ||
| before: { | ||
| ...sharedStyle, | ||
| [gradientSizeProp]: `${gradientSizePX}px`, | ||
| [beforeStart]: '0px', | ||
| background: `linear-gradient(${beforeBackgroundDegrees}deg, rgba(249, 250, 251, 0) 0%, ${color} 106.41%)`, | ||
| }, | ||
| after: { | ||
| ...sharedStyle, | ||
| [gradientSizeProp]: `${gradientSizePX}px`, | ||
| [afterStart]: '0px', | ||
| background: `linear-gradient(${afterBackgroundDegrees}deg, rgba(249, 250, 251, 0) 0%, ${color} 106.41%)`, | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| function getGradientStyleProps( | ||
| el: HTMLElement, | ||
| gradientSizePX: number | ||
| ): InnerFlexStyling { | ||
| const flexScrollDirection = getScrollDirection(el); | ||
| const { | ||
| // Used for both calcs | ||
| clientWidth, | ||
| clientHeight, | ||
| // Used for ROWs | ||
| scrollWidth, | ||
| scrollLeft, | ||
| // Used for COLUMNs | ||
| scrollHeight, | ||
| scrollTop, | ||
| } = el; | ||
|
|
||
| const isScrollable = | ||
| flexScrollDirection === FlexScrollDirection.ROW | ||
| ? scrollWidth > clientWidth | ||
| : scrollHeight > clientHeight; | ||
|
|
||
| if (!isScrollable) { | ||
| return { | ||
| _before: { opacity: 0, height: 0 }, | ||
| _after: { opacity: 0, height: 0 }, | ||
| }; | ||
| } | ||
|
|
||
| const start = | ||
| flexScrollDirection === FlexScrollDirection.ROW ? scrollLeft : scrollTop; | ||
| const windowSpace = | ||
| flexScrollDirection === FlexScrollDirection.ROW | ||
| ? clientWidth | ||
| : clientHeight; | ||
| const gradientSpace = | ||
| flexScrollDirection === FlexScrollDirection.ROW | ||
| ? clientHeight | ||
| : clientWidth; | ||
| const scrollSpace = | ||
| flexScrollDirection === FlexScrollDirection.ROW | ||
| ? scrollWidth | ||
| : scrollHeight; | ||
| const sizeProp = | ||
| flexScrollDirection === FlexScrollDirection.ROW ? 'height' : 'width'; | ||
|
|
||
| const scrollPosition = start + windowSpace; | ||
| const afterStart = scrollSpace - gradientSizePX; | ||
|
|
||
| const size = `${gradientSpace}px`; | ||
|
|
||
| const beforeGradientShownPercent = | ||
| start < gradientSizePX ? start / gradientSizePX : 1; | ||
|
|
||
| const afterGradientShownPercent = | ||
| scrollPosition >= afterStart | ||
| ? 1 - (scrollPosition - afterStart) / gradientSizePX | ||
| : 1; | ||
|
|
||
| const beforeGradientOpacity = easeInOutQuad( | ||
| Math.max(0, beforeGradientShownPercent) | ||
| ); | ||
|
|
||
| const afterGradientOpacity = easeInOutQuad( | ||
| Math.max(0, afterGradientShownPercent) | ||
| ); | ||
|
|
||
| const { before, after } = getGradientsBaseStyle( | ||
| flexScrollDirection, | ||
| gradientSizePX | ||
| ); | ||
|
|
||
| const { borderRadius } = window.getComputedStyle(el); | ||
|
|
||
| const overflowX = | ||
| flexScrollDirection === FlexScrollDirection.ROW ? 'auto' : undefined; | ||
| const overflowY = | ||
| flexScrollDirection === FlexScrollDirection.ROW ? undefined : 'auto'; | ||
|
|
||
| return { | ||
| _before: { | ||
| ...before, | ||
| opacity: beforeGradientOpacity, | ||
| [sizeProp]: size, | ||
| borderRadius, | ||
| }, | ||
| _after: { | ||
| ...after, | ||
| opacity: afterGradientOpacity, | ||
| [sizeProp]: size, | ||
| borderRadius, | ||
| }, | ||
| overflowX, | ||
| overflowY, | ||
| }; | ||
| } | ||
|
|
||
| export const FlexScrollGradient = forwardRef(function FlexScrollGradient( | ||
| { | ||
| nestedFlexProps, | ||
| ...props | ||
| }: FlexProps & { | ||
| nestedFlexProps?: FlexProps; | ||
| }, | ||
| ref | ||
| ) { | ||
| const internalRef = useRef<HTMLDivElement>(); | ||
| const refs = useMergeRefs(internalRef, ref); | ||
|
|
||
| const [flexScrollState, setFlexScrollState] = useState<InnerFlexStyling>({ | ||
| _before: { opacity: 0 }, | ||
| _after: { opacity: 0 }, | ||
| }); | ||
|
|
||
| const gradientSizePX = useBreakpointValue(gradientWidths) as number; | ||
|
|
||
| useLayoutEffect(() => { | ||
| const el = internalRef.current; | ||
| if (!el) { | ||
| return; | ||
| } | ||
|
|
||
| const doMeasure = (el: HTMLElement) => { | ||
| const state = getGradientStyleProps(el, gradientSizePX); | ||
| setFlexScrollState(() => state); | ||
| }; | ||
|
|
||
| const measure = () => { | ||
| doMeasure(el); | ||
| }; | ||
|
|
||
| measure(); | ||
|
|
||
| el.addEventListener('transitionend', measure, { passive: true }); | ||
| el.addEventListener('scroll', measure, { passive: true }); | ||
| window.addEventListener('resize', measure, { passive: true }); | ||
|
|
||
| return () => { | ||
| el.removeEventListener('transitionend', measure); | ||
| el.removeEventListener('scroll', measure); | ||
| window.removeEventListener('resize', measure); | ||
| }; | ||
| }, [internalRef, gradientSizePX]); | ||
|
|
||
| if (props.position) { | ||
| throw new Error( | ||
| 'FlexScrollGradient: position prop is not allowed. To get the gradient working we must use position relative. Wrap your component in another layer to handle positioning.' | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <Flex {...props} position="relative" isolation="isolate"> | ||
| <Flex | ||
| ref={refs} | ||
| {...nestedFlexProps} | ||
| overflowX={flexScrollState.overflowX} | ||
| overflowY={flexScrollState.overflowY} | ||
| _before={flexScrollState._before} | ||
| _after={flexScrollState._after} | ||
| > | ||
| {props.children} | ||
| </Flex> | ||
| </Flex> | ||
| ); | ||
| }); | ||
This file contains hidden or 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
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,7 +26,7 @@ export const HeadingSelfLink = forwardRef< | |
| id, | ||
| })} | ||
| {...props} | ||
| {...ref} | ||
| ref={ref} | ||
| > | ||
| {children} | ||
| <Link | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's hard to tell exactly what this component does... but it feels very complex for what seemingly amounts to making a gradient slightly bigger or smaller depending on the browser width. I don't doubt that it looks cool but it strikes me as a lot of complexity (and thus the maintenance) for what it buys us.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is copied from code in the monorepo.
It allows you to pull a nice fade/gradient on any scrollable container. The fade changes sizes depending on the space available so that it does not look out of place.
Although, I hear you, we have this implemented in a few different ways in the mono repo and all versions behave differently. This came up when working on the Guide Products V2 project. We had decided as a team to spend effort to create reusable and well-tested components which will solve UI problems for us once and only once.
I can port the full test suite for this component across as well. If that helps?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Though that doesn't refute the point I was trying to make. Potentially makes it worse as we have at least two copies of this complexity.
I'd rather not. It doesn't help my concerns of complexity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When examining how this looks, I think I prefer the look of a fixed-height gradient (or none at all) over one that has a dynamic height.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think some more general discussion about the desired fade behavior might be in order.
The other feature taken into account here is that the fade slowly shifts its opacity as you scroll until you pass its end.
Here is what I am talking about:
Screencast.from.2023-08-22.16-59-54.webm
Note: I made the fade red to help visibility.
Here's what it looks like normally
Screencast.from.2023-08-22.17-01-35.webm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ianrohde Showed me this approach which does work, but only in chrome and it puts the box shadow behind text, not in front of it: https://daverupert.com/2023/08/animation-timeline-scroll-shadows/