Skip to content
This repository was archived by the owner on Dec 19, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
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 Aug 10, 2023
a85591d
Scroll Indicator: Add some options
jarstelfox Aug 10, 2023
3d211c4
Scroll Indicator: adjust on window resize
jarstelfox Aug 11, 2023
713688b
Minor: Fix ref not working on heading link component
jarstelfox Aug 11, 2023
d1e95d6
TOC: Create a tocContext
jarstelfox Aug 11, 2023
0887332
Minor: Use import from react
jarstelfox Aug 11, 2023
c5c90d6
Troubleshooting content: Wrap in TOC Context provider
jarstelfox Aug 11, 2023
38c2ab6
Minor: Whitespace
jarstelfox Aug 11, 2023
a04dfd5
Sections: Add to toc context
jarstelfox Aug 11, 2023
6f23610
Minor: Add a ugly TOC to view that it is working
jarstelfox Aug 11, 2023
8743850
TOC active: Use intersectionobserver to detect which section is active
jarstelfox Aug 11, 2023
ee32410
TOC Context: Determine closet by distance from the cursor
jarstelfox Aug 11, 2023
9a303c0
Cursors: It was a bad idea.
jarstelfox Aug 11, 2023
91dd481
TOC: Consider active to match the distance traveled
jarstelfox Aug 11, 2023
f41a5c2
TOC: Fix infinate rerendering
jarstelfox Aug 11, 2023
e25e24b
Minor: Move scroll indicator down in dom
jarstelfox Aug 11, 2023
e40ca39
Minor: Hide scroll indicator unless it has been passed
jarstelfox Aug 11, 2023
816721a
TOC: Render to the right
jarstelfox Aug 11, 2023
b26ee67
TOC: Get looking decent
jarstelfox Aug 11, 2023
00e026e
Closest: Simplify algorithm
jarstelfox Aug 11, 2023
ea7227d
WIP
jarstelfox Aug 11, 2023
56d725d
TOC Context: Include scrolling behavior
jarstelfox Aug 14, 2023
6246281
TOC: Start to match figma
jarstelfox Aug 14, 2023
f3ab6d5
TOC: Add generic flex scroll gradient component
jarstelfox Aug 14, 2023
621a029
TOC Items: Scroll to them if required
jarstelfox Aug 14, 2023
092c460
Minor: Clean up some styling from the flex gradient work
jarstelfox Aug 14, 2023
f76123e
TSC: Make happy
jarstelfox Aug 15, 2023
9ff5b9e
Server side: Render titles
jarstelfox Aug 15, 2023
5e6749f
Refactor: Move some logic into hooks
jarstelfox Aug 15, 2023
79d4e16
TOC: Force server side first
jarstelfox Aug 16, 2023
dd172f0
Mobile TOC: Get basic UI working
jarstelfox Aug 16, 2023
d9be8b0
Mobile TOC: get layout working
jarstelfox Aug 16, 2023
956d3b8
Mobile TOC: Match figma as close as I can
jarstelfox Aug 16, 2023
65bea61
Minor: Remove console log
jarstelfox Aug 16, 2023
8edafb9
TOC: Add option to highlight an item on click
jarstelfox Aug 16, 2023
cd1d5dd
Minor: Desktop TOC Items
jarstelfox Aug 16, 2023
f1d4d5b
Remove untitled sections from the TOC
jarstelfox Aug 16, 2023
b3e9c19
Minor: Remove unused import
jarstelfox Aug 16, 2023
15cd211
TOC: Add ID to url on scroll by default
jarstelfox Aug 16, 2023
d581375
Mobile TOC: Ensure TOC is above other elements
jarstelfox Aug 16, 2023
3dce58b
Minor: Pull mobile toc out of the document flow
jarstelfox Aug 16, 2023
4456b97
Mobile TOC: Limit to height in example
jarstelfox Aug 16, 2023
78774cc
Mobile TOC: Add nice scroll fade effect
jarstelfox Aug 16, 2023
7838586
Flex Gradients: Take border radius into account
jarstelfox Aug 16, 2023
1d2b5b3
Refactor: Pull highlighting out of the shared context
jarstelfox Aug 16, 2023
3d032ef
Minor: Bypass ts error by reversing array
jarstelfox Aug 16, 2023
ea11cff
Mobile TOC: Close on hide
jarstelfox Aug 16, 2023
0272f4e
Merge remote-tracking branch 'origin/main' into scroll-add-ui
jarstelfox Aug 16, 2023
53aac01
Mobile TOC: Scroll to active menu item if shown
jarstelfox Aug 16, 2023
d26ad46
Minor: Menu let my code handle selection
jarstelfox Aug 16, 2023
a3e5190
TSC: Fix type error
jarstelfox Aug 17, 2023
f5585e3
TOCRecord: Remove visibility state
jarstelfox Aug 17, 2023
103880f
Minor: Improve scroll performance
jarstelfox Aug 17, 2023
d163565
TOC: Hide behind env flag
jarstelfox Aug 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ CODEGEN_STOREFRONT_SHOP_NAME="ifixit-test"

