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

feat: add carousel to home page (#66) #102

Merged
merged 9 commits into from
Sep 26, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CardProps } from "@databiosphere/findable-ui/lib/components/common/Card/card";
import * as MDX from "../content";

export const CAROUSEL_CARDS: Pick<CardProps, "text">[] = [
{
text: MDX.ShareUsageAndJoinAdvisoryPanel({}),
},
{
text: MDX.LearnToAnalyzeData({}),
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
mediaDesktopSmallUp,
mediaTabletUp,
} from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints";
import styled from "@emotion/styled";
import { Bullets } from "../../../../../../../common/Bullets/bullets";
import {
CAROUSEL_HEIGHT,
CAROUSEL_HEIGHT_SM,
MAX_CARD_WIDTH,
} from "./common/constants";

export const CarouselView = styled.div`
grid-column: 1 / -1;
max-width: ${MAX_CARD_WIDTH}px;
width: 100%;
${mediaDesktopSmallUp} {
grid-column: 7 / -1;
grid-row: 1 / 3;
justify-self: flex-end;
}
`;

export const Carousel = styled.div`
cursor: grab;
height: ${CAROUSEL_HEIGHT_SM}px;
position: relative; /* Positions CardPositioner. */
user-select: none;
&:active {
cursor: grabbing;
}
${mediaTabletUp} {
height: ${CAROUSEL_HEIGHT}px;
}
.MuiIconButton-root {
opacity: 0;
transition: opacity 150ms ease-in-out;
}
&:hover {
> .MuiIconButton-root {
opacity: 1;
}
}
`;

export const StyledBullets = styled(Bullets)`
bottom: 14px;
left: 50%;
position: absolute;
transform: translateX(-50%);
z-index: 100;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { SWIPE_ACTION } from "../../../../../../../../hooks/useSwipeInteraction/common/entities";
import {
Carousel as CarouselCards,
CarouselView,
StyledBullets,
} from "./carousel.styles";
import { Arrow } from "./components/Arrow/arrow";
import { Cards } from "./components/Cards/cards";
import { useInteractiveCarousel } from "./hooks/useInteractiveCarousel";

export const Carousel = (): JSX.Element => {
const {
activeIndex,
interactiveAction,
interactiveCards,
interactiveIndexes,
onSetActiveIndex,
onSetSwipeAction,
} = useInteractiveCarousel();
return (
<CarouselView>
<CarouselCards {...interactiveAction}>
<Arrow
onClick={(): void => onSetSwipeAction(SWIPE_ACTION.SWIPE_BACKWARD)}
swipeAction={SWIPE_ACTION.SWIPE_BACKWARD}
/>
<Cards activeIndex={activeIndex} cards={interactiveCards} />
<Arrow
onClick={(): void => onSetSwipeAction(SWIPE_ACTION.SWIPE_FORWARD)}
swipeAction={SWIPE_ACTION.SWIPE_FORWARD}
/>
<StyledBullets
activeBullet={activeIndex}
bullets={interactiveIndexes}
onBullet={onSetActiveIndex}
/>
</CarouselCards>
</CarouselView>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const CARD_OFFSET_Y = 8;
export const CARD_SCALE_X = 40;
export const MAX_CARD_HEIGHT = 216;
export const MAX_CARD_HEIGHT_SM = 280;
export const MAX_DECK_SIZE = 1; // Currently, deck size is only 1 additional card.
export const MAX_CARD_WIDTH = 504;
export const CAROUSEL_HEIGHT = MAX_CARD_HEIGHT + MAX_DECK_SIZE * CARD_OFFSET_Y;
export const CAROUSEL_HEIGHT_SM =
MAX_CARD_HEIGHT_SM + MAX_DECK_SIZE * CARD_OFFSET_Y;
export const TRANSITION_DELAY = 100;
export const TRANSITION_DURATION = 100;
export const ARROW_OFFSET_Y = (CARD_OFFSET_Y * MAX_DECK_SIZE) / 2;
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { SWIPE_ACTION } from "../../../../../../../../../hooks/useSwipeInteraction/common/entities";
import {
ARROW_OFFSET_Y,
CARD_OFFSET_Y,
CARD_SCALE_X,
MAX_CARD_WIDTH,
MAX_DECK_SIZE,
TRANSITION_DELAY,
TRANSITION_DURATION,
} from "./constants";

/**
* Returns the arrow's transform scaleX and translateY.
* @param swipeAction - Swipe action.
* @returns arrow's transform.
*/
export function getArrowTransform(swipeAction: SWIPE_ACTION): string {
return swipeAction === SWIPE_ACTION.SWIPE_FORWARD
? `translate(24px, calc(${ARROW_OFFSET_Y}px - 50%)) scaleX(-1)`
: `translate(-24px, calc(${ARROW_OFFSET_Y}px - 50%))`;
}

/**
* Returns the carousel card's position in the deck.
* @param index - Card index.
* @param activeIndex - Active index.
* @param lastIndex - Last index.
* @returns card position (position zero to deck size).
*/
export function getCardPosition(
index: number,
activeIndex: number,
lastIndex: number
): number {
const order = index - activeIndex;
if (order >= 0) return order;
// If the order is negative, stack the card to the end of the deck.
// Grab the last (positive) position in the deck and add the card position (index + 1).
return lastIndex - activeIndex + index + 1;
}

/**
* Returns the carousel card's x-axis scale.
* @param cardPosition - Card position.
* @returns card x-axis scale.
*/
export function getCardScaleX(cardPosition: number): string {
if (cardPosition === 0) return "scaleX(1)"; // The active card is scaled to 1.
return `scaleX(${
(MAX_CARD_WIDTH - cardPosition * CARD_SCALE_X) / MAX_CARD_WIDTH
})`;
}

/**
* Returns the carousel card's transform scaleX and translateY.
* @param cardPosition - Card position.
* @returns card transform.
*/
export function getCardTransform(cardPosition: number): string {
return `${getCardScaleX(cardPosition)} ${getCardTranslateY(cardPosition)}`;
}

/**
* Returns the carousel card's transition.
* @param cardPosition - Card position.
* @returns card transition.
*/
export function getCardTransition(cardPosition: number): string {
return `all ${TRANSITION_DURATION}ms ease-in-out ${
cardPosition * TRANSITION_DELAY
}ms, z-index 0ms ${TRANSITION_DELAY}ms`;
}

/**
* Returns the carousel card's y-axis offset.
* @param cardPosition - Card position.
* @returns y-axis offset.
*/
export function getCardTranslateY(cardPosition: number): string {
return `translateY(${(MAX_DECK_SIZE - cardPosition) * CARD_OFFSET_Y}px)`;
}

/**
* Returns the carousel card's z-index.
* @param cardPosition - Card position.
* @returns card z-index.
*/
export function getCardZIndex(cardPosition: number): number {
return MAX_DECK_SIZE - cardPosition;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { mediaTabletDown } from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints";
import {
inkMain,
smokeDark,
smokeLightest,
white,
} from "@databiosphere/findable-ui/lib/styles/common/mixins/colors";
import { black08 } from "@databiosphere/findable-ui/lib/theme/common/palette";
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import { IconButton as MIconButton } from "@mui/material";
import {
SwipeAction,
SWIPE_ACTION,
} from "../../../../../../../../../../hooks/useSwipeInteraction/common/entities";
import { MAX_DECK_SIZE } from "../../common/constants";
import { getArrowTransform } from "../../common/utils";

interface Props {
swipeAction: SwipeAction;
}

export const IconButton = styled(MIconButton, {
shouldForwardProp: (props) => props !== "swipeAction",
})<Props>`
& {
background-color: ${white};
border-radius: 50%;
box-shadow: inset 0 0 0 1px ${smokeDark}, 0 1px 0 0 ${black08};
color: ${inkMain};
position: absolute;
top: 50%;
transform: ${({ swipeAction }) => getArrowTransform(swipeAction)};
z-index: ${MAX_DECK_SIZE + 1};
&:hover {
background-color: ${smokeLightest};
}
&:active {
box-shadow: inset 0 0 0 1px ${smokeDark};
}
${mediaTabletDown} {
display: none;
}
}
${({ swipeAction }) =>
swipeAction === SWIPE_ACTION.SWIPE_BACKWARD &&
css`
left: 0;
`}
${({ swipeAction }) =>
swipeAction === SWIPE_ACTION.SWIPE_FORWARD &&
css`
right: 0;
`}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { SouthIcon } from "@databiosphere/findable-ui/lib/components/common/CustomIcon/components/SouthIcon/southIcon";
import { SwipeAction } from "../../../../../../../../../../hooks/useSwipeInteraction/common/entities";
import { IconButton } from "./arrow.styles";

interface ArrowProps {
onClick: () => void;
swipeAction: SwipeAction;
}

export const Arrow = ({ onClick, swipeAction }: ArrowProps): JSX.Element => {
return (
<IconButton
color="secondary"
onClick={onClick}
size="large"
swipeAction={swipeAction}
>
<SouthIcon fontSize="small" />
</IconButton>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
mediaTabletDown,
mediaTabletUp,
} from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints";
import {
inkLight,
smokeMain,
} from "@databiosphere/findable-ui/lib/styles/common/mixins/colors";
import {
textBody500,
textBodyLarge500,
textBodySmall4002Lines,
} from "@databiosphere/findable-ui/lib/styles/common/mixins/fonts";
import { elevation01 } from "@databiosphere/findable-ui/lib/theme/common/shadows";
import styled from "@emotion/styled";
import { Card as MCard } from "@mui/material";
import {
MAX_CARD_HEIGHT,
MAX_CARD_HEIGHT_SM,
MAX_CARD_WIDTH,
} from "../../common/constants";
import {
getCardTransform,
getCardTransition,
getCardZIndex,
} from "../../common/utils";

interface Props {
cardPosition: number;
}

export const CardPositioner = styled("div")<Props>`
display: grid;
height: 100%;
max-height: ${MAX_CARD_HEIGHT_SM}px;
max-width: ${MAX_CARD_WIDTH}px;
position: absolute;
transform: ${({ cardPosition }) => getCardTransform(cardPosition)};
transition: ${({ cardPosition }) => getCardTransition(cardPosition)};
width: 100%;
z-index: ${({ cardPosition }) => getCardZIndex(cardPosition)};
${mediaTabletUp} {
max-height: ${MAX_CARD_HEIGHT}px;
}
`;

export const Card = styled(MCard)`
border: none;
box-shadow: ${elevation01}, inset 0 0 0 1px ${smokeMain};
display: flex;
height: 100%;
width: 100%;
` as typeof MCard;

export const CardSection = styled.div`
display: flex;
flex: 1;
flex-direction: column;
padding: 24px;
`;

export const CardContent = styled.div`
h1,
h2,
h3 {
${textBodyLarge500};
margin: 0;
}
p {
${textBodySmall4002Lines};
color: ${inkLight};
margin: 8px 0;
&:last-of-type {
margin-bottom: 0;
}
}
${mediaTabletDown} {
-webkit-box-orient: vertical;
display: -webkit-box;
-webkit-line-clamp: 10;
overflow: hidden;
}
`;

export const CardActions = styled.div`
${textBody500};
align-items: center;
display: flex;
gap: 16px;
margin-top: 16px;
`;
Loading
Loading