forked from open-sauced/app
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a5006ed
commit e1a9ccb
Showing
11 changed files
with
1,585 additions
and
51 deletions.
There are no files selected for viewing
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
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
44 changes: 44 additions & 0 deletions
44
components/organisms/DevCardCarousel/dev-card-carousel.test.tsx
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/** | ||
* @jest-environment jsdom | ||
*/ | ||
|
||
import { render, screen } from "@testing-library/react"; | ||
import userEvent from "@testing-library/user-event"; | ||
import "@testing-library/jest-dom"; | ||
import DevCardCarousel from "./dev-card-carousel"; | ||
import { STUB_DEV_CARDS } from "components/organisms/DevCardCarousel/stubData"; | ||
|
||
describe("DevCardCarousel", () => { | ||
it("should render", () => { | ||
render(<DevCardCarousel cards={[...STUB_DEV_CARDS]} />); | ||
}); | ||
|
||
describe("when the user clicks on a card", () => { | ||
it("should trigger the onSelect", () => { | ||
const onSelect = jest.fn(); | ||
render(<DevCardCarousel cards={[...STUB_DEV_CARDS]} onSelect={onSelect} />); | ||
const thirdDevCard = screen.getByTitle(STUB_DEV_CARDS[2].username); | ||
userEvent.click(thirdDevCard); | ||
expect(onSelect).toHaveBeenCalledWith(STUB_DEV_CARDS[2].username); | ||
}); | ||
}); | ||
|
||
describe("when the user uses the arrow keys", () => { | ||
describe("when the user presses the right arrow key", () => { | ||
it("should select last card", () => { | ||
const onSelect = jest.fn(); | ||
render(<DevCardCarousel cards={[...STUB_DEV_CARDS]} onSelect={onSelect} />); | ||
userEvent.keyboard("{arrowright}"); | ||
expect(onSelect).toHaveBeenCalledWith(STUB_DEV_CARDS.slice(-1)[0].username); | ||
}); | ||
}); | ||
describe("when the user presses the left arrow key", () => { | ||
it("should select the second card", () => { | ||
const onSelect = jest.fn(); | ||
render(<DevCardCarousel cards={[...STUB_DEV_CARDS]} onSelect={onSelect} />); | ||
userEvent.keyboard("{arrowleft}"); | ||
expect(onSelect).toHaveBeenCalledWith(STUB_DEV_CARDS[1].username); | ||
}); | ||
}); | ||
}); | ||
}); |
130 changes: 130 additions & 0 deletions
130
components/organisms/DevCardCarousel/dev-card-carousel.tsx
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import cntl from "cntl"; | ||
import DevCard, { DevCardProps } from "components/molecules/DevCard/dev-card"; | ||
import { animated, to, useSprings } from "react-spring"; | ||
import { useGesture } from "@use-gesture/react"; | ||
import { useCallback, useEffect, useState } from "react"; | ||
import { useKey } from "react-use"; | ||
|
||
interface DevCardCarouselProps { | ||
isLoading?: boolean; | ||
cards: Omit<DevCardProps, "isLoading">[]; | ||
onSelect?: (username: string) => void; | ||
} | ||
|
||
const startTo = (index: number, delay = false) => ({ | ||
x: -50 * index, | ||
scale: 1 - index * 0.1, | ||
delay: delay ? index * 100 : 0, | ||
zIndex: 100 - index, | ||
coverOpacity: index * 0.1, | ||
immediate: (key: string) => key === "zIndex", | ||
}); | ||
|
||
const startFrom = (_i: number) => ({ x: 0, scale: 1, y: 0, zIndex: 0, coverOpacity: 0 }); | ||
|
||
const transform = (x: number, y: number, s: number) => `translate(${x}px, ${y}px) scale(${s})`; | ||
|
||
export default function DevCardCarousel(props: DevCardCarouselProps) { | ||
const [cardOrder, setCardOrder] = useState(props.cards.map((card, index) => index)); | ||
const [springProps, api] = useSprings(props.cards.length, (i: number) => ({ | ||
...startTo(i, true), | ||
from: startFrom(i), | ||
})); // Create a bunch of springs using the helpers above | ||
|
||
const bind = useGesture({ | ||
onHover: (state) => { | ||
const hoverIndex = state.args[0]; | ||
api.start((i) => { | ||
// move the card up in y value while hovering | ||
return { y: state.hovering && i === hoverIndex ? -20 : 0 }; | ||
}); | ||
}, | ||
}); | ||
|
||
const handleSelect = useCallback( | ||
(cardOrderIndex: number) => { | ||
const cardIndex = cardOrder[cardOrderIndex]; | ||
props.onSelect?.(props.cards[cardIndex].username); | ||
// take all cards above the clicked card and move them down | ||
setCardOrder((cards) => { | ||
const cardsAfterIndex = cards.slice(cardOrderIndex); | ||
const cardsBeforeIndexInclusive = cards.slice(0, cardOrderIndex); | ||
|
||
return [...cardsAfterIndex, ...cardsBeforeIndexInclusive]; | ||
}); | ||
}, | ||
[cardOrder, props, setCardOrder] | ||
); | ||
|
||
useKey( | ||
"ArrowRight", | ||
() => { | ||
handleSelect(cardOrder.length - 1); | ||
}, | ||
{}, | ||
[handleSelect] | ||
); | ||
useKey( | ||
"ArrowLeft", | ||
() => { | ||
handleSelect(1); | ||
}, | ||
{}, | ||
[handleSelect] | ||
); | ||
|
||
function handleClick(cardOrderIndex: number) { | ||
handleSelect(cardOrderIndex); | ||
} | ||
|
||
useEffect(() => { | ||
api.start((i) => { | ||
const newIndex = cardOrder.indexOf(i); | ||
return { ...startTo(newIndex) }; | ||
}); | ||
}, [cardOrder, api]); | ||
|
||
return ( | ||
<div> | ||
<div className="grid place-content-center"> | ||
{springProps.map(({ x, y, scale, zIndex, coverOpacity }, index) => { | ||
const cardProps = props.cards[index]; | ||
const cardOrderIndex = cardOrder.indexOf(index); | ||
const className = cntl` | ||
DevCardCarousel-card | ||
rounded-3xl | ||
left-0 | ||
top-0 | ||
w-full | ||
h-full | ||
relative | ||
cursor-pointer | ||
`; | ||
return ( | ||
<animated.div | ||
{...bind(index)} | ||
key={cardProps.username} | ||
className={className} | ||
style={{ | ||
gridArea: "1 / 1", | ||
zIndex: zIndex, | ||
transform: to([x, y, scale], transform), | ||
transformOrigin: "left center", | ||
}} | ||
> | ||
<DevCard isLoading={false} isInteractive={index === cardOrder[0]} {...cardProps} /> | ||
<animated.div | ||
className="DevCardCarousel-darken absolute left-0 right-0 top-0 bottom-0 bg-black rounded-3xl z-10" | ||
title={cardProps.username} | ||
style={{ opacity: coverOpacity, pointerEvents: index === cardOrder[0] ? "none" : "auto" }} | ||
onClick={() => { | ||
handleClick(cardOrderIndex); | ||
}} | ||
></animated.div> | ||
</animated.div> | ||
); | ||
})} | ||
</div> | ||
</div> | ||
); | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
export const STUB_DEV_CARDS = [ | ||
{ | ||
username: "foxyblocks", | ||
name: "Chris Schlensker", | ||
avatarURL: "https://avatars.githubusercontent.com/u/555044?v=4", | ||
prs: 2, | ||
contributions: 57, | ||
bio: "This is the story all about how my life got flipped turned upside down, and I'd like to take a minute just sit right there, I'll tell you how I became the prince of a town called Bel-Air.", | ||
activity: "high", | ||
prVelocity: 102, | ||
}, | ||
{ | ||
username: "codebytere", | ||
name: "Shelley Vohr", | ||
avatarURL: "https://avatars.githubusercontent.com/u/2036040?v=4", | ||
prs: 31, | ||
contributions: 1, | ||
bio: "This is the story all about how my life got flipped turned upside down, and I'd like to take a minute just sit right there, I'll tell you how I became the prince of a town called Bel-Air.", | ||
activity: "high", | ||
prVelocity: 67, | ||
}, | ||
{ | ||
username: "miniak", | ||
name: "Milan Burda", | ||
avatarURL: "https://avatars.githubusercontent.com/u/1281234?v=4", | ||
prs: 31, | ||
contributions: 1, | ||
bio: "This is the story all about how my life got flipped turned upside down, and I'd like to take a minute just sit right there, I'll tell you how I became the prince of a town called Bel-Air.", | ||
activity: "high", | ||
prVelocity: 67, | ||
}, | ||
{ | ||
username: "ckerr", | ||
name: "Charles Kerr", | ||
avatarURL: "https://avatars.githubusercontent.com/u/70381?v=4", | ||
prs: 31, | ||
contributions: 1, | ||
bio: "This is the story all about how my life got flipped turned upside down, and I'd like to take a minute just sit right there, I'll tell you how I became the prince of a town called Bel-Air.", | ||
activity: "high", | ||
prVelocity: 67, | ||
}, | ||
{ | ||
username: "JeanMeche", | ||
name: "Matthieu Riegler", | ||
avatarURL: "https://avatars.githubusercontent.com/u/1300985?v=4", | ||
prs: 31, | ||
contributions: 1, | ||
bio: "This is the story all about how my life got flipped turned upside down, and I'd like to take a minute just sit right there, I'll tell you how I became the prince of a town called Bel-Air.", | ||
activity: "high", | ||
prVelocity: 67, | ||
}, | ||
{ | ||
username: "annacmc", | ||
name: "Anna McPhee", | ||
avatarURL: "https://avatars.githubusercontent.com/u/30754158?v=4", | ||
prs: 31, | ||
contributions: 1, | ||
bio: "This is the story all about how my life got flipped turned upside down, and I'd like to take a minute just sit right there, I'll tell you how I became the prince of a town called Bel-Air.", | ||
activity: "high", | ||
prVelocity: 67, | ||
}, | ||
] as const; |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.