From 7bf0984698e43b9aa2dd879f65951318ed410390 Mon Sep 17 00:00:00 2001 From: Maze <167211895+mazeincoding@users.noreply.github.com> Date: Sat, 31 Jan 2026 00:20:04 +0100 Subject: codebase overhaul (#697) --- apps/web/src/components/editable-timecode.tsx | 154 ++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 apps/web/src/components/editable-timecode.tsx (limited to 'apps/web/src/components/editable-timecode.tsx') diff --git a/apps/web/src/components/editable-timecode.tsx b/apps/web/src/components/editable-timecode.tsx new file mode 100644 index 00000000..d32f067a --- /dev/null +++ b/apps/web/src/components/editable-timecode.tsx @@ -0,0 +1,154 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { formatTimeCode, parseTimeCode } from "@/lib/time"; +import type { TTimeCode } from "@/types/time"; +import { cn } from "@/utils/ui"; + +interface EditableTimecodeProps { + time: number; + duration: number; + format?: TTimeCode; + fps: number; + onTimeChange?: ({ time }: { time: number }) => void; + className?: string; + disabled?: boolean; +} + +export function EditableTimecode({ + time, + duration, + format = "HH:MM:SS:FF", + fps, + onTimeChange, + className, + disabled = false, +}: EditableTimecodeProps) { + const [isEditing, setIsEditing] = useState(false); + const [inputValue, setInputValue] = useState(""); + const [hasError, setHasError] = useState(false); + const inputRef = useRef(null); + const enterPressedRef = useRef(false); + const formattedTime = formatTimeCode({ timeInSeconds: time, format, fps }); + + const startEditing = () => { + if (disabled) return; + setIsEditing(true); + setInputValue(formattedTime); + setHasError(false); + enterPressedRef.current = false; + }; + + const cancelEditing = () => { + setIsEditing(false); + setInputValue(""); + setHasError(false); + enterPressedRef.current = false; + }; + + const applyEdit = () => { + const parsedTime = parseTimeCode({ timeCode: inputValue, format, fps }); + + if (parsedTime === null) { + setHasError(true); + return; + } + + const clampedTime = Math.max( + 0, + duration ? Math.min(duration, parsedTime) : parsedTime, + ); + + onTimeChange?.({ time: clampedTime }); + setIsEditing(false); + setInputValue(""); + setHasError(false); + enterPressedRef.current = false; + }; + + const handleKeyDown = ({ + key, + preventDefault, + }: React.KeyboardEvent) => { + if (key === "Enter") { + preventDefault(); + enterPressedRef.current = true; + applyEdit(); + } else if (key === "Escape") { + preventDefault(); + cancelEditing(); + } + }; + + const handleInputChange = ({ + target, + }: React.ChangeEvent) => { + setInputValue(target.value); + setHasError(false); + }; + + const handleBlur = () => { + if (!enterPressedRef.current && isEditing) { + applyEdit(); + } + }; + + const handleDisplayKeyDown = ({ + key, + preventDefault, + }: React.KeyboardEvent) => { + if (disabled) return; + + if (key === "Enter" || key === " ") { + preventDefault(); + startEditing(); + } + }; + + useEffect(() => { + if (isEditing && inputRef.current) { + inputRef.current.focus(); + inputRef.current.select(); + } + }, [isEditing]); + + if (isEditing) { + return ( + + ); + } + + return ( + + ); +} -- cgit v1.3.1