Timeline tweaks for mobile (#10726)

* add dense prop, combine duplicate code, fix mobile bug

* put segment height in hook

* playground
This commit is contained in:
Josh Hawkins
2024-03-28 10:03:06 -05:00
committed by GitHub
parent 985b2d7b27
commit 36d5e5b45f
10 changed files with 258 additions and 348 deletions

View File

@@ -1,3 +1,5 @@
import useDraggableElement from "@/hooks/use-draggable-element";
import { useTimelineUtils } from "@/hooks/use-timeline-utils";
import { DraggableElement } from "@/types/draggable-element";
import {
ReactNode,
@@ -12,85 +14,150 @@ import { isIOS, isMobile } from "react-device-detect";
export type ReviewTimelineProps = {
timelineRef: RefObject<HTMLDivElement>;
handlebarRef: RefObject<HTMLDivElement>;
handlebarTimeRef: RefObject<HTMLDivElement>;
handlebarMouseMove: (e: MouseEvent | TouchEvent) => void;
handlebarMouseUp: (e: MouseEvent | TouchEvent) => void;
handlebarMouseDown: (
e:
| React.MouseEvent<HTMLDivElement, MouseEvent>
| React.TouchEvent<HTMLDivElement>,
) => void;
contentRef: RefObject<HTMLDivElement>;
segmentDuration: number;
timelineDuration: number;
timelineStartAligned: number;
showHandlebar: boolean;
showExportHandles: boolean;
exportStartRef: RefObject<HTMLDivElement>;
exportStartTimeRef: RefObject<HTMLDivElement>;
exportEndRef: RefObject<HTMLDivElement>;
exportEndTimeRef: RefObject<HTMLDivElement>;
exportStartMouseMove: (e: MouseEvent | TouchEvent) => void;
exportStartMouseUp: (e: MouseEvent | TouchEvent) => void;
exportStartMouseDown: (
e:
| React.MouseEvent<HTMLDivElement, MouseEvent>
| React.TouchEvent<HTMLDivElement>,
) => void;
exportEndMouseMove: (e: MouseEvent | TouchEvent) => void;
exportEndMouseUp: (e: MouseEvent | TouchEvent) => void;
exportEndMouseDown: (
e:
| React.MouseEvent<HTMLDivElement, MouseEvent>
| React.TouchEvent<HTMLDivElement>,
) => void;
isDragging: boolean;
exportStartPosition?: number;
exportEndPosition?: number;
handlebarTime?: number;
setHandlebarTime?: React.Dispatch<React.SetStateAction<number>>;
onHandlebarDraggingChange?: (isDragging: boolean) => void;
onlyInitialHandlebarScroll?: boolean;
exportStartTime?: number;
exportEndTime?: number;
setExportStartTime?: React.Dispatch<React.SetStateAction<number>>;
setExportEndTime?: React.Dispatch<React.SetStateAction<number>>;
dense: boolean;
children: ReactNode;
};
export function ReviewTimeline({
timelineRef,
handlebarRef,
handlebarTimeRef,
handlebarMouseMove,
handlebarMouseUp,
handlebarMouseDown,
contentRef,
segmentDuration,
timelineDuration,
timelineStartAligned,
showHandlebar = false,
showExportHandles = false,
exportStartRef,
exportStartTimeRef,
exportEndRef,
exportEndTimeRef,
exportStartMouseMove,
exportStartMouseUp,
exportStartMouseDown,
exportEndMouseMove,
exportEndMouseUp,
exportEndMouseDown,
isDragging,
exportStartPosition,
exportEndPosition,
handlebarTime,
setHandlebarTime,
onHandlebarDraggingChange,
onlyInitialHandlebarScroll = false,
exportStartTime,
setExportStartTime,
exportEndTime,
setExportEndTime,
dense,
children,
}: ReviewTimelineProps) {
const [isDraggingHandlebar, setIsDraggingHandlebar] = useState(false);
const [isDraggingExportStart, setIsDraggingExportStart] = useState(false);
const [isDraggingExportEnd, setIsDraggingExportEnd] = useState(false);
const [exportStartPosition, setExportStartPosition] = useState(0);
const [exportEndPosition, setExportEndPosition] = useState(0);
const handlebarRef = useRef<HTMLDivElement>(null);
const handlebarTimeRef = useRef<HTMLDivElement>(null);
const exportStartRef = useRef<HTMLDivElement>(null);
const exportStartTimeRef = useRef<HTMLDivElement>(null);
const exportEndRef = useRef<HTMLDivElement>(null);
const exportEndTimeRef = useRef<HTMLDivElement>(null);
const isDragging = useMemo(
() => isDraggingHandlebar || isDraggingExportStart || isDraggingExportEnd,
[isDraggingHandlebar, isDraggingExportStart, isDraggingExportEnd],
);
const exportSectionRef = useRef<HTMLDivElement>(null);
const segmentHeight = useMemo(() => {
if (timelineRef.current) {
const { scrollHeight: timelineHeight } =
timelineRef.current as HTMLDivElement;
return timelineHeight / (timelineDuration / segmentDuration);
}
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [segmentDuration, timelineDuration, timelineRef, showExportHandles]);
const [draggableElementType, setDraggableElementType] =
useState<DraggableElement>();
const { alignStartDateToTimeline, alignEndDateToTimeline, segmentHeight } =
useTimelineUtils({
segmentDuration,
timelineDuration,
timelineRef,
});
const paddedExportStartTime = useMemo(() => {
if (exportStartTime) {
return alignStartDateToTimeline(exportStartTime) + segmentDuration;
}
}, [exportStartTime, segmentDuration, alignStartDateToTimeline]);
const paddedExportEndTime = useMemo(() => {
if (exportEndTime) {
return alignEndDateToTimeline(exportEndTime);
}
}, [exportEndTime, alignEndDateToTimeline]);
const {
handleMouseDown: handlebarMouseDown,
handleMouseUp: handlebarMouseUp,
handleMouseMove: handlebarMouseMove,
} = useDraggableElement({
contentRef,
timelineRef,
draggableElementRef: handlebarRef,
segmentDuration,
showDraggableElement: showHandlebar,
draggableElementTime: handlebarTime,
setDraggableElementTime: setHandlebarTime,
initialScrollIntoViewOnly: onlyInitialHandlebarScroll,
timelineDuration,
timelineStartAligned,
isDragging: isDraggingHandlebar,
setIsDragging: setIsDraggingHandlebar,
draggableElementTimeRef: handlebarTimeRef,
dense,
});
const {
handleMouseDown: exportStartMouseDown,
handleMouseUp: exportStartMouseUp,
handleMouseMove: exportStartMouseMove,
} = useDraggableElement({
contentRef,
timelineRef,
draggableElementRef: exportStartRef,
segmentDuration,
showDraggableElement: showExportHandles,
draggableElementTime: exportStartTime,
draggableElementLatestTime: paddedExportEndTime,
setDraggableElementTime: setExportStartTime,
alignSetTimeToSegment: true,
timelineDuration,
timelineStartAligned,
isDragging: isDraggingExportStart,
setIsDragging: setIsDraggingExportStart,
draggableElementTimeRef: exportStartTimeRef,
setDraggableElementPosition: setExportStartPosition,
dense,
});
const {
handleMouseDown: exportEndMouseDown,
handleMouseUp: exportEndMouseUp,
handleMouseMove: exportEndMouseMove,
} = useDraggableElement({
contentRef,
timelineRef,
draggableElementRef: exportEndRef,
segmentDuration,
showDraggableElement: showExportHandles,
draggableElementTime: exportEndTime,
draggableElementEarliestTime: paddedExportStartTime,
setDraggableElementTime: setExportEndTime,
alignSetTimeToSegment: true,
timelineDuration,
timelineStartAligned,
isDragging: isDraggingExportEnd,
setIsDragging: setIsDraggingExportEnd,
draggableElementTimeRef: exportEndTimeRef,
setDraggableElementPosition: setExportEndPosition,
dense,
});
const handleHandlebar = useCallback(
(
e:
@@ -177,6 +244,19 @@ export function ReviewTimeline({
],
);
const textSizeClasses = useCallback(
(draggableElement: DraggableElement) => {
if (isDragging && isMobile && draggableElementType === draggableElement) {
return "text-lg";
} else if (dense) {
return "text-[8px] md:text-xs";
} else {
return "text-xs";
}
},
[dense, isDragging, draggableElementType],
);
useEffect(() => {
if (
exportSectionRef.current &&
@@ -218,6 +298,12 @@ export function ReviewTimeline({
};
}, [handleMouseMove, handleMouseUp, isDragging]);
useEffect(() => {
if (onHandlebarDraggingChange) {
onHandlebarDraggingChange(isDraggingHandlebar);
}
}, [isDraggingHandlebar, onHandlebarDraggingChange]);
return (
<div
ref={timelineRef}
@@ -234,7 +320,7 @@ export function ReviewTimeline({
</div>
{showHandlebar && (
<div
className={`absolute left-0 top-0 ${isDragging && isIOS ? "" : "z-20"} w-full`}
className={`absolute left-0 top-0 ${isDraggingHandlebar && isIOS ? "" : "z-20"} w-full`}
role="scrollbar"
ref={handlebarRef}
>
@@ -245,21 +331,25 @@ export function ReviewTimeline({
>
<div
className={`relative w-full ${
isDragging ? "cursor-grabbing" : "cursor-grab"
isDraggingHandlebar ? "cursor-grabbing" : "cursor-grab"
}`}
>
<div
className={`bg-destructive rounded-full mx-auto ${
segmentDuration < 60 ? "w-12 md:w-20" : "w-12 md:w-16"
} h-5 ${isDragging && isMobile && draggableElementType == "handlebar" ? "fixed top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-[30%] h-[30px] bg-destructive/80" : "static"} flex items-center justify-center`}
dense
? "w-12 md:w-20"
: segmentDuration < 60
? "w-24"
: "w-20"
} h-5 ${isDraggingHandlebar && isMobile ? "fixed top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-destructive/80" : "static"} flex items-center justify-center`}
>
<div
ref={handlebarTimeRef}
className={`text-white pointer-events-none ${isDragging && isMobile && draggableElementType == "handlebar" ? "text-lg" : "text-[8px] md:text-xs"} z-10`}
className={`text-white pointer-events-none ${textSizeClasses("handlebar")} z-10`}
></div>
</div>
<div
className={`absolute h-[4px] w-full bg-destructive ${isDragging && isMobile && draggableElementType == "handlebar" ? "top-1" : "top-1/2 transform -translate-y-1/2"}`}
className={`absolute h-[4px] w-full bg-destructive ${isDraggingHandlebar && isMobile ? "top-1" : "top-1/2 transform -translate-y-1/2"}`}
></div>
</div>
</div>
@@ -268,7 +358,7 @@ export function ReviewTimeline({
{showExportHandles && (
<>
<div
className={`absolute left-0 top-0 ${isDragging && isIOS ? "" : "z-20"} w-full`}
className={`export-end absolute left-0 top-0 ${isDraggingExportEnd && isIOS ? "" : "z-20"} w-full`}
role="scrollbar"
ref={exportEndRef}
>
@@ -279,21 +369,25 @@ export function ReviewTimeline({
>
<div
className={`relative mt-[6.5px] w-full ${
isDragging ? "cursor-grabbing" : "cursor-grab"
isDraggingExportEnd ? "cursor-grabbing" : "cursor-grab"
}`}
>
<div
className={`bg-selected -mt-4 mx-auto ${
segmentDuration < 60 ? "w-12 md:w-20" : "w-12 md:w-16"
} h-5 ${isDragging && isMobile && draggableElementType == "export_end" ? "fixed mt-0 rounded-full top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-[30%] h-[30px] bg-selected/80" : "rounded-tr-lg rounded-tl-lg static"} flex items-center justify-center`}
dense
? "w-12 md:w-20"
: segmentDuration < 60
? "w-24"
: "w-20"
} h-5 ${isDraggingExportEnd && isMobile ? "fixed mt-0 rounded-full top-[18px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-selected/80" : "rounded-tr-lg rounded-tl-lg static"} flex items-center justify-center`}
>
<div
ref={exportEndTimeRef}
className={`text-white pointer-events-none ${isDragging && isMobile && draggableElementType == "export_end" ? "text-lg mt-0" : "text-[8px] md:text-xs"} z-10`}
className={`text-white pointer-events-none ${isDraggingExportEnd && isMobile ? "mt-0" : ""} ${textSizeClasses("export_end")} z-10`}
></div>
</div>
<div
className={`absolute h-[4px] w-full bg-selected ${isDragging && isMobile && draggableElementType == "export_end" ? "top-0" : "top-1/2 transform -translate-y-1/2"}`}
className={`absolute h-[4px] w-full bg-selected ${isDraggingExportEnd && isMobile ? "top-0" : "top-1/2 transform -translate-y-1/2"}`}
></div>
</div>
</div>
@@ -303,7 +397,7 @@ export function ReviewTimeline({
className="bg-selected/50 absolute w-full"
></div>
<div
className={`absolute left-0 top-0 ${isDragging && isIOS ? "" : "z-20"} w-full`}
className={`export-start absolute left-0 top-0 ${isDraggingExportStart && isIOS ? "" : "z-20"} w-full`}
role="scrollbar"
ref={exportStartRef}
>
@@ -318,16 +412,20 @@ export function ReviewTimeline({
}`}
>
<div
className={`absolute h-[4px] w-full bg-selected ${isDragging && isMobile && draggableElementType == "export_start" ? "top-[12px]" : "top-1/2 transform -translate-y-1/2"}`}
className={`absolute h-[4px] w-full bg-selected ${isDraggingExportStart && isMobile ? "top-[12px]" : "top-1/2 transform -translate-y-1/2"}`}
></div>
<div
className={`bg-selected mt-4 mx-auto ${
segmentDuration < 60 ? "w-12 md:w-20" : "w-12 md:w-16"
} h-5 ${isDragging && isMobile && draggableElementType == "export_start" ? "fixed mt-0 rounded-full top-[4px] left-1/2 transform -translate-x-1/2 z-20 w-[30%] h-[30px] bg-selected/80" : "rounded-br-lg rounded-bl-lg static"} flex items-center justify-center`}
dense
? "w-12 md:w-20"
: segmentDuration < 60
? "w-24"
: "w-20"
} h-5 ${isDraggingExportStart && isMobile ? "fixed mt-0 rounded-full top-[4px] left-1/2 transform -translate-x-1/2 z-20 w-32 h-[30px] bg-selected/80" : "rounded-br-lg rounded-bl-lg static"} flex items-center justify-center`}
>
<div
ref={exportStartTimeRef}
className={`text-white pointer-events-none ${isDragging && isMobile && draggableElementType == "export_start" ? "text-lg mt-0" : "text-[8px] md:text-xs"} z-10`}
className={`text-white pointer-events-none ${isDraggingExportStart && isMobile ? "mt-0" : ""} ${textSizeClasses("export_start")} z-10`}
></div>
</div>
</div>