feat: Introduce a generic inline rename input component for document editing
This commit is contained in:
@@ -62,11 +62,12 @@ const DesktopDocumentContainer: React.FC<React.ComponentProps<typeof DesktopDocu
|
||||
onDeselect: (ids: string[]) => void;
|
||||
onDocumentActivate?: (id: string, event?: any) => void;
|
||||
selection: string[];
|
||||
requestCanvasFocus?: () => void;
|
||||
}> = React.memo((props) => {
|
||||
const { layoutCard, selected, onSelect, onDeselect, onDocumentActivate, selection } = props;
|
||||
const { layoutCard, selected, onSelect, onDeselect, onDocumentActivate, selection, requestCanvasFocus } = props;
|
||||
|
||||
// We assume layoutCard is always present in this context
|
||||
const cardPointerHandlers = useCardPointer(layoutCard!, !!selected, selection, onSelect, onDeselect, onDocumentActivate);
|
||||
const cardPointerHandlers = useCardPointer(layoutCard!, !!selected, selection, onSelect, onDeselect, onDocumentActivate, requestCanvasFocus);
|
||||
|
||||
return (
|
||||
<DesktopDocumentCard
|
||||
@@ -194,7 +195,11 @@ const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({
|
||||
}, [layoutStore.items]);
|
||||
|
||||
const handleShellKeyDown = useCallback(() => { }, []);
|
||||
const focusShell = useCallback(() => { }, []);
|
||||
const focusShell = useCallback(() => {
|
||||
if (containerRef.current) {
|
||||
containerRef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Tag Interactions
|
||||
const tagInteractions = useDeskTagInteractions({
|
||||
@@ -209,6 +214,11 @@ const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
const handleWindowKeyDown = (e: KeyboardEvent) => {
|
||||
// Only handle events if the container itself is the target (focused)
|
||||
if (e.target !== containerRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Space preview logic
|
||||
if (e.code === 'Space' && selectedDocumentIds.length > 0) {
|
||||
const lastId = selectedDocumentIds[selectedDocumentIds.length - 1];
|
||||
@@ -422,6 +432,7 @@ const DesktopWorkspaceContent: React.FC<DesktopWorkspaceProps> = ({
|
||||
handleSelectionChange(Array.from(newSelection));
|
||||
}}
|
||||
selection={selectedDocumentIds}
|
||||
requestCanvasFocus={focusShell}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -14,7 +14,8 @@ export const useCardPointer = (
|
||||
selection: string[],
|
||||
onSelect: (ids: string[], extend?: boolean) => void,
|
||||
onDeselect: (ids: string[]) => void,
|
||||
onDocumentActivate?: (id: string, event?: React.PointerEvent) => void
|
||||
onDocumentActivate?: (id: string, event?: React.PointerEvent) => void,
|
||||
requestCanvasFocus?: () => void
|
||||
) => {
|
||||
const [state, setState] = React.useState<PointerState>('idle');
|
||||
const initialPosition = useRef<{ x: number, y: number } | null>(null);
|
||||
@@ -55,6 +56,8 @@ export const useCardPointer = (
|
||||
// Register pointer with card ID
|
||||
addPointer(e.pointerId, card.id);
|
||||
|
||||
requestCanvasFocus?.();
|
||||
|
||||
setState('click');
|
||||
initialPosition.current = { x: e.clientX, y: e.clientY };
|
||||
lastPosition.current = { x: e.clientX, y: e.clientY };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState, type FormEvent } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { EditIcon, IconX, CheckIcon, PlusIcon } from '../ui/icons';
|
||||
import InlineRenameInput from './components/InlineRenameInput';
|
||||
import SelectionAssignmentMenu, {
|
||||
SelectionAssignmentMenuItem,
|
||||
type NormalizedSelectionAssignmentItem,
|
||||
@@ -631,38 +632,21 @@ const DocumentSummarySection: React.FC<DocumentSummarySectionProps> = ({
|
||||
}
|
||||
|
||||
const renderTitleEditForm = (extraClassName?: string) => (
|
||||
<form className={`doc-title-edit${extraClassName ? ` ${extraClassName}` : ''}`} onSubmit={submitTitleEdit}>
|
||||
<input
|
||||
value={titleDraft}
|
||||
onChange={(event) => {
|
||||
setTitleDraft(event.target.value);
|
||||
if (titleError) {
|
||||
setTitleError(null);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
cancelTitleEdit();
|
||||
}
|
||||
}}
|
||||
aria-label="Document title"
|
||||
autoFocus
|
||||
disabled={titleSaving}
|
||||
/>
|
||||
<button type="submit" className="icon-button icon-button--accent" disabled={titleSaving} aria-label="Save title">
|
||||
<CheckIcon size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button"
|
||||
onClick={cancelTitleEdit}
|
||||
disabled={titleSaving}
|
||||
aria-label="Cancel"
|
||||
>
|
||||
<IconX size={16} />
|
||||
</button>
|
||||
</form>
|
||||
<InlineRenameInput
|
||||
value={titleDraft}
|
||||
onChange={(value) => {
|
||||
setTitleDraft(value);
|
||||
if (titleError) {
|
||||
setTitleError(null);
|
||||
}
|
||||
}}
|
||||
onSubmit={() => submitTitleEdit({ preventDefault: () => { } } as any)}
|
||||
onCancel={cancelTitleEdit}
|
||||
isSaving={titleSaving}
|
||||
className={`doc-title-edit${extraClassName ? ` ${extraClassName}` : ''}`}
|
||||
aria-label="Document title"
|
||||
autoFocus
|
||||
/>
|
||||
);
|
||||
|
||||
const titleMetaDisplay = editableTitle && isTitleEditing
|
||||
@@ -685,32 +669,21 @@ const DocumentSummarySection: React.FC<DocumentSummarySectionProps> = ({
|
||||
);
|
||||
|
||||
const issuedDisplay = editableIssued && isIssuedEditing ? (
|
||||
<form className="doc-issued-edit" onSubmit={submitIssuedEdit}>
|
||||
<input
|
||||
type="date"
|
||||
value={issuedDraft}
|
||||
onChange={(event) => {
|
||||
setIssuedDraft(event.target.value);
|
||||
if (issuedError) {
|
||||
setIssuedError(null);
|
||||
}
|
||||
}}
|
||||
aria-label="Issued on"
|
||||
disabled={issuedSaving}
|
||||
/>
|
||||
<button type="submit" className="icon-button icon-button--accent" disabled={issuedSaving} aria-label="Save issued date">
|
||||
<CheckIcon size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="icon-button"
|
||||
onClick={cancelIssuedEdit}
|
||||
disabled={issuedSaving}
|
||||
aria-label="Cancel"
|
||||
>
|
||||
<IconX size={16} />
|
||||
</button>
|
||||
</form>
|
||||
<InlineRenameInput
|
||||
type="date"
|
||||
value={issuedDraft}
|
||||
onChange={(value) => {
|
||||
setIssuedDraft(value);
|
||||
if (issuedError) {
|
||||
setIssuedError(null);
|
||||
}
|
||||
}}
|
||||
onSubmit={() => submitIssuedEdit({ preventDefault: () => { } } as any)}
|
||||
onCancel={cancelIssuedEdit}
|
||||
isSaving={issuedSaving}
|
||||
className="doc-issued-edit"
|
||||
aria-label="Issued on"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<span className="detail-meta__value">{issuedDateLabel || 'Not set'}</span>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { CheckIcon, CloseIcon } from '../../ui/icons';
|
||||
|
||||
interface InlineRenameInputProps {
|
||||
interface InlineRenameInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'onSubmit' | 'value'> {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onSubmit: () => void;
|
||||
@@ -21,11 +21,13 @@ const InlineRenameInput: React.FC<InlineRenameInputProps> = ({
|
||||
canSubmit = true,
|
||||
inputRef,
|
||||
className = 'doc-title-edit',
|
||||
type = 'text',
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<span className={className}>
|
||||
<input
|
||||
type="text"
|
||||
type={type}
|
||||
ref={inputRef}
|
||||
value={value}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
@@ -45,6 +47,7 @@ const InlineRenameInput: React.FC<InlineRenameInputProps> = ({
|
||||
onCancel();
|
||||
}
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -48,6 +48,11 @@ export const useDocumentsNavigation = ({
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
// Only handle events that target the container directly
|
||||
if (event.target !== event.currentTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { key, shiftKey } = event;
|
||||
const triggers = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', 'Enter', ' ', 'Space', 'Spacebar'];
|
||||
if (!triggers.includes(key)) {
|
||||
|
||||
Reference in New Issue
Block a user