Add Timeline Component #35

Merged
j285he merged 17 commits from j285he-timeline into main 2022-09-12 20:00:02 -04:00
4 changed files with 257 additions and 1 deletions

View File

@ -0,0 +1,75 @@
.wrapper {
position: relative;
}
.line {
position: absolute;
height: 100%;
border-radius: calc(10rem / 16);
background-color: var(--secondary-accent);
z-index: -1;
}
.timelineSections {
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
gap: calc(20rem / 16);
}
.timelineSection {
width: 100%;
display: flex;
j285he marked this conversation as resolved
Review

Is this necessary? 🤔

Is this necessary? 🤔
flex-direction: row;
justify-content: center;
}
.time {
margin: 0;
text-align: right;
font-size: calc(30rem / 16);
font-weight: 700;
color: var(--secondary-accent);
word-wrap: break-word;
}
.circle {
background-color: var(--secondary-accent);
box-shadow: calc(0rem / 16) calc(0rem / 16) calc(30rem / 16) var(--secondary-accent);
display: flex;
justify-content: center;
align-items: center;
}
.innerCircle {
background-color: var(--label);
display: none;
}
.text {
height: fit-content;
margin: 0;
padding: calc(15rem / 16);
border-radius: calc(10rem / 16);
font-size: calc(20rem / 16);
font-weight: 700;
color: var(--label);
border: calc(2rem / 16) solid var(--card-background);
background-color: var(--card-background);
word-wrap: break-word;
box-sizing: border-box;
}
.timelineSection:hover .time {
color: var(--secondary-accent-light);
}
.timelineSection:hover .innerCircle {
display: inline;
}
.timelineSection:hover .text {
border: calc(2rem / 16) solid var(--secondary-accent-light);
box-shadow: calc(0rem / 16) calc(0rem / 16) calc(20rem / 16) var(--secondary-accent);
}

142
components/Timeline.tsx Normal file
View File

@ -0,0 +1,142 @@
import React from "react";
import styles from "./Timeline.module.css";
interface TimelineData {
time: string;
text: string;
}
interface TimelineProps {
data: TimelineData[];
/** Whether the time is transformed to uppercase. */
isTimeUppercase?: boolean;
/** Width of the middle timeline line, in pixels */
lineWidth?: number;
/** Width of the outer circles on the timeline, in pixels. */
outerCircleWidth?: number;
/** Width of the inner circles on the timeline, in pixels. */
innerCircleWidth?: number;
/** Width of time label, in pixels. */
timeWidth?: number;
/** Width of text label, in pixels. */
textWidth?: number;
/** Distance between the time label AND the text label to middle line, in pixels. */
gap?: number;
className?: string;
}
export function Timeline({
data,
isTimeUppercase = true,
lineWidth = 5,
outerCircleWidth = 30,
innerCircleWidth = 15,
timeWidth = 200,
textWidth = 300,
gap = 50,
className,
}: TimelineProps) {
const largerMiddleElement =
outerCircleWidth > lineWidth ? outerCircleWidth : lineWidth;
const width = timeWidth + gap + largerMiddleElement + gap + textWidth;
if (innerCircleWidth > outerCircleWidth) {
throw new Error(
`<Timeline /> - innerCircleWidth (${innerCircleWidth}) is larger than outerCircleWidth (${outerCircleWidth})`
);
}
return (
<div
className={
className ? `${className} ${styles.wrapper}` : `${styles.wrapper}`
}
style={{ width: width }}
>
<div
className={styles.line}
style={{
width: lineWidth,
left: width / 2 - lineWidth / 2,
}}
/>
<div className={styles.timelineSections}>
{data.map((datum) => (
<TimelineSection
key={datum.time}
datum={datum}
width={width}
isTimeUppercase={isTimeUppercase}
outerCircleWidth={outerCircleWidth}
innerCircleWidth={innerCircleWidth}
timeWidth={timeWidth}
textWidth={textWidth}
gap={gap}
/>
))}
</div>
</div>
);
}
interface TimelineSectionProps {
datum: TimelineData;
j285he marked this conversation as resolved
Review

Have we considered using .timelineSection:hover time, .timelineSection:hover text, etc. CSS selectors to handle the hover styles?

The current hovering works fine, my only (minor) qualm is that it feels a bit odd that currently the hover styles are triggered by mousing over the space underneath the time label, but not by mousing over the gaps. (Also currently if the user moves their mouse quickly in a horizontal direction across the timeline, the section will quickly switch between hovered/unhovered styles as the cursor moves between time - gap - circle - gap - text, etc.)

Have we considered using `.timelineSection:hover time`, `.timelineSection:hover text`, etc. CSS selectors to handle the hover styles? The current hovering works fine, my only (minor) qualm is that it feels a bit odd that currently the hover styles are triggered by mousing over the space underneath the time label, but not by mousing over the gaps. (Also currently if the user moves their mouse quickly in a horizontal direction across the timeline, the section will quickly switch between hovered/unhovered styles as the cursor moves between time - gap - circle - gap - text, etc.)
Review

Ok, using css hover on .timelineSection will still result in hover effects triggering for the space underneath labels or in the space between the labels and the edge of the timeline.

Ok, using css hover on `.timelineSection` will still result in hover effects triggering for the space underneath labels or in the space between the labels and the edge of the timeline.
Review

Okay, I think that makes sense (in my opinion) since now it's like all the gaps/empty spaces trigger the hover effect, whereas previously the space underneath the time would trigger the hover effects but other spaces/gaps wouldn't.

Okay, I think that makes sense (in my opinion) since now it's like all the gaps/empty spaces trigger the hover effect, whereas previously the space underneath the time would trigger the hover effects but other spaces/gaps wouldn't.
width: number;
isTimeUppercase: boolean;
outerCircleWidth: number;
innerCircleWidth: number;
timeWidth: number;
textWidth: number;
gap: number;
}
function TimelineSection({
datum,
width,
isTimeUppercase,
j285he marked this conversation as resolved
Review

These spacer divs should (hopefully?) no longer be necessary if we get rid of position: absolute on .timelineSection and can be replaced with margin/padding and gap. 🙂

These spacer divs should (hopefully?) no longer be necessary if we get rid of `position: absolute` on `.timelineSection` and can be replaced with `margin`/`padding` and `gap`. 🙂
outerCircleWidth,
innerCircleWidth,
timeWidth,
textWidth,
gap,
}: TimelineSectionProps) {
return (
<div className={styles.timelineSection} style={{ gap: gap }}>
<div
className={styles.time}
style={{
width: timeWidth,
marginLeft: (width - 2 * gap - outerCircleWidth) / 2 - timeWidth,
}}
>
{isTimeUppercase ? datum.time.toUpperCase() : datum.time}
</div>
<div
j285he marked this conversation as resolved
Review

Clean up the hover state and onMouseEnter/Leave shenanigans, if we're going to do hovering in CSS :))

Clean up the hover state and onMouseEnter/Leave shenanigans, if we're going to do hovering in CSS :))
className={styles.circle}
style={{
width: outerCircleWidth,
height: outerCircleWidth,
borderRadius: outerCircleWidth,
}}
>
<div
className={styles.innerCircle}
style={{
width: innerCircleWidth,
height: innerCircleWidth,
borderRadius: innerCircleWidth,
}}
/>
</div>
<div
className={styles.text}
style={{
width: textWidth,
marginRight: (width - 2 * gap - outerCircleWidth) / 2 - textWidth,
}}
>
{datum.text}
</div>
</div>
);
}