# Flags
NEXT_PUBLIC_FLAG__PRODUCT_PAGE_ENABLED=true
NEXT_PUBLIC_FLAG__STORE_HOME_PAGE_ENABLED=true
NEXT_PUBLIC_FLAG__STORE_HOME_PAGE_ENABLED=true
NEXT_PUBLIC_FLAG__TROUBLESHOOTING_TOC_ENABLED=true
4 changes: 3 additions & 1 deletion frontend/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ NEXT_PUBLIC_GTAG_ID=G-5ZXNWJ73GK
# Overridden in prod
NEXT_PUBLIC_ALGOLIA_PRODUCT_INDEX_NAME=dev_product_group_en
NEXT_PUBLIC_DEFAULT_STORE_CODE=us
NEXT_PUBLIC_POLYFILL_DOMAIN=https://www.ifixit.com
NEXT_PUBLIC_POLYFILL_DOMAIN=https://www.ifixit.com
# flags
NEXT_PUBLIC_FLAG__TROUBLESHOOTING_TOC_ENABLED=false
1 change: 1 addition & 0 deletions frontend/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ STRAPI_IMAGE_DOMAIN=ifixit-dev-strapi-uploads.s3.us-west-1.amazonaws.com
NEXT_PUBLIC_ALGOLIA_PRODUCT_INDEX_NAME=dev_product_group_en
NEXT_PUBLIC_DEFAULT_STORE_CODE=test
NEXT_PUBLIC_FLAG__PRODUCT_PAGE_ENABLED=true
NEXT_PUBLIC_FLAG__TROUBLESHOOTING_TOC_ENABLED=false
NEXT_PUBLIC_MATOMO_URL=https://matomo.ubreakit.com
NODE_OPTIONS="--dns-result-order ipv4first"
243 changes: 243 additions & 0 deletions frontend/components/common/FlexScrollGradient.tsx
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(
Copy link
Member

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.

Copy link
Contributor Author

@jarstelfox jarstelfox Aug 22, 2023

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?

Copy link
Member

@danielbeardsley danielbeardsley Aug 22, 2023

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.

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.

Although, I hear you, we have this implemented in a few different ways in the mono repo and all versions behave differently

:trollface: -< (link!)

I can port the full test suite for this component across as well. If that helps?

I'd rather not. It doesn't help my concerns of complexity.

Copy link
Member

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.

Copy link
Contributor Author

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

Copy link
Contributor Author

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/

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>
);
});
2 changes: 2 additions & 0 deletions frontend/config/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ export const flags = {
process.env.NEXT_PUBLIC_FLAG__PRODUCT_PAGE_ENABLED === 'true',
STORE_HOME_PAGE_ENABLED:
process.env.NEXT_PUBLIC_FLAG__STORE_HOME_PAGE_ENABLED === 'true',
TROUBLESHOOTING_TOC_ENABLED:
process.env.NEXT_PUBLIC_FLAG__TROUBLESHOOTING_TOC_ENABLED === 'true',
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const HeadingSelfLink = forwardRef<
id,
})}
{...props}
{...ref}
ref={ref}
>
{children}
<Link
Expand Down
Loading