diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/cards/constants.ts b/app/components/Home/components/Section/components/SectionHero/components/Carousel/cards/constants.ts new file mode 100644 index 0000000..0c25329 --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/cards/constants.ts @@ -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[] = [ + { + text: MDX.ShareUsageAndJoinAdvisoryPanel({}), + }, + { + text: MDX.LearnToAnalyzeData({}), + }, +]; diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/carousel.styles.ts b/app/components/Home/components/Section/components/SectionHero/components/Carousel/carousel.styles.ts new file mode 100644 index 0000000..c295152 --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/carousel.styles.ts @@ -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; +`; diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/carousel.tsx b/app/components/Home/components/Section/components/SectionHero/components/Carousel/carousel.tsx new file mode 100644 index 0000000..1230623 --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/carousel.tsx @@ -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 ( + + + onSetSwipeAction(SWIPE_ACTION.SWIPE_BACKWARD)} + swipeAction={SWIPE_ACTION.SWIPE_BACKWARD} + /> + + onSetSwipeAction(SWIPE_ACTION.SWIPE_FORWARD)} + swipeAction={SWIPE_ACTION.SWIPE_FORWARD} + /> + + + + ); +}; diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/common/constants.ts b/app/components/Home/components/Section/components/SectionHero/components/Carousel/common/constants.ts new file mode 100644 index 0000000..450f25b --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/common/constants.ts @@ -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; diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/common/utils.ts b/app/components/Home/components/Section/components/SectionHero/components/Carousel/common/utils.ts new file mode 100644 index 0000000..2c2250a --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/common/utils.ts @@ -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; +} diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Arrow/arrow.styles.ts b/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Arrow/arrow.styles.ts new file mode 100644 index 0000000..7d27298 --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Arrow/arrow.styles.ts @@ -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", +})` + & { + 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; + `} +`; diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Arrow/arrow.tsx b/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Arrow/arrow.tsx new file mode 100644 index 0000000..7438289 --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Arrow/arrow.tsx @@ -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 ( + + + + ); +}; diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Cards/cards.styles.ts b/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Cards/cards.styles.ts new file mode 100644 index 0000000..8175a27 --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Cards/cards.styles.ts @@ -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")` + 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; +`; diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Cards/cards.tsx b/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Cards/cards.tsx new file mode 100644 index 0000000..b475bff --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Cards/cards.tsx @@ -0,0 +1,32 @@ +import { CardProps } from "@databiosphere/findable-ui/lib/components/common/Card/card"; +import { RoundedPaper } from "@databiosphere/findable-ui/lib/components/common/Paper/paper.styles"; +import { Fragment } from "react"; +import { getCardPosition } from "../../common/utils"; +import { Card, CardContent, CardPositioner, CardSection } from "./cards.styles"; + +export interface CardsProps { + activeIndex: number; + cards: CardProps[]; +} + +export const Cards = ({ activeIndex, cards }: CardsProps): JSX.Element => { + const lastIndex = cards.length - 1; + return ( + + {cards.map(({ text }, c) => { + return ( + + + + {text} + + + + ); + })} + + ); +}; diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/content/index.tsx b/app/components/Home/components/Section/components/SectionHero/components/Carousel/content/index.tsx new file mode 100644 index 0000000..f2b2a8a --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/content/index.tsx @@ -0,0 +1,2 @@ +export { default as LearnToAnalyzeData } from "./learnToAnalyzeData.mdx"; +export { default as ShareUsageAndJoinAdvisoryPanel } from "./shareUsageAndJoinAdvisoryPanel.mdx"; diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/content/learnToAnalyzeData.mdx b/app/components/Home/components/Section/components/SectionHero/components/Carousel/content/learnToAnalyzeData.mdx new file mode 100644 index 0000000..85e58a0 --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/content/learnToAnalyzeData.mdx @@ -0,0 +1,14 @@ +### Learn how to analyze your data with Galaxy + +The BRC Analytics platform makes heavy use of the Galaxy platform for data analysis. To learn how to use Galaxy or to improve your existing skills, enroll in the [Galaxy Academy 2024](https://training.galaxyproject.org/training-material/events/galaxy-academy-2024.html), a global online training event taking place October 7 - 11, 2024. + + + + + diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/content/shareUsageAndJoinAdvisoryPanel.mdx b/app/components/Home/components/Section/components/SectionHero/components/Carousel/content/shareUsageAndJoinAdvisoryPanel.mdx new file mode 100644 index 0000000..30f9aed --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/content/shareUsageAndJoinAdvisoryPanel.mdx @@ -0,0 +1,10 @@ +### Share your usage and join our advisory panel + +BRC Analytics is actively evolving to provide comprehensive data access and analytical tools for all 785 eukaryotic pathogens, host taxa, and vectors previously supported by VEuPathDB. We need your help to improve—share your usage patterns and join our design advisory panel to help shape the future of the platform. + + + + diff --git a/app/components/Home/components/Section/components/SectionHero/components/Carousel/hooks/useInteractiveCarousel.ts b/app/components/Home/components/Section/components/SectionHero/components/Carousel/hooks/useInteractiveCarousel.ts new file mode 100644 index 0000000..1d22cc6 --- /dev/null +++ b/app/components/Home/components/Section/components/SectionHero/components/Carousel/hooks/useInteractiveCarousel.ts @@ -0,0 +1,50 @@ +import { CardProps } from "@databiosphere/findable-ui/lib/components/common/Card/card"; +import { useMemo } from "react"; +import { + UseSwipeInteraction, + useSwipeInteraction, +} from "../../../../../../../../../hooks/useSwipeInteraction/useSwipeInteraction"; +import { CAROUSEL_CARDS } from "../cards/constants"; + +export interface UseInteractiveCarousel { + activeIndex: UseSwipeInteraction["activeIndex"]; + interactiveAction?: UseSwipeInteraction["interactiveAction"]; + interactiveCards: CardProps[]; + interactiveIndexes: number[]; + onSetActiveIndex: UseSwipeInteraction["onSetActiveIndex"]; + onSetSwipeAction: UseSwipeInteraction["onSetSwipeAction"]; +} + +/** + * Facilitates interaction capabilities for the carousel. + * @returns carousel cards, interactive indexes, and interactive actions. + */ +export function useInteractiveCarousel(): UseInteractiveCarousel { + // Raw carousel cards. + const carouselCards = CAROUSEL_CARDS; + // Get the interactive indexes. + const interactiveIndexes = useMemo( + () => buildInteractiveIndexes(carouselCards), + [carouselCards] + ); + // Get the active index and interactive actions. + const swipeInteraction = useSwipeInteraction( + interactiveIndexes.length, + true, + 12000 + ); + return { + interactiveCards: carouselCards, + interactiveIndexes, + ...swipeInteraction, + }; +} + +/** + * Returns array of interactive indexes. + * @param cards - Cards. + * @returns a list of indexes that are interactive. + */ +function buildInteractiveIndexes(cards: CardProps[]): number[] { + return [...Array(cards.length).keys()]; +} diff --git a/app/components/Home/components/Section/components/SectionHero/components/Hero/hero.tsx b/app/components/Home/components/Section/components/SectionHero/components/Hero/hero.tsx index 17fd3fe..c56714d 100644 --- a/app/components/Home/components/Section/components/SectionHero/components/Hero/hero.tsx +++ b/app/components/Home/components/Section/components/SectionHero/components/Hero/hero.tsx @@ -8,7 +8,6 @@ import { Height, } from "../../../../../../../Layout/components/Hero/common/entities"; import { - getElementHref, getFillUrl, getViewBox, } from "../../../../../../../Layout/components/Hero/common/utils"; @@ -27,7 +26,7 @@ export interface HeroProps { export const Hero = ({ gridSize = GRID_SIZE, - height = GRID_SIZE * 3, + height = gridSize * 3, }: HeroProps): JSX.Element => { return ( - {[ - ELEMENT_ID.PATTERN_SMOKE_RECT, - ELEMENT_ID.PATTERN_SMOKE_CIRCLE, - // ELEMENT_ID.PATTERN_BLUE_RECT, - // ELEMENT_ID.PATTERN_YELLOW_RECT, - ].map((elementId) => ( - - - - ))} - {/**/} - + {[ELEMENT_ID.PATTERN_SMOKE_RECT, ELEMENT_ID.PATTERN_SMOKE_CIRCLE].map( + (elementId) => ( + + + + ) + )} ); }; diff --git a/app/components/Home/components/Section/components/SectionHero/sectionHero.styles.ts b/app/components/Home/components/Section/components/SectionHero/sectionHero.styles.ts index d2a3fc9..93e233f 100644 --- a/app/components/Home/components/Section/components/SectionHero/sectionHero.styles.ts +++ b/app/components/Home/components/Section/components/SectionHero/sectionHero.styles.ts @@ -1,20 +1,19 @@ -import { - mediaDesktopSmallUp, - mediaTabletUp, -} from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints"; +import { mediaDesktopSmallUp } from "@databiosphere/findable-ui/lib/styles/common/mixins/breakpoints"; import { inkLight, smokeLightest, } from "@databiosphere/findable-ui/lib/styles/common/mixins/colors"; -import { textBodyLarge400 } from "@databiosphere/findable-ui/lib/styles/common/mixins/fonts"; +import { textBodyLarge4002Lines } from "@databiosphere/findable-ui/lib/styles/common/mixins/fonts"; +import { black } from "@databiosphere/findable-ui/lib/theme/common/palette"; import styled from "@emotion/styled"; +import { Section } from "../../../../../common/Section/section"; import { section, sectionGrid, sectionLayout, } from "../../../../../Layout/components/AppLayout/components/Section/section.styles"; -export const Section = styled.section` +export const StyledSection = styled(Section)` ${section}; background-color: ${smokeLightest}; overflow: hidden; @@ -25,68 +24,56 @@ export const Section = styled.section` export const SectionLayout = styled.div` ${sectionLayout}; ${sectionGrid}; - align-content: flex-end; - min-height: 400px; - padding: 56px 16px; + align-content: flex-start; + gap: 56px 16px; + justify-items: center; + padding: 112px 16px; + + ${mediaDesktopSmallUp} { + gap: 8px 16px; + justify-items: unset; + } `; export const Headline = styled.div` + display: grid; + gap: 8px 0; grid-column: 1 / -1; + text-align: center; + max-width: 560px; ${mediaDesktopSmallUp} { - grid-column: 1 / 8; + grid-column: 1 / span 6; + text-align: left; } `; export const Head = styled.h1` - font-family: "Inter Tight", sans-serif; - font-size: 64px; + color: ${black}; + font-family: "Inter", sans-serif; + font-size: 48px; font-weight: 500; - letter-spacing: -0.4px; - line-height: 72px; + letter-spacing: -1.4px; + line-height: 56px; margin: 0; - - ${mediaTabletUp} { - span { - display: block; - } - } `; export const SubHeadline = styled.div` - grid-column: 1 / -1; - - ${mediaDesktopSmallUp} { - grid-column: 8 / -1; - justify-self: flex-end; - } -`; - -export const SubHeadlinePositioner = styled.div` display: grid; - gap: 20px; - max-width: 392px; + gap: 16px; + justify-items: center; .MuiButton-root { - justify-self: flex-start; + text-transform: none; + } + + ${mediaDesktopSmallUp} { + justify-items: flex-start; } `; export const Subhead = styled.h2` - ${textBodyLarge400}; + ${textBodyLarge4002Lines}; color: ${inkLight}; margin: 0; - - span { - display: block; - margin: 8px 0; - - &:first-of-type { - margin-top: 0; - } - - &:last-of-type { - margin-bottom: 0; - } - } `; diff --git a/app/components/Home/components/Section/components/SectionHero/sectionHero.tsx b/app/components/Home/components/Section/components/SectionHero/sectionHero.tsx index a098c2a..8cb8635 100644 --- a/app/components/Home/components/Section/components/SectionHero/sectionHero.tsx +++ b/app/components/Home/components/Section/components/SectionHero/sectionHero.tsx @@ -1,53 +1,49 @@ -import { ANCHOR_TARGET } from "@databiosphere/findable-ui/lib/components/Links/common/entities"; import { Button } from "@mui/material"; +import { Fragment } from "react"; +import { ROUTES } from "../../../../../../../routes/constants"; +import { calculateGridSize } from "../../../../../Layout/components/Hero/common/utils"; +import { Carousel } from "./components/Carousel/carousel"; import { Hero } from "./components/Hero/hero"; import { Head, Headline, - Section, SectionLayout, + StyledSection, Subhead, SubHeadline, - SubHeadlinePositioner, } from "./sectionHero.styles"; export const SectionHero = (): JSX.Element => { return ( -
- - - - - Analytics for - pathogen, host, - and vector data - - - - - - - BRC Analytics is under active development. This site will - provide data access and analytical tools for all 785 eukaryotic - pathogens, host taxa, and vectors previously served by - VEuPathDB. However, we cannot do this alone. - - - Use the button below to tell us about your usage patterns and - enroll into our design advisory panel. - - - - - - -
+ + {(height): JSX.Element => ( + + + + + + Analytics for pathogen, + host, and vector data + + + + Comprehensive tools for exploring and interpreting genomic + annotations and functional insights into disease-causing + organisms and their carriers + + + + + + + + )} + ); }; diff --git a/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/components/Hero/hero.tsx b/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/components/Hero/hero.tsx index b418239..786dbe7 100644 --- a/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/components/Hero/hero.tsx +++ b/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/components/Hero/hero.tsx @@ -1,11 +1,7 @@ import { Fragment } from "react"; import { FILL, GRID_SIZE } from "../../../../../../../Hero/common/constants"; import { ELEMENT_ID, Height } from "../../../../../../../Hero/common/entities"; -import { - getElementHref, - getFillUrl, - getViewBox, -} from "../../../../../../../Hero/common/utils"; +import { getFillUrl, getViewBox } from "../../../../../../../Hero/common/utils"; import { CoralPinkCircle } from "../../../../../../../Hero/components/Defs/CoralPinkCircle/coralPinkCircle"; import { SmokeCircle } from "../../../../../../../Hero/components/Defs/SmokeCircle/smokeCircle"; import { SmokeRect } from "../../../../../../../Hero/components/Defs/SmokeRect/smokeRect"; @@ -19,7 +15,7 @@ export interface HeroProps { export const Hero = ({ gridSize = GRID_SIZE, - height = GRID_SIZE * 1.5, + height = gridSize * 1.5, }: HeroProps): JSX.Element => { return ( - {[ - ELEMENT_ID.PATTERN_SMOKE_RECT, - ELEMENT_ID.PATTERN_YELLOW_RECT, - ELEMENT_ID.PATTERN_SMOKE_CIRCLE, - ].map((elementId) => ( - - - - ))} - + {[ELEMENT_ID.PATTERN_SMOKE_RECT, ELEMENT_ID.PATTERN_SMOKE_CIRCLE].map( + (elementId) => ( + + + + ) + )} ); }; diff --git a/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/sectionHero.styles.ts b/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/sectionHero.styles.ts index 5e97aad..0b33942 100644 --- a/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/sectionHero.styles.ts +++ b/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/sectionHero.styles.ts @@ -5,9 +5,10 @@ import { } from "@databiosphere/findable-ui/lib/styles/common/mixins/colors"; import { textBodyLarge400 } from "@databiosphere/findable-ui/lib/styles/common/mixins/fonts"; import styled from "@emotion/styled"; +import { Section } from "../../../../../../../common/Section/section"; import { section, sectionGrid, sectionLayout } from "../../section.styles"; -export const Section = styled.section` +export const StyledSection = styled(Section)` ${section}; background-color: ${smokeLightest}; overflow: hidden; @@ -18,7 +19,8 @@ export const Section = styled.section` export const SectionLayout = styled.div` ${sectionLayout}; ${sectionGrid}; - min-height: 184px; + align-content: flex-start; + min-height: 152px; padding: 56px 16px; `; diff --git a/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/sectionHero.tsx b/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/sectionHero.tsx index aff6432..aab53e7 100644 --- a/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/sectionHero.tsx +++ b/app/components/Layout/components/AppLayout/components/Section/components/SectionHero/sectionHero.tsx @@ -2,13 +2,14 @@ import { Breadcrumb, Breadcrumbs, } from "@databiosphere/findable-ui/lib/components/common/Breadcrumbs/breadcrumbs"; -import { ReactNode } from "react"; +import { Fragment, ReactNode } from "react"; +import { calculateGridSize } from "../../../../../Hero/common/utils"; import { Hero } from "./components/Hero/hero"; import { Head, Headline, - Section, SectionLayout, + StyledSection, Subhead, SubHeadline, } from "./sectionHero.styles"; @@ -25,19 +26,23 @@ export const SectionHero = ({ subHead, }: SectionHeroProps): JSX.Element => { return ( -
- - - - - {head} - - {subHead && ( - - {subHead} - - )} - -
+ + {(height): JSX.Element => ( + + + + + + {head} + + {subHead && ( + + {subHead} + + )} + + + )} + ); }; diff --git a/app/components/Layout/components/Hero/common/utils.ts b/app/components/Layout/components/Hero/common/utils.ts index 55c2284..a5c2dff 100644 --- a/app/components/Layout/components/Hero/common/utils.ts +++ b/app/components/Layout/components/Hero/common/utils.ts @@ -1,4 +1,10 @@ -import { SHAPE_HEIGHT, SHAPE_WIDTH, X_POSITION, Y_POSITION } from "./constants"; +import { + GRID_SIZE, + SHAPE_HEIGHT, + SHAPE_WIDTH, + X_POSITION, + Y_POSITION, +} from "./constants"; /** * Returns the path for the animateMotion element of the blue rectangle. @@ -63,6 +69,22 @@ export function calculateCircleYPosition( return gridSize / 2 - (gridSize / 2) * Math.sin(degreesToRadians(angle)); } +/** + * Calculates the grid size based on the height and count. + * @param height - Section height. + * @param patternCount - Pattern count; vertical repeat of grid pattern. + * @returns grid size. + */ +export function calculateGridSize( + height = GRID_SIZE * 2, + patternCount?: number +): number { + if (height <= GRID_SIZE * 2) { + return height / (patternCount || 2); + } + return height / (patternCount || 3); +} + /** * Returns radians from degrees. * @param angle - Angle. diff --git a/app/components/common/Bullets/bullets.styles.ts b/app/components/common/Bullets/bullets.styles.ts index 05d7967..723925e 100644 --- a/app/components/common/Bullets/bullets.styles.ts +++ b/app/components/common/Bullets/bullets.styles.ts @@ -1,7 +1,5 @@ -import { - primaryMain, - smokeMain, -} from "@databiosphere/findable-ui/lib/styles/common/mixins/colors"; +import { Dot } from "@databiosphere/findable-ui/lib/components/common/Dot/dot"; +import { smokeMain } from "@databiosphere/findable-ui/lib/styles/common/mixins/colors"; import { css } from "@emotion/react"; import styled from "@emotion/styled"; import { ButtonBase as MButtonBase } from "@mui/material"; @@ -12,23 +10,23 @@ interface Props { export const Bullets = styled.div` display: flex; - gap: 8px; justify-content: center; `; -export const Bullet = styled(MButtonBase, { +export const Bullet = styled(MButtonBase)` + padding: 4px; +`; + +export const StyledDot = styled(Dot, { shouldForwardProp: (props) => props !== "isActive", })` background-color: ${smokeMain}; - border-radius: 50%; - cursor: pointer; - display: inline-block; height: 6px; width: 6px; - ${(props) => - props.isActive && + ${({ isActive }) => + isActive && css` - background-color: ${primaryMain(props)}; + background-color: #fc5e60; `} `; diff --git a/app/components/common/Bullets/bullets.tsx b/app/components/common/Bullets/bullets.tsx index 5290bbd..a4b081e 100644 --- a/app/components/common/Bullets/bullets.tsx +++ b/app/components/common/Bullets/bullets.tsx @@ -1,4 +1,4 @@ -import { Bullet, Bullets as SectionBullets } from "./bullets.styles"; +import { Bullet, Bullets as SectionBullets, StyledDot } from "./bullets.styles"; interface BulletsProps { activeBullet: number; @@ -18,14 +18,15 @@ export const Bullets = ({ {bullets.map((bullet) => ( { onBullet(bullet); }} onKeyDown={(): void => { onBullet(bullet); }} - /> + > + + ))} ); diff --git a/app/components/common/Section/section.styles.ts b/app/components/common/Section/section.styles.ts new file mode 100644 index 0000000..acd2b39 --- /dev/null +++ b/app/components/common/Section/section.styles.ts @@ -0,0 +1,5 @@ +import styled from "@emotion/styled"; + +export const StyledSection = styled.section` + width: 100%; +`; diff --git a/app/components/common/Section/section.tsx b/app/components/common/Section/section.tsx new file mode 100644 index 0000000..e2659e9 --- /dev/null +++ b/app/components/common/Section/section.tsx @@ -0,0 +1,22 @@ +import { + getBorderBoxSizeHeight, + useResizeObserver, +} from "@databiosphere/findable-ui/lib/hooks/useResizeObserver"; +import { useRef } from "react"; +import { StyledSection } from "./section.styles"; + +export interface SectionProps { + children: (height?: number) => JSX.Element; + className?: string; +} + +export const Section = ({ children, className }: SectionProps): JSX.Element => { + const sectionRef = useRef(null); + const { height } = + useResizeObserver(sectionRef, getBorderBoxSizeHeight) || {}; + return ( + + {children?.(height)} + + ); +}; diff --git a/app/hooks/useSwipeInteraction/useSwipeInteraction.tsx b/app/hooks/useSwipeInteraction/useSwipeInteraction.tsx index 4dcc009..c4539b9 100644 --- a/app/hooks/useSwipeInteraction/useSwipeInteraction.tsx +++ b/app/hooks/useSwipeInteraction/useSwipeInteraction.tsx @@ -14,6 +14,8 @@ import { SwipeAction, SwipeCoordinates, SWIPE_ACTION } from "./common/entities"; export interface InteractiveAction { onMouseDown: (mouseEvent: MouseEvent) => void; + onMouseEnter: () => void; + onMouseLeave: () => void; onMouseUp: (mouseEvent: MouseEvent) => void; onTouchEnd: (touchEvent: TouchEvent) => void; onTouchMove: (touchEvent: TouchEvent) => void; @@ -39,6 +41,7 @@ export function useSwipeInteraction( swipeEnabled = true, swipeDelay = 0 ): UseSwipeInteraction { + const [interactiveDelay, setInteractiveDelay] = useState(swipeDelay); const swipeStartCoordsRef = useRef( DEFAULT_SWIPE_COORDINATES ); @@ -52,6 +55,14 @@ export function useSwipeInteraction( swipeStartCoordsRef.current = getMouseCoords(mouseEvent); }, []); + const onMouseEnter = useCallback((): void => { + setInteractiveDelay(0); + }, []); + + const onMouseLeave = useCallback((): void => { + setInteractiveDelay(swipeDelay); + }, [swipeDelay]); + const onMouseUp = useCallback((mouseEvent: MouseEvent): void => { const mouseStartCoords = swipeStartCoordsRef.current; const mouseEndCoords = getMouseCoords(mouseEvent); @@ -123,12 +134,12 @@ export function useSwipeInteraction( }, [swipeAction, onSwipeToIndex]); useEffect(() => { - if (swipeDelay === 0) return; + if (interactiveDelay === 0) return; const timeout = setTimeout(() => { onSwipeToIndex(1); - }, swipeDelay); + }, interactiveDelay); return () => clearTimeout(timeout); - }, [activeIndex, swipeDelay, onSwipeToIndex]); + }, [activeIndex, interactiveDelay, onSwipeToIndex]); if (!swipeEnabled) { return { @@ -142,6 +153,8 @@ export function useSwipeInteraction( activeIndex, interactiveAction: { onMouseDown, + onMouseEnter, + onMouseLeave, onMouseUp, onTouchEnd, onTouchMove, diff --git a/mdx-components.tsx b/mdx-components.tsx index d50f817..1803365 100644 --- a/mdx-components.tsx +++ b/mdx-components.tsx @@ -1,9 +1,12 @@ import { Link } from "@databiosphere/findable-ui/lib/components/Links/components/Link/link"; import { MDXComponents } from "mdx/types"; +import { CardActions } from "./app/components/Home/components/Section/components/SectionHero/components/Carousel/components/Cards/cards.styles"; export function useMDXComponents(components: MDXComponents): MDXComponents { return { ...components, + CardActions, + Link, a: ({ children, href }) => Link({ label: children, url: href ?? "" }), }; }