fix(clip): a time's error line sits in the flow, the remove button off the cut, and every player surface rings amber
- useTimecodeField holds a time field's draft for its caller, which places TimecodeError (role alert) in its own flow under the fields' row: it no longer lies over the crop row taking its clicks, runs out of the editor's card, or joins the field's <label> name. - The editor's remove-cut buttons sit in a row under the track at their cut (trackLeft), off the cut's grab area, where a press drags it; RangeTrack's adornment slot is gone. - media-focus re-points --focus to the media amber on the Plex player, the YouTube stage and every bg-media-chrome surface; a contract test holds each such class string to media-focus or media-scope, the field's focus edge to the focus token, and --color-focus to the inline theme. - An upload's quota headline says the converted file does not fit, not "Storage full". - DESIGN.md: the tokens paragraph says what is re-pointed where; the decision row measures all three colours on the chrome.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useEffectEvent, useRef, useState, type SubmitEvent } from "react";
|
||||
import { useEffect, useEffectEvent, useId, useRef, useState, type SubmitEvent } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Crop, Crosshair, Maximize, Minus, Plus, Repeat, Scissors, X } from "lucide-react";
|
||||
@@ -18,7 +18,8 @@ import SegmentedToggle from "./ui/SegmentedToggle";
|
||||
import { Button } from "./ui/Button";
|
||||
import { IconButton } from "./ui/IconButton";
|
||||
import { Input } from "./ui/Input";
|
||||
import { TimecodeField } from "./ui/TimecodeField";
|
||||
import { TimecodeError, TimecodeField } from "./ui/TimecodeField";
|
||||
import { useTimecodeField, type TimecodeDraft } from "./ui/useTimecodeField";
|
||||
|
||||
// The clip panel: marks a range of what is playing and cuts it into a download of its own. It lives
|
||||
// IN the player (the YouTube card under the video, the Plex player's control band), so the video
|
||||
@@ -164,6 +165,9 @@ export default function ClipPanel({
|
||||
seek(next.end, final);
|
||||
};
|
||||
|
||||
const inField = useTimecodeField(clip.start, hours, (v) => moveIn(v, true));
|
||||
const outField = useTimecodeField(clip.end, hours, (v) => moveOut(v, true));
|
||||
const timeErrorId = useId();
|
||||
const body: ClipRequest = {
|
||||
source: src.source,
|
||||
ref: src.ref,
|
||||
@@ -291,7 +295,8 @@ export default function ClipPanel({
|
||||
<MarkField
|
||||
label={t("clip.in")}
|
||||
value={clip.start}
|
||||
hours={hours}
|
||||
field={inField}
|
||||
errorId={timeErrorId}
|
||||
onSet={(v) => moveIn(v, true)}
|
||||
nudge={NUDGE_S}
|
||||
hereLabel={t("clip.inHere")}
|
||||
@@ -300,7 +305,8 @@ export default function ClipPanel({
|
||||
<MarkField
|
||||
label={t("clip.out")}
|
||||
value={clip.end}
|
||||
hours={hours}
|
||||
field={outField}
|
||||
errorId={timeErrorId}
|
||||
onSet={(v) => moveOut(v, true)}
|
||||
nudge={NUDGE_S}
|
||||
hereLabel={t("clip.outHere")}
|
||||
@@ -322,6 +328,7 @@ export default function ClipPanel({
|
||||
{t("clip.loop")}
|
||||
</Button>
|
||||
</div>
|
||||
{(inField.invalid || outField.invalid) && <TimecodeError id={timeErrorId} hours={hours} />}
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Button
|
||||
@@ -389,7 +396,8 @@ export default function ClipPanel({
|
||||
function MarkField({
|
||||
label,
|
||||
value,
|
||||
hours,
|
||||
field,
|
||||
errorId,
|
||||
onSet,
|
||||
nudge,
|
||||
hereLabel,
|
||||
@@ -397,7 +405,8 @@ function MarkField({
|
||||
}: {
|
||||
label: string;
|
||||
value: number;
|
||||
hours: boolean;
|
||||
field: TimecodeDraft;
|
||||
errorId: string;
|
||||
onSet: (v: number) => void;
|
||||
nudge: number;
|
||||
hereLabel: string;
|
||||
@@ -418,10 +427,9 @@ function MarkField({
|
||||
<TimecodeField
|
||||
size="sm"
|
||||
className="w-24 text-center tabular-nums"
|
||||
value={value}
|
||||
hours={hours}
|
||||
field={field}
|
||||
errorId={errorId}
|
||||
aria-label={label}
|
||||
onCommit={onSet}
|
||||
/>
|
||||
<IconButton
|
||||
variant="overlay"
|
||||
|
||||
Reference in New Issue
Block a user