Skip to content

Instantly share code, notes, and snippets.

@kristersd
Last active January 7, 2023 19:39
Show Gist options
  • Save kristersd/87ab8f7256776978dd4a20212b7e632e to your computer and use it in GitHub Desktop.
Save kristersd/87ab8f7256776978dd4a20212b7e632e to your computer and use it in GitHub Desktop.
import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
import "./styles.css";
const useMouseDrag = ({ onDrag }: { onDrag: (movementX: number) => void }) => {
const ref = useRef<HTMLDivElement>(null);
const isPressed = useRef(false);
const onStartDrag = () => {
isPressed.current = true;
};
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (isPressed.current) {
onDrag(Math.trunc(e.movementX * -0.75));
}
};
const handleMouseUp = () => {
isPressed.current = false;
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [onDrag]);
return {
bind: () => ({
ref,
onMouseDown: onStartDrag
})
};
};
const TimelineRange = ({
width,
onDrag
}: {
width: number;
onDrag: (movementX: number) => void;
}) => {
const { bind } = useMouseDrag({ onDrag });
return (
<div
{...bind()}
style={{
display: "flex",
cursor: "grab",
gap: width,
userSelect: "none",
height: 20,
backgroundColor: "rebeccapurple",
color: "white",
position: "relative"
}}
>
{Array.from({ length: 288 }, (_, i, v = i + 1000) => (
<div key={i}>
<div
style={{
position: "absolute",
top: 0,
left: width * i - (i && 20)
}}
>
{v}
</div>
<div> </div>
</div>
))}
</div>
);
};
const STRIPE_THICKNESS = 1;
const VerticalStripes = ({
width,
children
}: {
width: number;
children: ReactNode;
}) => {
const offset = width - STRIPE_THICKNESS;
return (
<div
style={{
height: 200,
background: `repeating-linear-gradient(90deg, transparent, transparent ${offset}px, #000 ${offset}px, transparent ${width}px)`
}}
>
{children}
</div>
);
};
const Event = ({ width, color }: { width: number; color: string }) => {
return (
<div
style={{
width,
height: 100,
backgroundColor: color,
borderRadius: "0.3rem"
}}
></div>
);
};
export default function App() {
const [width, setWidth] = useState(100);
const scrollRef = useRef<HTMLDivElement>(null);
const handleDrag = useCallback((movementX: number) => {
if (scrollRef.current) {
scrollRef.current.scrollLeft = scrollRef.current.scrollLeft + movementX;
}
}, []);
return (
<div className="App" style={{ display: "grid", placeItems: "center" }}>
<div
ref={scrollRef}
style={{
overflow: "hidden",
width: "70vw"
}}
>
<div
style={{
minWidth: "fit-content"
}}
>
<TimelineRange width={width} onDrag={handleDrag} />
<div
style={{ display: "flex", flexDirection: "column", gap: "1rem" }}
>
<VerticalStripes width={width}>
<div
style={{
display: "flex",
alignItems: "flex-end",
height: "100%"
}}
>
<Event width={width * 1.5} color="red" />
<Event width={width * 2.6} color="purple" />
<Event width={width * 1.75} color="lime" />
<Event width={width * 2.5} color="blue" />
</div>
</VerticalStripes>
<VerticalStripes width={width}>
<div
style={{
display: "flex",
alignItems: "flex-end",
height: "100%"
}}
>
<Event width={width * 1.5} color="red" />
<Event width={width * 2.6} color="purple" />
<Event width={width * 1.75} color="lime" />
<Event width={width * 2.5} color="blue" />
</div>
</VerticalStripes>
</div>
</div>
</div>
<input
type="range"
min={1}
max={2000}
value={width}
onChange={(e) => setWidth(Math.max(+e.target.value, 100))}
/>
Zoom {width} %
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment