feat(desktop): immersive focus mode for editing tasks
Editing a task in the stream was ambiguous: nothing signalled that you were editing, playback chrome stayed live, Escape exited the whole stream, and arrow keys could navigate to another particle mid-edit. Focusing any field now enters a focus mode: the surrounding stream chrome dims and blurs while the task card lifts above it with a soft ring, so the task you're editing is unmistakable. Escape (or clicking outside the card) leaves the editor and resumes playback from where it paused instead of exiting the stream, and arrow/list keys no longer pull focus to another particle while editing. Text particles already get this via their full-screen edit overlay, so the two editing flows are now consistent. Addresses #286 Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01BDWBJjERKk8zUmh6bcKWSM
This commit is contained in:
@@ -37,6 +37,10 @@ interface TaskParticleViewProps {
|
||||
paused: boolean;
|
||||
onEnded: () => void;
|
||||
onProgress?: (ratio: number) => void;
|
||||
/** Dim and blur the surrounding stream chrome while a field is focused. */
|
||||
immersive?: boolean;
|
||||
/** Notify the parent when focus enters or leaves the card (focus mode). */
|
||||
onEditingChange?: (editing: boolean) => void;
|
||||
}
|
||||
|
||||
const DWELL_DURATION_S = 8;
|
||||
@@ -56,6 +60,8 @@ export function TaskParticleView({
|
||||
paused,
|
||||
onEnded,
|
||||
onProgress,
|
||||
immersive = false,
|
||||
onEditingChange,
|
||||
}: TaskParticleViewProps) {
|
||||
const { networkId } = parseParticlePath(containerPath);
|
||||
const network = useNetwork(networkId);
|
||||
@@ -74,6 +80,19 @@ export function TaskParticleView({
|
||||
const [editing, setEditing] = useState(false);
|
||||
useSuspendPlayback(editing, `task-edit-${particle.id}`);
|
||||
|
||||
// Mirror focus state to the stream so it can lock navigation and route
|
||||
// Escape to "leave the editor" instead of "exit the stream". Reset on
|
||||
// unmount so a particle change mid-edit doesn't strand the stream in
|
||||
// focus mode.
|
||||
useEffect(() => {
|
||||
onEditingChange?.(editing);
|
||||
return () => onEditingChange?.(false);
|
||||
}, [editing, onEditingChange]);
|
||||
|
||||
const exitFocus = useCallback(() => {
|
||||
(document.activeElement as HTMLElement | null)?.blur();
|
||||
}, []);
|
||||
|
||||
useFixedDwell({
|
||||
id: particle.id,
|
||||
durationS: DWELL_DURATION_S,
|
||||
@@ -166,8 +185,20 @@ export function TaskParticleView({
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center bg-gradient-to-b from-black/40 via-black/20 to-black/40 px-8 pt-[var(--stream-safe-top,2rem)] pb-[var(--stream-safe-bottom,2rem)]">
|
||||
{immersive && editing && (
|
||||
<div
|
||||
className="animate-in fade-in-0 fixed inset-0 z-30 bg-black/50 backdrop-blur-md duration-200"
|
||||
onMouseDown={exitFocus}
|
||||
aria-hidden
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="scrollbar-card flex max-h-full w-full max-w-[calc(var(--message-content-width)_+_var(--message-card-padding)*2)] flex-col gap-5 overflow-y-auto overscroll-contain rounded bg-white/10 p-[var(--message-card-padding)] backdrop-blur-md"
|
||||
className={cn(
|
||||
'scrollbar-card flex max-h-full w-full max-w-[calc(var(--message-content-width)_+_var(--message-card-padding)*2)] flex-col gap-5 overflow-y-auto overscroll-contain rounded bg-white/10 p-[var(--message-card-padding)] backdrop-blur-md transition-shadow',
|
||||
immersive &&
|
||||
editing &&
|
||||
'relative z-40 shadow-2xl shadow-black/50 ring-1 ring-white/15',
|
||||
)}
|
||||
onFocusCapture={() => setEditing(true)}
|
||||
onBlurCapture={(e) => {
|
||||
if (!e.currentTarget.contains(e.relatedTarget)) setEditing(false);
|
||||
|
||||
Reference in New Issue
Block a user