Add Quotation Carousel (#36)

Made without visx, because that was easier.

Closes #10.

Co-authored-by: Amy Wang <a258wang@csclub.uwaterloo.ca>
Reviewed-on: #36
Reviewed-by: j285he <j285he@localhost>
histogram-component
Amy Wang 5 months ago
parent 9cd5c158e7
commit a2dbcb90c6
  1. 124
      components/QuotationCarousel.module.css
  2. 130
      components/QuotationCarousel.tsx
  3. 12
      data/mocks.ts
  4. 5
      pages/_app.css
  5. 8
      pages/playground.module.css
  6. 22
      pages/playground.tsx

@ -0,0 +1,124 @@
.carousel {
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
gap: calc(8rem / 16);
}
.circle {
position: absolute;
top: 30%;
right: 52%;
z-index: -1;
background-color: var(--tertiary-background);
clip-path: circle();
}
.right.circle {
top: unset;
right: unset;
bottom: 30%;
left: 52%;
}
.carouselButton {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: calc(16rem / 16);
height: min-content;
background: none;
border: none;
cursor: pointer;
}
.arrow {
position: relative;
width: calc(20rem / 16);
height: calc(40rem / 16);
transition: 0.2s;
}
.previous.arrow {
transform: rotate(180deg);
}
.carouselButton:hover > .arrow {
translate: calc(4rem / 16);
}
.carouselButton:hover > .previous.arrow {
translate: calc(-4rem / 16);
}
.card {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: stretch;
gap: calc(16rem / 16);
min-height: inherit;
height: 100%;
width: 100%;
padding: calc(30rem / 16);
background-color: var(--translucent-accent);
border: calc(2rem / 16) solid var(--primary-text);
border-radius: calc(12rem / 16);
box-shadow: 0 calc(1rem / 16) calc(10rem / 16) var(--primary-accent);
}
.card ul {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
width: 100%;
margin: 0;
padding: 0;
flex-grow: 1;
}
.card li {
position: absolute;
left: 0;
right: 0;
margin: 0;
padding: 0;
list-style: none;
visibility: visible;
opacity: 1;
transition: 0.1s;
}
.card li.hidden {
visibility: hidden;
opacity: 0;
}
.card p {
margin: 0 calc(16rem / 16);
font-weight: bold;
text-align: center;
}
.quotationMark {
width: calc(20rem / 16);
height: calc(20rem / 16);
}
.right.quotationMark {
transform: rotate(180deg);
align-self: end;
}

@ -0,0 +1,130 @@
import React, { useState } from "react";
import { Color } from "utils/Color";
import styles from "./QuotationCarousel.module.css";
interface QuotationCarouselProps {
data: string[];
/** Width of the entire carousel including the buttons, in px. */
width?: number;
/** Minimum height of the carousel, in px. */
height?: number;
/** Diameter of the background circles, in px. Set to 0 for no circles. */
circleDiameter?: number;
className?: string;
}
interface CarouselButtonProps {
onClick: () => void;
isPrevious?: boolean;
}
export function QuotationCarousel(props: QuotationCarouselProps) {
const {
data,
width = 600,
height = 100,
circleDiameter = 120,
className,
} = props;
const [activeIdx, setActiveIdx] = useState(0);
function showNextCard() {
setActiveIdx((activeIdx + 1) % data.length);
}
function showPreviousCard() {
setActiveIdx((activeIdx - 1 + data.length) % data.length);
}
return (
<section
className={
className ? `${className} ${styles.carousel}` : styles.carousel
}
style={{ width: `${width / 16}rem`, minHeight: `${height / 16}rem` }}
>
<Circle className={styles.circle} diameter={circleDiameter} />
<Circle
className={`${styles.right} ${styles.circle}`}
diameter={circleDiameter}
/>
<CarouselButton onClick={showPreviousCard} isPrevious />
<div className={styles.card}>
<QuotationMark className={styles.quotationMark} />
<ul>
{data.map((quote, idx) => (
<li key={idx} className={idx !== activeIdx ? styles.hidden : ""}>
<p>{quote}</p>
</li>
))}
</ul>
<QuotationMark className={`${styles.right} ${styles.quotationMark}`} />
</div>
<CarouselButton onClick={showNextCard} />
</section>
);
}
function Circle({
className,
diameter,
}: {
className: string;
diameter: number;
}) {
return (
<div
className={className}
aria-hidden
style={{
width: `${diameter / 16}rem`,
height: `${diameter / 16}rem`,
}}
/>
);
}
function CarouselButton({ isPrevious, onClick }: CarouselButtonProps) {
return (
<button className={styles.carouselButton} onClick={onClick}>
<svg
className={
isPrevious ? `${styles.previous} ${styles.arrow}` : styles.arrow
}
width="39"
height="72"
viewBox="0 0 39 72"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4 4L34.4206 35.804C35.2926 36.7157 35.2597 38.1619 34.3471 39.0329L4 68"
stroke={Color.primaryAccentLighter}
strokeWidth="4"
strokeLinecap="round"
/>
</svg>
</button>
);
}
function QuotationMark({ className }: { className: string }) {
return (
<svg
className={className}
aria-hidden
width="68"
height="56"
viewBox="0 0 68 56"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M21.34 0.855375L31.1131 9.91768C28.9808 11.9315 27.0262 13.9454 25.2492 15.9592C23.5908 17.9731 22.2877 19.9869 21.34 22.0008C20.3923 24.0146 19.9185 25.9692 19.9185 27.8646C19.9185 29.4046 20.3331 30.7077 21.1623 31.7738C21.9915 32.84 22.9392 33.8469 24.0054 34.7946C25.0715 35.7423 26.0192 36.8677 26.8485 38.1708C27.6777 39.4738 28.0923 41.1323 28.0923 43.1461C28.0923 46.5815 26.8485 49.5431 24.3608 52.0308C21.9915 54.5185 18.8523 55.7623 14.9431 55.7623C11.1523 55.7623 7.71693 54.2223 4.63693 51.1423C1.67539 48.0623 0.194616 44.1531 0.194616 39.4146C0.194616 35.6238 0.964617 31.7146 2.50462 27.6869C4.16308 23.5408 6.53231 19.2169 9.61231 14.7154C12.6923 10.2138 16.6015 5.59383 21.34 0.855375ZM57.7669 0.855375L67.54 9.91768C65.4077 11.9315 63.4531 13.9454 61.6762 15.9592C60.0177 17.9731 58.7146 19.9869 57.7669 22.0008C56.8192 24.0146 56.3454 25.9692 56.3454 27.8646C56.3454 29.4046 56.76 30.7077 57.5892 31.7738C58.4185 32.84 59.3662 33.8469 60.4323 34.7946C61.4985 35.7423 62.4462 36.8677 63.2754 38.1708C64.1046 39.4738 64.5192 41.1323 64.5192 43.1461C64.5192 46.5815 63.2754 49.5431 60.7877 52.0308C58.4185 54.5185 55.2792 55.7623 51.37 55.7623C47.5792 55.7623 44.1439 54.2223 41.0639 51.1423C38.1023 48.0623 36.6215 44.1531 36.6215 39.4146C36.6215 35.6238 37.3915 31.7146 38.9315 27.6869C40.59 23.5408 42.9592 19.2169 46.0392 14.7154C49.1192 10.2138 53.0285 5.59383 57.7669 0.855375Z"
fill={Color.primaryText}
/>
</svg>
);
}

@ -64,3 +64,15 @@ export const moreMockCategoricalData = [
{ key: "Ada", value: 2.21 },
{ key: "Dart", value: 2.21 },
];
export const mockQuoteData = [
"The quick brown fox jumps over the lazy dog.",
"Sphinx of black quartz, judge my vow!",
"Pack my box with five dozen liquor jugs.",
];
export const mockQuoteDataLong = [
"Here, have some quotes of varying lengths, and see how they look.",
"Hello, world!",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla in enim neque. Sed sit amet convallis tellus. Integer condimentum a felis id gravida. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nullam metus libero, sagittis in consectetur in, scelerisque sed sapien. Nullam ut feugiat sapien. Praesent dictum ac ipsum ac lacinia.",
];

@ -20,6 +20,7 @@ body {
--light--primary-accent-light: var(--orange);
--light--primary-accent-lighter: #FFBC9F;
--light--secondary-accent: #E55F98;
--light--translucent-accent: rgba(255, 231, 231, 0.75);
--light--secondary-accent-light: #FEA0C8;
--light--primary-heading: #D02B53;
--light--primary-text: #483B35;
@ -37,6 +38,7 @@ body {
--dark--primary-accent-lighter: var(--lighter-pink);
--dark--secondary-accent: var(--orange);
--dark--secondary-accent-light: var(--light-orange);
--dark--translucent-accent: rgba(239, 131, 157, 0.75);
--dark--primary-heading: #FFC48D;
--dark--secondary-heading: var(--pink);
--dark--link: var(--pink);
@ -54,6 +56,7 @@ body {
--primary-accent-lighter: var(--dark--primary-accent-lighter);
--secondary-accent: var(--dark--secondary-accent);
--secondary-accent-light: var(--dark--secondary-accent-light);
--translucent-accent: var(--dark--translucent-accent);
--primary-heading: var(--dark--primary-heading);
--secondary-heading: var(--dark--secondary-heading);
--link: var(--dark--link);
@ -117,4 +120,4 @@ p {
--card-background: var(--dark--card-background);
--label: var(--dark--label);
}
}
}

@ -5,3 +5,11 @@
.barGraphDemo {
border: calc(1rem / 16) solid black;
}
.quotationCarouselDemo {
display: flex;
flex-direction: column;
align-items: center;
gap: calc(48rem / 16);
margin: calc(32rem / 16);
}

@ -1,7 +1,14 @@
import { BarGraphHorizontal, BarGraphVertical } from "components/BarGraph";
import { mockCategoricalData, moreMockCategoricalData } from "data/mocks";
import {
mockCategoricalData,
moreMockCategoricalData,
mockQuoteData,
mockQuoteDataLong,
} from "data/mocks";
import React from "react";
import { QuotationCarousel } from "@/components/QuotationCarousel";
import { ColorPalette } from "../components/ColorPalette";
import { WordCloud } from "../components/WordCloud";
@ -59,6 +66,19 @@ export default function Home() {
value: word.value,
}))}
/>
<h2>
<code>{"<QuotationCarousel />"}</code>
</h2>
<div className={styles.quotationCarouselDemo}>
<QuotationCarousel data={mockQuoteData} circleDiameter={0} />
<QuotationCarousel
data={mockQuoteDataLong}
width={800}
height={160}
circleDiameter={180}
/>
</div>
</div>
);
}

Loading…
Cancel
Save