fix(mobile): render quick-capture options inline inside bottom sheet modal
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s

This commit is contained in:
sandrodach committed 2026-10-25 21:53:04 +01:00
1 parent e8723cbf82
commit 1e47f3aeb7
2 files changed
+28 -15

No files matched your search

+3 -1
View File
@@ -1022,7 +1022,7 @@ export const App: React.FC = () => {
onClick={() => setShowMobileCapture(false)} onClick={() => setShowMobileCapture(false)}
> >
<div <div
className="w-full bg-panel border-t border-border rounded-t-2xl shadow-panel p-4 pb-[calc(1.5rem+env(safe-area-inset-bottom))] animate-in slide-in-from-bottom duration-200" className="w-full max-h-[85vh] overflow-y-auto bg-panel border-t border-border rounded-t-2xl shadow-panel p-4 pb-[calc(1.5rem+env(safe-area-inset-bottom))] animate-in slide-in-from-bottom duration-200"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
{/* Header */} {/* Header */}
@@ -1045,6 +1045,8 @@ export const App: React.FC = () => {
{/* InstantCaptureBar */} {/* InstantCaptureBar */}
<div className="pt-1"> <div className="pt-1">
<InstantCaptureBar <InstantCaptureBar
inlineOptions={true}
defaultShowOptions={true}
onCaptured={(thought, target) => { onCaptured={(thought, target) => {
handleCaptured(thought, target); handleCaptured(thought, target);
setShowMobileCapture(false); setShowMobileCapture(false);
@@ -21,19 +21,23 @@ interface InstantCaptureBarProps {
onCaptured: (thought: Thought, target?: CaptureTarget) => void; onCaptured: (thought: Thought, target?: CaptureTarget) => void;
lists?: SharedList[]; lists?: SharedList[];
autoFocusTrigger?: number; autoFocusTrigger?: number;
inlineOptions?: boolean;
defaultShowOptions?: boolean;
} }
export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
onCaptured, onCaptured,
lists = [], lists = [],
autoFocusTrigger = 0, autoFocusTrigger = 0,
inlineOptions = false,
defaultShowOptions = false,
}) => { }) => {
const [content, setContent] = useState(''); const [content, setContent] = useState('');
const [priority, setPriority] = useState<PriorityLevel>('normal'); const [priority, setPriority] = useState<PriorityLevel>('normal');
const [dueDate, setDueDate] = useState(''); const [dueDate, setDueDate] = useState('');
const [target, setTarget] = useState<CaptureTarget>('inbox'); const [target, setTarget] = useState<CaptureTarget>('inbox');
const [targetListId, setTargetListId] = useState<string>(''); const [targetListId, setTargetListId] = useState<string>('');
const [showOptions, setShowOptions] = useState(false); const [showOptions, setShowOptions] = useState(defaultShowOptions);
const [isListening, setIsListening] = useState(false); const [isListening, setIsListening] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [justSavedLabel, setJustSavedLabel] = useState<string | null>(null); const [justSavedLabel, setJustSavedLabel] = useState<string | null>(null);
@@ -72,6 +76,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
// Close dropdown on outside click // Close dropdown on outside click
useEffect(() => { useEffect(() => {
if (inlineOptions) return;
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(e.target as Node)) { if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
setShowOptions(false); setShowOptions(false);
@@ -79,7 +84,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
}; };
document.addEventListener('mousedown', handleClickOutside); document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside);
}, []); }, [inlineOptions]);
// Global keyboard shortcut: 'c' or '/' focuses the Instant Capture input immediately // Global keyboard shortcut: 'c' or '/' focuses the Instant Capture input immediately
useEffect(() => { useEffect(() => {
@@ -293,7 +298,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
type="button" type="button"
onClick={() => setShowOptions((prev) => !prev)} onClick={() => setShowOptions((prev) => !prev)}
title="Ziel wählen (Quick Dump, direkt als Aufgabe, auf Liste oder als Notiz) & Optionen" title="Ziel wählen (Quick Dump, direkt als Aufgabe, auf Liste oder als Notiz) & Optionen"
className={`h-6 px-2.5 capture-pill rounded-full text-[11px] flex items-center gap-1 transition-colors ${ className={`h-6 px-2.5 capture-pill rounded-full text-[11px] flex items-center gap-1 transition-colors cursor-pointer ${
showOptions || target !== 'inbox' || priority !== 'normal' || dueDate showOptions || target !== 'inbox' || priority !== 'normal' || dueDate
? 'bg-accent-dim text-accent border border-accent-border font-medium' ? 'bg-accent-dim text-accent border border-accent-border font-medium'
: 'text-muted hover:text-main hover:bg-panel-elevated border border-transparent' : 'text-muted hover:text-main hover:bg-panel-elevated border border-transparent'
@@ -308,11 +313,11 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
) : ( ) : (
<Layers className="w-3 h-3" /> <Layers className="w-3 h-3" />
)} )}
<span className="hidden md:inline max-w-[110px] truncate"> <span className={`${inlineOptions ? 'inline' : 'hidden md:inline'} max-w-[110px] truncate`}>
{getTargetLabel()} {getTargetLabel()}
</span> </span>
{dueDate && <span className="hidden lg:inline text-[10px] opacity-80">• {dueDate.slice(5)}</span>} {dueDate && <span className="hidden lg:inline text-[10px] opacity-80">• {dueDate.slice(5)}</span>}
<ChevronDown className="w-3 h-3 opacity-70" /> <ChevronDown className={`w-3 h-3 opacity-70 transition-transform ${showOptions ? 'rotate-180' : ''}`} />
</button> </button>
{/* Voice Dictation Button */} {/* Voice Dictation Button */}
@@ -362,9 +367,15 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
</div> </div>
</div> </div>
{/* Expandable Target & Metadata Panel (Theme-controlled Dropdown) */} {/* Expandable Target & Metadata Panel (Theme-controlled Dropdown or Inline) */}
{showOptions && ( {showOptions && (
<div className="absolute top-full left-0 right-0 mt-1.5 z-50 p-3 panel-surface shadow-panel space-y-3 text-xs"> <div
className={
inlineOptions
? 'mt-3 p-3 panel-surface border border-border rounded-xl space-y-3 text-xs animate-in fade-in duration-150'
: 'absolute top-full left-0 right-0 mt-1.5 z-50 p-3 panel-surface shadow-panel space-y-3 text-xs'
}
>
{/* Row 1: Where should this entry go? */} {/* Row 1: Where should this entry go? */}
<div> <div>
<div className="flex items-center justify-between mb-1.5"> <div className="flex items-center justify-between mb-1.5">
@@ -386,7 +397,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
executeCapture('inbox'); executeCapture('inbox');
} }
}} }}
className={`h-8 px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors ${ className={`min-h-[36px] px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors cursor-pointer ${
target === 'inbox' target === 'inbox'
? 'bg-accent-dim border-accent text-main font-medium' ? 'bg-accent-dim border-accent text-main font-medium'
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover' : 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
@@ -405,7 +416,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
executeCapture('task'); executeCapture('task');
} }
}} }}
className={`h-8 px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors ${ className={`min-h-[36px] px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors cursor-pointer ${
target === 'task' target === 'task'
? 'bg-accent-dim border-accent text-main font-medium' ? 'bg-accent-dim border-accent text-main font-medium'
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover' : 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
@@ -424,7 +435,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
setTargetListId(lists[0].id); setTargetListId(lists[0].id);
} }
}} }}
className={`h-8 px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors ${ className={`min-h-[36px] px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors cursor-pointer ${
target === 'list_item' target === 'list_item'
? 'bg-accent-dim border-accent text-main font-medium' ? 'bg-accent-dim border-accent text-main font-medium'
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover' : 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
@@ -443,7 +454,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
executeCapture('document'); executeCapture('document');
} }
}} }}
className={`h-8 px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors ${ className={`min-h-[36px] px-2.5 border rounded-btn flex items-center gap-1.5 text-left transition-colors cursor-pointer ${
target === 'document' target === 'document'
? 'bg-accent-dim border-accent text-main font-medium' ? 'bg-accent-dim border-accent text-main font-medium'
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover' : 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
@@ -475,7 +486,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
executeCapture('list_item', lst.id); executeCapture('list_item', lst.id);
} }
}} }}
className={`h-7 px-2.5 border rounded-btn text-xs flex items-center gap-1.5 transition-colors ${ className={`min-h-[30px] px-2.5 border rounded-btn text-xs flex items-center gap-1.5 transition-colors cursor-pointer ${
isSelected isSelected
? 'bg-accent text-on-accent border-accent font-medium' ? 'bg-accent text-on-accent border-accent font-medium'
: 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover' : 'bg-canvas border-border text-muted hover:text-main hover:bg-panel-hover'
@@ -506,7 +517,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
key={p} key={p}
type="button" type="button"
onClick={() => setPriority(p)} onClick={() => setPriority(p)}
className={`px-2.5 py-0.5 border rounded-btn transition-colors text-xs ${ className={`min-h-[30px] px-2.5 py-1 border rounded-btn transition-colors text-xs cursor-pointer ${
active active
? 'bg-accent text-on-accent border-accent font-medium' ? 'bg-accent text-on-accent border-accent font-medium'
: 'bg-canvas text-muted hover:text-main border-border' : 'bg-canvas text-muted hover:text-main border-border'
@@ -527,7 +538,7 @@ export const InstantCaptureBar: React.FC<InstantCaptureBarProps> = ({
type="date" type="date"
value={dueDate} value={dueDate}
onChange={(e) => setDueDate(e.target.value)} onChange={(e) => setDueDate(e.target.value)}
className="input-field px-2 py-0.5 text-xs" className="input-field px-2.5 py-1 text-xs min-h-[30px]"
/> />
</div> </div>
</div> </div>