Add Timeline Component (#35)
Closes #7 Staging: https://j285he-timeline-csc-class-profile-staging-snedadah.k8s.csclub.cloud/playground/ Co-authored-by: Jared He <66887902+jaredjhe@users.noreply.github.com> Reviewed-on: #35 Reviewed-by: Amy <a258wang@csclub.uwaterloo.ca>pull/65/head
parent
fc5600cb20
commit
e3948c0577
@ -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; |
||||
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); |
||||
} |
@ -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; |
||||
width: number; |
||||
isTimeUppercase: boolean; |
||||
outerCircleWidth: number; |
||||
innerCircleWidth: number; |
||||
timeWidth: number; |
||||
textWidth: number; |
||||
gap: number; |
||||
} |
||||
|
||||
function TimelineSection({ |
||||
datum, |
||||
width, |
||||
isTimeUppercase, |
||||
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 |
||||
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> |
||||
); |
||||
} |
Loading…
Reference in new issue