From be83cabecda649b52cb23be0d1f4ec822629b112 Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Wed, 20 Nov 2024 12:14:23 +0100 Subject: [PATCH] =?UTF-8?q?feat(lld):=20=F0=9F=8E=A0=20update=20react-ui?= =?UTF-8?q?=20default=20carousel=20design=20(#8371)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(lld): add arrows to the default carousel * feat(lld): update the default carousel bullets * feat(lld): hide carousel controls for a single slide * fix(lld): carousel styling * chore(lld): improve the carousel story * chore: update change log * chore(lld): add handlers for the ChevronArrow onClick functions * fix(lld): storybook's light theme background * fix(lld): do not set a background color for the carousel * chore: set the `@ledgerhq/ui-shared` change to minor instead of patch --- .changeset/orange-ducks-do.md | 6 ++ libs/ui/packages/react/.storybook/preview.tsx | 6 +- .../layout/Carousel/Carousel.stories.tsx | 16 +++-- .../layout/Carousel/ChevronArrow.tsx | 70 +++++++++++++++++++ .../Carousel/Footer/Pagination/index.tsx | 2 +- .../Carousel/Footer/Pagination/utils.ts | 22 +++--- .../layout/Carousel/Footer/index.tsx | 2 + .../Carousel/Footer/variantContentCard.tsx | 2 - .../src/components/layout/Carousel/index.tsx | 37 ++++++++-- libs/ui/packages/shared/palettes/dark.ts | 2 + libs/ui/packages/shared/palettes/light.ts | 2 + 11 files changed, 137 insertions(+), 30 deletions(-) create mode 100644 .changeset/orange-ducks-do.md create mode 100644 libs/ui/packages/react/src/components/layout/Carousel/ChevronArrow.tsx diff --git a/.changeset/orange-ducks-do.md b/.changeset/orange-ducks-do.md new file mode 100644 index 000000000000..db47b97b6be2 --- /dev/null +++ b/.changeset/orange-ducks-do.md @@ -0,0 +1,6 @@ +--- +"@ledgerhq/react-ui": minor +"@ledgerhq/ui-shared": minor +--- + +Update the react-ui Carousel based on the portfolio content cards design diff --git a/libs/ui/packages/react/.storybook/preview.tsx b/libs/ui/packages/react/.storybook/preview.tsx index 68921f1d6776..95f3efcbf4bd 100644 --- a/libs/ui/packages/react/.storybook/preview.tsx +++ b/libs/ui/packages/react/.storybook/preview.tsx @@ -6,7 +6,7 @@ import { StyleProvider } from "../src/styles"; export const decorators = [ (Story, { globals }) => { const backgrounds = globals?.backgrounds ?? {}; - const theme = backgrounds?.value === palettes.dark.background.main ? "dark" : "light"; + const theme = backgrounds?.value === palettes.dark.background.default ? "dark" : "light"; return ( @@ -29,11 +29,11 @@ export const parameters = { values: [ { name: "light", - value: palettes.light.background.main, + value: palettes.light.background.default, }, { name: "dark", - value: palettes.dark.background.main, + value: palettes.dark.background.default, }, ], }, diff --git a/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx b/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx index fe055f4d9e2e..f5a50a0a9bc4 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx +++ b/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx @@ -1,14 +1,15 @@ +import type { Meta, StoryObj } from "@storybook/react"; import React from "react"; import Carousel from "./"; import { Props } from "./types"; -const CarouselStory = (args: Props) => { - const slides = [...Array(5)].map((_, index) => ( +const CarouselStory = (args: Omit & { children: number }) => { + const slides = Array.from({ length: args.children }, (_, index) => (
@@ -24,16 +25,18 @@ export default { argTypes: { children: { description: "The elements to be displayed.", + control: { type: "range", min: 1, max: 10, step: 1 }, }, variant: { description: "Variant for the carousel.", options: ["default", "content-card"], defaultValue: "default", - control: { type: "select" }, + control: "inline-radio", }, }, args: { variant: "default", + children: 5, }, parameters: { docs: { @@ -42,6 +45,7 @@ export default { }, }, }, -}; + render: CarouselStory, +} satisfies Meta; -export const Default = CarouselStory.bind({}); +export const Default: StoryObj = {}; diff --git a/libs/ui/packages/react/src/components/layout/Carousel/ChevronArrow.tsx b/libs/ui/packages/react/src/components/layout/Carousel/ChevronArrow.tsx new file mode 100644 index 000000000000..ed2f51322e54 --- /dev/null +++ b/libs/ui/packages/react/src/components/layout/Carousel/ChevronArrow.tsx @@ -0,0 +1,70 @@ +import React from "react"; +import styled from "styled-components"; +import { Icons } from "../../../assets"; + +type Props = ContainerProps & { + onClick: () => void; +}; + +export function ChevronArrow(props: Props) { + return ( + + + + ); +} + +type ContainerProps = { + direction: "left" | "right"; +}; + +const ChevronArrowContainer = styled.button` + display: inline-flex; + justify-content: center; + align-items: center; + width: 32px; + height: 32px; + + position: absolute; + top: 50%; + left: ${({ direction }) => (direction === "left" ? "0" : "auto")}; + right: ${({ direction }) => (direction === "left" ? "auto" : "0")}; + z-index: 1; + + --dir: ${({ direction }) => (direction === "left" ? "1" : "-1")}; + scale: var(--dir) 1; + translate: calc(-50% * var(--dir)) -50%; + + background-color: ${({ theme }) => theme.colors.background.default}; // Fake the transparent clip + border-radius: 100%; + border: none; + outline: none; + + ::before { + content: ""; + display: block; + position: absolute; + z-index: -1; + inset: 3px; + background-color: ${({ theme }) => theme.colors.opacityDefault.c05}; + border-color: ${({ theme }) => theme.colors.opacityDefault.c05}; + border-radius: 100%; + border-style: solid; + border-width: 1px; + } + svg { + color: ${({ theme }) => theme.colors.primary.c100}; + } + ::before, + svg { + cursor: pointer; + } + + transition: opacity 0.2s ease-in-out; + opacity: var(--hover-transition); + ::before, + svg { + transition: translate 0.2s ease-in-out; + translate: calc(-50% + 50% * var(--hover-transition)) 0; + } +`; diff --git a/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/index.tsx b/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/index.tsx index db5019e6bab8..62f40352ac8b 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/index.tsx +++ b/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/index.tsx @@ -13,7 +13,7 @@ const Pagination = ({ children, currentIndex }: SubProps) => { return ( {children.map((child, index) => ( - + ))} ); diff --git a/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/utils.ts b/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/utils.ts index 1920f750ffc3..763a629a47c3 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/utils.ts +++ b/libs/ui/packages/react/src/components/layout/Carousel/Footer/Pagination/utils.ts @@ -3,17 +3,17 @@ import { ItemStatus } from "./types"; /** * Returns the status of an indexed item from the carousel index. */ -export const getItemStatus = (itemIndex: number, activeIndex: number) => { - const itemDistanceFromActiveIndex = Math.abs(itemIndex - activeIndex); +export const getItemStatus = (itemIndex: number, activeIndex: number, itemCount: number) => { + const isActive = itemIndex === activeIndex; + if (isActive) { + return ItemStatus.active; + } - switch (itemDistanceFromActiveIndex) { - case 0: - return ItemStatus.active; - case 1: - return ItemStatus.nearby; - case 2: - return ItemStatus.far; - default: - return ItemStatus.none; + const isAdjacent = Math.abs(itemIndex - activeIndex) === 1; + if (isAdjacent) { + return ItemStatus.nearby; } + + const isEdge = itemIndex === 0 || itemIndex === itemCount - 1; + return isEdge ? ItemStatus.far : ItemStatus.nearby; }; diff --git a/libs/ui/packages/react/src/components/layout/Carousel/Footer/index.tsx b/libs/ui/packages/react/src/components/layout/Carousel/Footer/index.tsx index fa04654914a4..f9c809076a7d 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/Footer/index.tsx +++ b/libs/ui/packages/react/src/components/layout/Carousel/Footer/index.tsx @@ -9,6 +9,8 @@ const Footers: { [key in Variant]: FC } = { }; const Footer = (props: SubProps) => { + if (props.children.length === 1) return null; + const Component = Footers[props.variant]; return ; }; diff --git a/libs/ui/packages/react/src/components/layout/Carousel/Footer/variantContentCard.tsx b/libs/ui/packages/react/src/components/layout/Carousel/Footer/variantContentCard.tsx index 879384a7e1ca..a809b5c1674b 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/Footer/variantContentCard.tsx +++ b/libs/ui/packages/react/src/components/layout/Carousel/Footer/variantContentCard.tsx @@ -25,8 +25,6 @@ const FooterArrowContainer = styled.div` `; const FooterContentCard = ({ children, emblaApi, currentIndex, variant }: SubProps) => { - if (children.length === 1) return null; - return ( >` + position: relative; + + --hover-transition: 0; + &:hover { + --hover-transition: 1; + } +`; + /** * This component uses the https://github.com/davidjerleke/embla-carousel library. */ @@ -45,15 +55,27 @@ const Carousel = ({ children, variant = "default" }: Props) => { emblaApi.on("reInit", updateIndex); }, [emblaApi, updateIndex]); + const handleGotoPrevSlide = () => emblaApi?.scrollPrev(); + const handleGotoNextSlide = () => emblaApi?.scrollNext(); + return (
- - - {children.map(child => ( - {child} - ))} - - + + {variant === "default" && children.length > 1 && ( + <> + + + + )} + + + + {children.map(child => ( + {child} + ))} + + +
); }; + export default Carousel; diff --git a/libs/ui/packages/shared/palettes/dark.ts b/libs/ui/packages/shared/palettes/dark.ts index 4d798a8fdc72..8977e93cd00d 100644 --- a/libs/ui/packages/shared/palettes/dark.ts +++ b/libs/ui/packages/shared/palettes/dark.ts @@ -107,8 +107,10 @@ export default { black: "#000000", }, background: { + default: "#131214", main: "#131214", drawer: "#1D1C1F", + card: "#1C1D1F", }, effects: { dropShadow: "rgba(0, 0, 0, 0.48)", diff --git a/libs/ui/packages/shared/palettes/light.ts b/libs/ui/packages/shared/palettes/light.ts index 81a7e796c331..6c78cdad9397 100644 --- a/libs/ui/packages/shared/palettes/light.ts +++ b/libs/ui/packages/shared/palettes/light.ts @@ -107,8 +107,10 @@ export default { black: "#000000", }, background: { + default: "#F9F9F9", main: "#FFFFFF", drawer: "#FFFFFF", + card: "#FFFFFF", }, effects: { dropShadow: "rgba(0, 0, 0, 0.16)",