From 9cd5c158e7a3d75222490d07728e7348f97032a4 Mon Sep 17 00:00:00 2001 From: Shahan Neda Date: Fri, 2 Sep 2022 17:39:46 -0400 Subject: [PATCH 1/2] Sample page and graph wrappers (#32) --- .vscode/settings.json | 3 +- components/ComponentWrapper.module.css | 59 ++++++++++++ components/ComponentWrapper.tsx | 42 ++++++++ components/WordCloud.tsx | 10 +- package.json | 2 +- pages/_app.css | 10 +- pages/_app.tsx | 13 ++- pages/index.tsx | 2 + pages/samplePage.module.css | 5 + pages/samplePage.tsx | 127 +++++++++++++++++++++++++ utils/getWindowDimensions.ts | 35 +++++++ utils/isMobile.ts | 3 + 12 files changed, 303 insertions(+), 8 deletions(-) create mode 100644 components/ComponentWrapper.module.css create mode 100644 components/ComponentWrapper.tsx create mode 100644 pages/samplePage.module.css create mode 100644 pages/samplePage.tsx create mode 100644 utils/getWindowDimensions.ts create mode 100644 utils/isMobile.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 99e58e0..eb98407 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,12 +2,13 @@ "typescript.tsdk": "node_modules/typescript/lib", "eslint.format.enable": true, "eslint.codeActionsOnSave.mode": "all", + "css.lint.validProperties": ["composes"], "css.format.spaceAroundSelectorSeparator": true, "[css]": { "editor.suggest.insertMode": "replace", "gitlens.codeLens.scopes": ["document"], "editor.formatOnSave": true, - "editor.defaultFormatter": "vscode.css-language-features" + "editor.defaultFormatter": "vscode.css-language-features", }, "[javascript]": { "editor.formatOnSave": false, diff --git a/components/ComponentWrapper.module.css b/components/ComponentWrapper.module.css new file mode 100644 index 0000000..4fad333 --- /dev/null +++ b/components/ComponentWrapper.module.css @@ -0,0 +1,59 @@ +.sideWrapperCommon { + background-color: var(--secondary-background); + display: flex; + padding: calc(40rem / 16) calc(50rem / 16); + margin: calc(65rem / 16) 0; + width: 90%; +} + +.wrapperRight { + composes: sideWrapperCommon; + align-self: end; + margin-right: 0; + padding-right: 0; + border-radius: calc(200rem / 16) 0 0 calc(200rem / 16); + flex-direction: row-reverse; + padding-right: calc(50rem / 16); +} + +.wrapperLeft { + composes: sideWrapperCommon; + align-self: start; + margin-left: 0; + padding-left: 0; + border-radius: 0 calc(200rem / 16) calc(200rem / 16) 0; + flex-direction: row; + padding-left: calc(50rem / 16); +} + +.noBackground { + background: none; + align-self: center; +} + +.wrapperCenter { + flex-direction: column; + text-align: center; + gap: calc(25rem / 16); + /* to match the 65px margin with the left/right variant: + add 45px bottom margin, since internal wrapper contributes 20px for the center component + 0px top margin, since h3 contributes 45px and internal wrapper contributes 20px for the center component + */ + margin: 0 0 calc(45rem / 16) 0; + padding: 0 15%; +} + +@media screen and (max-width: 768px) { + .sideWrapperCommon { + margin: auto; + flex-direction: column; + text-align: center; + padding: 0; + border-radius: 0; + width: 100%; + } +} + +.internalWrapper { + padding: calc(20rem / 16); +} \ No newline at end of file diff --git a/components/ComponentWrapper.tsx b/components/ComponentWrapper.tsx new file mode 100644 index 0000000..8893905 --- /dev/null +++ b/components/ComponentWrapper.tsx @@ -0,0 +1,42 @@ +import React from "react"; + +import styles from "./ComponentWrapper.module.css"; + +type AlignOption = "left" | "center" | "right"; + +type ComponentWrapperProps = { + children: React.ReactNode; + heading: string; + bodyText: string; + align?: AlignOption; + noBackground?: boolean; +}; + +export function ComponentWrapper({ + heading, + bodyText, + children, + align = "left", + noBackground = false, +}: ComponentWrapperProps) { + const alignClasses: { [key in AlignOption]: string } = { + left: styles.wrapperLeft, + center: styles.wrapperCenter, + right: styles.wrapperRight, + }; + + return ( +
+
+

{heading}

+

{bodyText}

+
+
{children}
+
+ ); +} diff --git a/components/WordCloud.tsx b/components/WordCloud.tsx index 9591dac..ca295e5 100644 --- a/components/WordCloud.tsx +++ b/components/WordCloud.tsx @@ -197,10 +197,12 @@ const shouldNotRerender = ( nextProps: WordCloudWordsProps ) => { if ( - prevProps.tooltipLeft !== nextProps.tooltipLeft || - prevProps.tooltipTop !== nextProps.tooltipTop || - nextProps.tooltipLeft === undefined || - nextProps.tooltipTop === undefined + // if width changes, rerender, else don't rerender for a tooltip change + prevProps.width === nextProps.width && + (prevProps.tooltipLeft !== nextProps.tooltipLeft || + prevProps.tooltipTop !== nextProps.tooltipTop || + nextProps.tooltipLeft === undefined || + nextProps.tooltipTop === undefined) ) { return true; // do not re-render } diff --git a/package.json b/package.json index 6358840..814c0d7 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,9 @@ "@visx/group": "^2.10.0", "@visx/scale": "^2.2.2", "@visx/shape": "^2.10.0", + "@visx/text": "^2.10.0", "@visx/tooltip": "^2.10.0", "@visx/wordcloud": "^2.10.0", - "@visx/text": "^2.10.0", "next": "12.1.6", "react": "18.1.0", "react-dom": "18.1.0" diff --git a/pages/_app.css b/pages/_app.css index 0c34108..30aea2c 100644 --- a/pages/_app.css +++ b/pages/_app.css @@ -73,7 +73,11 @@ h2 { color: var(--primary-heading); } -h3, +h3 { + color: var(--secondary-heading); + font-size: calc(45rem / 16); +} + h4, h5, h6 { @@ -89,6 +93,10 @@ a:hover { color: var(--link-hover); } +p { + font-size: calc(28rem / 16); +} + @media only screen and (prefers-color-scheme: dark) { body { --primary-background: var(--dark--primary-background); diff --git a/pages/_app.tsx b/pages/_app.tsx index 7803bd0..52d7142 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -1,9 +1,20 @@ import type { AppProps } from "next/app"; +import Head from "next/head"; import React from "react"; import "./_app.css"; import "./font.css"; export default function App({ Component, pageProps }: AppProps): JSX.Element { - return ; + return ( + <> + + + + + + ); } diff --git a/pages/index.tsx b/pages/index.tsx index 4ff1fb7..58dda94 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -5,6 +5,8 @@ export default function Home() { return (

Click here to visit the playground +
+ Click here to visit a sample page

); } diff --git a/pages/samplePage.module.css b/pages/samplePage.module.css new file mode 100644 index 0000000..b50d07b --- /dev/null +++ b/pages/samplePage.module.css @@ -0,0 +1,5 @@ +.page { + display: flex; + flex-direction: column; + justify-content: center; +} \ No newline at end of file diff --git a/pages/samplePage.tsx b/pages/samplePage.tsx new file mode 100644 index 0000000..f067888 --- /dev/null +++ b/pages/samplePage.tsx @@ -0,0 +1,127 @@ +import { BarGraphHorizontal, BarGraphVertical } from "components/BarGraph"; +import { mockCategoricalData, moreMockCategoricalData } from "data/mocks"; +import React from "react"; +import { useWindowDimensions } from "utils/getWindowDimensions"; +import { useIsMobile } from "utils/isMobile"; + +import { ComponentWrapper } from "@/components/ComponentWrapper"; + +import { WordCloud } from "../components/WordCloud"; + +import styles from "./samplePage.module.css"; + +export default function SamplePage() { + const { width } = useWindowDimensions(); + const isMobile = useIsMobile(); + + return ( +
+ + + + + + ({ + text: word.key, + value: word.value, + }))} + // For components that we don't want to match the width necessarily we can provide direct values + width={isMobile ? width / 1.5 : 800} + height={500} + /> + + + + + + + + + + + + + + + + ({ + text: word.key, + value: word.value, + }))} + width={isMobile ? width / 1.5 : width / 2} + height={500} + /> + +
+ ); +} diff --git a/utils/getWindowDimensions.ts b/utils/getWindowDimensions.ts new file mode 100644 index 0000000..427568b --- /dev/null +++ b/utils/getWindowDimensions.ts @@ -0,0 +1,35 @@ +// Attribution: https://stackoverflow.com/questions/36862334/get-viewport-window-height-in-reactjs +import { useState, useEffect } from "react"; + +type WindowDimensions = { + width: number; + height: number; +}; + +const getWindowDimensions = (): WindowDimensions => { + const { innerWidth, innerHeight } = window; + + return { + width: innerWidth, + height: innerHeight, + }; +}; + +export const useWindowDimensions = (): WindowDimensions => { + const [windowDimensions, setWindowDimensions] = useState({ + width: 0, + height: 0, + }); + + const handleResize = () => { + setWindowDimensions(getWindowDimensions()); + }; + + useEffect(() => { + handleResize(); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + return windowDimensions; +}; diff --git a/utils/isMobile.ts b/utils/isMobile.ts new file mode 100644 index 0000000..4022df1 --- /dev/null +++ b/utils/isMobile.ts @@ -0,0 +1,3 @@ +import { useWindowDimensions } from "./getWindowDimensions"; + +export const useIsMobile = () => useWindowDimensions().width <= 768; From a2dbcb90c6b7400eff033cb0975e724ec6b353c6 Mon Sep 17 00:00:00 2001 From: Amy Date: Fri, 2 Sep 2022 21:53:05 -0400 Subject: [PATCH 2/2] Add Quotation Carousel (#36) Made without visx, because that was easier. Closes #10. Co-authored-by: Amy Wang Reviewed-on: https://git.csclub.uwaterloo.ca/www/cs-2022-class-profile/pulls/36 Reviewed-by: j285he --- components/QuotationCarousel.module.css | 124 ++++++++++++++++++++++ components/QuotationCarousel.tsx | 130 ++++++++++++++++++++++++ data/mocks.ts | 12 +++ pages/_app.css | 5 +- pages/playground.module.css | 8 ++ pages/playground.tsx | 22 +++- 6 files changed, 299 insertions(+), 2 deletions(-) create mode 100644 components/QuotationCarousel.module.css create mode 100644 components/QuotationCarousel.tsx diff --git a/components/QuotationCarousel.module.css b/components/QuotationCarousel.module.css new file mode 100644 index 0000000..40241da --- /dev/null +++ b/components/QuotationCarousel.module.css @@ -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; +} diff --git a/components/QuotationCarousel.tsx b/components/QuotationCarousel.tsx new file mode 100644 index 0000000..fed81a7 --- /dev/null +++ b/components/QuotationCarousel.tsx @@ -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 ( +
+ + + +
+ +
    + {data.map((quote, idx) => ( +
  • +

    {quote}

    +
  • + ))} +
+ +
+ +
+ ); +} + +function Circle({ + className, + diameter, +}: { + className: string; + diameter: number; +}) { + return ( +
+ ); +} + +function CarouselButton({ isPrevious, onClick }: CarouselButtonProps) { + return ( + + ); +} + +function QuotationMark({ className }: { className: string }) { + return ( + + + + ); +} diff --git a/data/mocks.ts b/data/mocks.ts index 162f5aa..3631cd6 100644 --- a/data/mocks.ts +++ b/data/mocks.ts @@ -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.", +]; diff --git a/pages/_app.css b/pages/_app.css index 30aea2c..8c5d52f 100644 --- a/pages/_app.css +++ b/pages/_app.css @@ -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); } -} \ No newline at end of file +} diff --git a/pages/playground.module.css b/pages/playground.module.css index a7aefd8..97f5eac 100644 --- a/pages/playground.module.css +++ b/pages/playground.module.css @@ -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); +} diff --git a/pages/playground.tsx b/pages/playground.tsx index 5707edb..1ff00bb 100644 --- a/pages/playground.tsx +++ b/pages/playground.tsx @@ -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, }))} /> + +

+ {""} +

+
+ + +
); }