View File

@ -80,6 +80,33 @@ export const moreMockCategoricalData = [
{ key: "Dart", value: 2.21 },
];
export const mockTimelineData = [
{
time: "Fall 2020",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
},
{
time: "Winter 2021",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad",
},
{
time: "Spring 2021",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor i",
},
{
time: "Fall 2021",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proid",
},
{
time: "Winter 2022",
text: "Lorem ipsum dolor sit amet, consectetur adipi",
},
{
time: "Spring 2022",
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut en",
},
];
export const mockBoxPlotData = [
{
category: "1A",

View File

@ -7,12 +7,14 @@ import {
mockQuoteData,
mockQuoteDataLong,
mockPieData,
mockTimelineData,
} from "data/mocks";
import React from "react";
import About from "@/components/About";
import { PieChart } from "@/components/PieChart";
import { QuotationCarousel } from "@/components/QuotationCarousel";
import { Timeline } from "@/components/Timeline";
import { CenterWrapper } from "../components/CenterWrapper";
import { ColorPalette } from "../components/ColorPalette";
@ -25,13 +27,15 @@ export default function Home() {
<div className={styles.page}>
<h1>Playground</h1>
<p>Show off your components here!</p>
<ColorPalette />
<h2>
<code>{"<PieChart />"}</code>
</h2>
<div style={{ padding: "30px" }}>
<PieChart data={mockPieData} width={800} labelWidth={215} />
</div>
<ColorPalette />
<h2>
<code>Text Styles</code>
</h2>
@ -57,6 +61,7 @@ export default function Home() {
right: 20,
}}
/>
<h2>
<code>{"<BarGraphVertical />"}</code>
</h2>
@ -76,6 +81,7 @@ export default function Home() {
right: 20,
}}
/>
<h2>
<code>{"<WordCloud />"}</code>
</h2>
@ -85,6 +91,12 @@ export default function Home() {
value: word.value,
}))}
/>
<h2>
<code>{"<Timeline />"}</code>
</h2>
<Timeline data={mockTimelineData} />
<h2>
<code>{"<Textbox />"}</code>
</h2>