Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CTC-98 Add shadow to OakMediaClipList accordion #335

Merged
merged 6 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 14 additions & 2 deletions src/components/atoms/InternalAccordion/InternalAccordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,24 @@ const FlexWithReset = styled(OakFlex)`

export const InternalAccordionContent = ({
children,
onScroll,
ref,
...rest
}: OakBoxProps & { "aria-labelledby": string }) => {
}: OakBoxProps & {
"aria-labelledby": string;
onScroll?: () => void;
ref?: React.MutableRefObject<null | HTMLDivElement>;
}) => {
const { isOpen } = useAccordionContext();

return (
<OakBox hidden={!isOpen} role="region" {...rest}>
<OakBox
hidden={!isOpen}
role="region"
{...rest}
onScroll={onScroll}
ref={ref}
>
{children}
</OakBox>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { OakThemeProvider } from "@/components/atoms";
import { oakDefaultTheme } from "@/styles";
import renderWithTheme from "@/test-helpers/renderWithTheme";

const useRefSpy = jest.spyOn(React, "useRef");

describe(InternalChevronAccordion, () => {
it("matches snapshot", () => {
const tree = create(
Expand Down Expand Up @@ -44,4 +46,112 @@ describe(InternalChevronAccordion, () => {

expect(queryByRole("region")).not.toBeInTheDocument();
});

it("renders correct initial opacity of box shadow when scroll is present", () => {
Object.defineProperties(HTMLElement.prototype, {
scrollHeight: { get: () => 348, configurable: true },
clientHeight: { get: () => 300, configurable: true },
scrollTop: { get: () => 20, configurable: true },
});

const { getByTestId } = renderWithTheme(
<InternalChevronAccordion
header="See more"
id="see-more"
initialOpen={true}
>
Here it is
</InternalChevronAccordion>,
);

const boxShadow = getByTestId("bottom-box-shadow");
const styles = getComputedStyle(boxShadow);

expect(useRefSpy).toHaveBeenCalled();
expect(styles.opacity).toBe("1");
});

it("renders correct initial opacity of box shadow when scroll is not present", () => {
Object.defineProperties(HTMLElement.prototype, {
scrollHeight: { get: () => 348, configurable: true },
clientHeight: { get: () => 348, configurable: true },
scrollTop: { get: () => 20, configurable: true },
});

const { getByTestId } = renderWithTheme(
<InternalChevronAccordion
header="See more"
id="see-more"
initialOpen={true}
>
Here it is
</InternalChevronAccordion>,
);

const boxShadow = getByTestId("bottom-box-shadow");
const styles = getComputedStyle(boxShadow);

expect(useRefSpy).toHaveBeenCalled();
expect(styles.opacity).toBe("0");
});

it("renders correct opacity of box shadow after scrolling to the end", () => {
const { getByTestId } = renderWithTheme(
<InternalChevronAccordion
header="See more"
id="see-more"
initialOpen={true}
>
Here it is
</InternalChevronAccordion>,
);

Object.defineProperties(HTMLElement.prototype, {
scrollHeight: { get: () => 348, configurable: true },
clientHeight: { get: () => 328, configurable: true },
scrollTop: { get: () => 0, configurable: true },
});

act(() => {
fireEvent.scroll(getByTestId("scrollable-content"), {
scrollY: 20,
});
});

const boxShadow = getByTestId("bottom-box-shadow");
const styles = getComputedStyle(boxShadow);

expect(useRefSpy).toHaveBeenCalled();
expect(styles.opacity).toBe("0");
});

it("renders correct opacity of box shadow after scrolling notto the end", async () => {
weronika-szalas marked this conversation as resolved.
Show resolved Hide resolved
const { getByTestId } = renderWithTheme(
<InternalChevronAccordion
header="See more"
id="see-more"
initialOpen={true}
>
Here it is
</InternalChevronAccordion>,
);

Object.defineProperties(HTMLElement.prototype, {
scrollHeight: { get: () => 348, configurable: true },
clientHeight: { get: () => 328, configurable: true },
scrollTop: { get: () => 0, configurable: true },
});

act(() => {
fireEvent.scroll(getByTestId("scrollable-content"), {
scrollY: 10,
});
});

const boxShadow = getByTestId("bottom-box-shadow");
const styles = getComputedStyle(boxShadow);

expect(useRefSpy).toHaveBeenCalled();
expect(styles.opacity).toBe("1");
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactNode } from "react";
import React, { ReactNode, useEffect, useRef, useState } from "react";
import styled from "styled-components";

import { OakHandDrawnFocusUnderline } from "@/components/molecules/OakHandDrawnFocusUnderline";
Expand All @@ -19,6 +19,7 @@ import InternalAccordionProvider from "@/components/atoms/InternalAccordion/Inte
import { InternalStyledSvgProps } from "@/components/atoms/InternalStyledSvg";
import { FlexStyleProps, flexStyle } from "@/styles/utils/flexStyle";
import { parseSpacing } from "@/styles/helpers/parseSpacing";
import { parseOpacity } from "@/styles/helpers/parseOpacity";
import { ColorStyleProps } from "@/styles/utils/colorStyle";

export type InternalChevronAccordionProps = {
Expand Down Expand Up @@ -78,6 +79,26 @@ const StyledContainer = styled(OakFlex)`
${flexStyle}
`;

type BottomBoxShadowProps = {
shouldDisplayShadow: boolean;
};

export const BottomBoxShadow = styled(OakBox)<BottomBoxShadowProps>`
position: absolute;
bottom: 0;
width: 100%;
height: 50px;
opacity: ${(props) =>
props.shouldDisplayShadow
? parseOpacity("opaque")
: parseOpacity("transparent")};
z-index: 100;
-webkit-box-shadow: inset 0px -55px 30px -30px rgba(255, 255, 255, 1);
-moz-box-shadow: inset 0px -55px 30px -30px rgba(255, 255, 255, 1);
box-shadow: inset 0px -55px 30px -30px rgba(255, 255, 255, 1);
padding: 2px;
`;

/**
* An accordion component that can be used to show/hide content
*/
Expand All @@ -88,6 +109,36 @@ const Accordion = ({
id,
...styleProps
}: InternalChevronAccordionProps) => {
const [shouldDisplayShadow, setShouldDisplayShadow] = useState(false);
const scrollBox = useRef<HTMLDivElement>(null);

useEffect(() => {
const scrollHeight = scrollBox.current?.scrollHeight;
const clientHeight = scrollBox.current?.clientHeight;

if (scrollHeight && clientHeight && scrollHeight > clientHeight) {
setShouldDisplayShadow(true);
} else {
setShouldDisplayShadow(false);
}
}, []);

const handleScroll = () => {
const scrollHeight = scrollBox.current?.scrollHeight;
const scrollTop = scrollBox.current?.scrollTop;
const clientHeight = scrollBox.current?.clientHeight;

if (scrollHeight && scrollTop) {
const bottom = scrollHeight - scrollTop === clientHeight;

if (bottom) {
setShouldDisplayShadow(false);
} else {
setShouldDisplayShadow(true);
}
}
};

const { isOpen } = useAccordionContext();

return (
Expand Down Expand Up @@ -127,10 +178,24 @@ const Accordion = ({
/>
</OakBox>
</StyledAccordionButton>
<InternalAccordionContent aria-labelledby={id} $overflow={"scroll"}>
{children}
</InternalAccordionContent>
<OakBox
ref={scrollBox}
$position={"relative"}
$overflow={"scroll"}
onScroll={handleScroll}
data-testid={"scrollable-content"}
>
<InternalAccordionContent aria-labelledby={id}>
{children}
</InternalAccordionContent>
</OakBox>
<StyledAccordionUnderline $fill={"border-decorative5-stronger"} />
{isOpen && (
<BottomBoxShadow
shouldDisplayShadow={shouldDisplayShadow}
data-testid="bottom-box-shadow"
/>
)}
</StyledContainer>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ exports[`InternalChevronAccordion matches snapshot 1`] = `
}

