fix(mobile): render quick-capture options inline inside bottom sheet modal
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
This commit is contained in:
1 parent
e8723cbf82
commit
1e47f3aeb7
2 files changed
+28
-15
No files matched your search
@@ -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>
|
||||||
|
|||||||
Reference in new issue
Block a user