140 lines
3.4 KiB
TypeScript
140 lines
3.4 KiB
TypeScript
import React, { useState } from "react";
|
|
|
|
import styles from "./Timeline.module.css";
|
|
|
|
interface TimelineData {
|
|
time: string;
|
|
text: string;
|
|
}
|
|
|
|
interface TimelineProps {
|
|
data: TimelineData[];
|
|
/** Width of the entire timeline, in pixels. */
|
|
width: number;
|
|
/** Height of the entire timeline, in pixels. */
|
|
height: number;
|
|
/** Whether the time is transformed to uppercase */
|
|
isTimeUppercase?: boolean;
|
|
/** Width of time label, in pixels. */
|
|
timeWidth?: number;
|
|
/** Width of text label, in pixels. */
|
|
textWidth?: number;
|
|
/** Distance between labels to middle line, in pixels. */
|
|
labelsOffset?: number;
|
|
className?: string;
|
|
}
|
|
|
|
export default function Timeline({
|
|
data,
|
|
width,
|
|
height,
|
|
isTimeUppercase = true,
|
|
timeWidth = 200,
|
|
textWidth = 300,
|
|
labelsOffset = 50,
|
|
className,
|
|
}: TimelineProps) {
|
|
return (
|
|
<div
|
|
className={
|
|
className ? `${className} ${styles.wrapper}` : `${styles.wrapper}`
|
|
}
|
|
style={{ width: width, height: height }}
|
|
>
|
|
<div
|
|
className={styles.line}
|
|
style={{ height: height, left: width / 2 }}
|
|
></div>
|
|
<div className={styles.timelineSections} style={{ width: width }}>
|
|
{data.map((datum) => (
|
|
<TimelineSection
|
|
key={datum.time}
|
|
datum={datum}
|
|
width={width}
|
|
isTimeUppercase={isTimeUppercase}
|
|
timeWidth={timeWidth}
|
|
textWidth={textWidth}
|
|
labelsOffset={labelsOffset}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface TimelineSectionProps {
|
|
datum: TimelineData;
|
|
width: number;
|
|
isTimeUppercase: boolean;
|
|
timeWidth: number;
|
|
textWidth: number;
|
|
labelsOffset: number;
|
|
}
|
|
|
|
function TimelineSection({
|
|
datum,
|
|
width,
|
|
isTimeUppercase,
|
|
timeWidth,
|
|
|
|
textWidth,
|
|
labelsOffset,
|
|
}: TimelineSectionProps) {
|
|
const [onHover, setHover] = useState(false);
|
|
|
|
const handleMouseEnter = () => {
|
|
setHover(true);
|
|
console.log(onHover);
|
|
};
|
|
const handleMouseLeave = () => setHover(false);
|
|
|
|
// divs for customizable margins are necessary, absolute positioning loses vertical flex-box functionality
|
|
return (
|
|
<div className={styles.timelineSection}>
|
|
<div
|
|
style={{
|
|
width: (width - labelsOffset - labelsOffset - 30) / 2 - timeWidth,
|
|
}}
|
|
></div>
|
|
<div
|
|
className={onHover ? `${styles.time} ${styles.timeHover}` : styles.time}
|
|
style={{
|
|
width: timeWidth,
|
|
}}
|
|
onMouseEnter={handleMouseEnter}
|
|
onMouseLeave={handleMouseLeave}
|
|
>
|
|
{isTimeUppercase ? datum.time.toUpperCase() : datum.time}
|
|
</div>
|
|
<div style={{ width: labelsOffset }}></div>
|
|
<div
|
|
className={styles.circle}
|
|
style={{ width: 30 }}
|
|
onMouseEnter={handleMouseEnter}
|
|
onMouseLeave={handleMouseLeave}
|
|
>
|
|
<div
|
|
className={styles.innerCircle}
|
|
style={{ display: onHover ? "inline" : "none" }}
|
|
></div>
|
|
</div>
|
|
<div style={{ width: labelsOffset }}></div>
|
|
<div
|
|
className={onHover ? `${styles.text} ${styles.textHover}` : styles.text}
|
|
style={{
|
|
width: textWidth,
|
|
}}
|
|
onMouseEnter={handleMouseEnter}
|
|
onMouseLeave={handleMouseLeave}
|
|
>
|
|
{datum.text}
|
|
</div>
|
|
<div
|
|
style={{
|
|
width: (width - labelsOffset - labelsOffset - 30) / 2 - textWidth,
|
|
}}
|
|
></div>
|
|
</div>
|
|
);
|
|
}
|