.c12 {
position: relative;
overflow: scroll;
font-family: --var(google-font),Lexend,sans-serif;
overflow: scroll;
Expand Down Expand Up @@ -151,6 +152,19 @@ exports[`InternalChevronAccordion matches snapshot 1`] = `
visibility: visible;
}

.c17 {
position: absolute;
bottom: 0;
width: 100%;
height: 50px;
opacity: 0;
z-index: 100;
-webkit-box-shadow: inset 0px -55px 30px -30px rgba(255,255,255,1);
-moz-box-shadow: inset 0px -55px 30px -30px rgba(255,255,255,1);
box-shadow: inset 0px -55px 30px -30px rgba(255,255,255,1);
padding: 2px;
}

<div
className="c0 c1 c2"
>
Expand Down Expand Up @@ -205,12 +219,18 @@ exports[`InternalChevronAccordion matches snapshot 1`] = `
</div>
</button>
<div
aria-labelledby="see-more"
className="c12"
hidden={false}
role="region"
data-testid="scrollable-content"
onScroll={[Function]}
>
Here it is
<div
aria-labelledby="see-more"
className="c13"
hidden={false}
role="region"
>
Here it is
</div>
</div>
<div
className="c13 c14 c15 c16"
Expand All @@ -233,5 +253,9 @@ exports[`InternalChevronAccordion matches snapshot 1`] = `
/>
</svg>
</div>
<div
className="c13 c17"
data-testid="bottom-box-shadow"
/>
</div>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ exports[`OakMediaClipListAccordion matches snapshot 1`] = `
}

.c15 {
position: relative;
overflow: scroll;
font-family: --var(google-font),Lexend,sans-serif;
overflow: scroll;
Expand Down Expand Up @@ -174,6 +175,19 @@ exports[`OakMediaClipListAccordion matches snapshot 1`] = `
visibility: visible;
}

.c20 {
position: absolute;
bottom: 0;
width: 100%;
height: 50px;
opacity: 0;
z-index: 100;
-webkit-box-shadow: inset 0px -55px 30px -30px rgba(255,255,255,1);
-moz-box-shadow: inset 0px -55px 30px -30px rgba(255,255,255,1);
box-shadow: inset 0px -55px 30px -30px rgba(255,255,255,1);
padding: 2px;
}

.c2 {
position: relative;
border-width: 2px;
Expand Down Expand Up @@ -260,12 +274,18 @@ exports[`OakMediaClipListAccordion matches snapshot 1`] = `
</div>
</button>
<div
aria-labelledby="see-more"
className="c15"
hidden={false}
role="region"
data-testid="scrollable-content"
onScroll={[Function]}
>
Here it is
<div
aria-labelledby="see-more"
className="c16"
hidden={false}
role="region"
>
Here it is
</div>
</div>
<div
className="c16 c17 c18 c19"
Expand All @@ -288,6 +308,10 @@ exports[`OakMediaClipListAccordion matches snapshot 1`] = `
/>
</svg>
</div>
<div
className="c16 c20"
data-testid="bottom-box-shadow"
/>
</div>
</div>
`;
Loading
Loading