feat(triage): bigger phone actions like the long-press sheet, folded queue
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m22s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m22s
chore(release): v0.11.1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9fb33b23ec
commit
3b9b1a5f8e
4 files changed
+121
-68
No files matched your search
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mindsift-frontend",
|
||||
"version": "0.11.0",
|
||||
"version": "0.11.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mindsift-frontend",
|
||||
"version": "0.11.0",
|
||||
"version": "0.11.1",
|
||||
"license": "Unlicense",
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "5.3.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "mindsift-frontend",
|
||||
"private": true,
|
||||
"version": "0.11.0",
|
||||
"version": "0.11.1",
|
||||
"license": "Unlicense",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -18,6 +18,9 @@ import {
|
||||
Edit3,
|
||||
Layers,
|
||||
MoreHorizontal,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ChevronDown,
|
||||
} from 'lucide-react';
|
||||
import { SharedList, Thought } from '../../lib/types';
|
||||
import { addDays, formatDue } from '../../lib/due';
|
||||
@@ -227,6 +230,9 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
||||
const [cards, setCards] = useState<Thought[]>(deck);
|
||||
const [lastTriaged, setLastTriaged] = useState<Thought | null>(null);
|
||||
const [showDateModal, setShowDateModal] = useState(false);
|
||||
// Phones: the waiting queue is folded until asked for
|
||||
const [showQueueMobile, setShowQueueMobile] = useState(false);
|
||||
const queueRef = useRef<HTMLDivElement | null>(null);
|
||||
const [taskDueDate, setTaskDueDate] = useState(() => addDays(1));
|
||||
const [showListSelector, setShowListSelector] = useState(false);
|
||||
|
||||
@@ -725,70 +731,96 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mobile Quick Action Buttons under the card (Touch targets >= 44px) */}
|
||||
<div className="grid grid-cols-4 gap-1.5 sm:hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAction('discard')}
|
||||
title="Wegwerfen"
|
||||
className="btn-action h-11 px-1 flex flex-col items-center justify-center gap-0.5 text-[11px] whitespace-nowrap touch-manipulation cursor-pointer"
|
||||
>
|
||||
<X className="w-4 h-4 text-muted flex-shrink-0" />
|
||||
<span>Wegwerfen</span>
|
||||
</button>
|
||||
{/* Phones: the swipe actions on top (← discard, → task), below a 2×2 block like the long-press sheet */}
|
||||
<div className="sm:hidden space-y-2">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAction('discard')}
|
||||
className="min-h-[52px] px-3 py-2 rounded-xl bg-status-err-bg border border-status-err/30 text-status-err text-sm font-semibold flex items-center justify-center gap-2 touch-manipulation cursor-pointer active:scale-98"
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
<span>Wegwerfen</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAction('keep', { convertTo: 'task' })}
|
||||
className="min-h-[52px] px-3 py-2 rounded-xl bg-accent text-on-accent text-sm font-semibold flex items-center justify-center gap-2 touch-manipulation cursor-pointer active:scale-98"
|
||||
>
|
||||
<span>Aufgabe</span>
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={writableLists.length === 0}
|
||||
onClick={() => {
|
||||
if (singleWritableList) {
|
||||
handleAction('keep', {
|
||||
convertTo: 'list_item',
|
||||
targetListId: singleWritableList.id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
setShowDateModal(false);
|
||||
setShowListSelector((prev) => !prev);
|
||||
}}
|
||||
title={
|
||||
writableLists.length === 0
|
||||
? 'Keine Liste vorhanden'
|
||||
: singleWritableList
|
||||
? `Auf ${singleWritableList.title}`
|
||||
: 'Auf eine geteilte Liste setzen'
|
||||
}
|
||||
className={`btn-action h-11 px-1 flex flex-col items-center justify-center gap-0.5 text-[11px] whitespace-nowrap touch-manipulation cursor-pointer ${
|
||||
writableLists.length === 0
|
||||
? 'opacity-40 cursor-not-allowed'
|
||||
: showListSelector
|
||||
? 'border-accent text-accent bg-accent-dim'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<ShoppingCart className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<span className="truncate max-w-full">{singleWritableList ? singleWritableList.title : 'Liste'}</span>
|
||||
</button>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
disabled={writableLists.length === 0}
|
||||
onClick={() => {
|
||||
if (singleWritableList) {
|
||||
handleAction('keep', { convertTo: 'list_item', targetListId: singleWritableList.id });
|
||||
return;
|
||||
}
|
||||
setShowDateModal(false);
|
||||
setShowListSelector((prev) => !prev);
|
||||
}}
|
||||
className={`min-h-[52px] px-3.5 py-2.5 rounded-xl bg-canvas hover:bg-panel-elevated border border-border text-left flex items-center gap-3 transition-colors cursor-pointer active:scale-98 touch-manipulation ${
|
||||
writableLists.length === 0 ? 'opacity-40 cursor-not-allowed' : showListSelector ? 'border-accent bg-accent-dim' : ''
|
||||
}`}
|
||||
>
|
||||
<ShoppingCart className="w-5 h-5 text-accent flex-shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<div className="text-sm font-semibold text-main">Auf Liste</div>
|
||||
<div className="text-[11px] text-muted truncate">
|
||||
{writableLists.length === 0
|
||||
? 'Keine Liste vorhanden'
|
||||
: singleWritableList
|
||||
? singleWritableList.title
|
||||
: 'Einkauf / Geteilt'}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAction('keep', { convertTo: 'document' })}
|
||||
title="In Notiz umwandeln"
|
||||
className="btn-action h-11 px-1 flex flex-col items-center justify-center gap-0.5 text-[11px] whitespace-nowrap touch-manipulation cursor-pointer"
|
||||
>
|
||||
<FileText className="w-4 h-4 text-accent flex-shrink-0" />
|
||||
<span>Notiz</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowListSelector(false);
|
||||
setShowDateModal((prev) => !prev);
|
||||
}}
|
||||
className={`min-h-[52px] px-3.5 py-2.5 rounded-xl bg-canvas hover:bg-panel-elevated border border-border text-left flex items-center gap-3 transition-colors cursor-pointer active:scale-98 touch-manipulation ${showDateModal ? 'border-accent bg-accent-dim' : ''}`}
|
||||
>
|
||||
<Calendar className="w-5 h-5 text-accent flex-shrink-0" />
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-main">Mit Termin</div>
|
||||
<div className="text-[11px] text-muted">Datum wählen</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAction('keep', { convertTo: 'task' })}
|
||||
title="Als Aufgabe übernehmen"
|
||||
className="btn-primary h-11 px-1 flex flex-col items-center justify-center gap-0.5 text-[11px] font-semibold whitespace-nowrap touch-manipulation cursor-pointer"
|
||||
>
|
||||
<CheckSquare className="w-4 h-4 flex-shrink-0" />
|
||||
<span>Aufgabe</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleAction('keep', { convertTo: 'document' })}
|
||||
className="min-h-[52px] px-3.5 py-2.5 rounded-xl bg-canvas hover:bg-panel-elevated border border-border text-left flex items-center gap-3 transition-colors cursor-pointer active:scale-98 touch-manipulation"
|
||||
>
|
||||
<FileText className="w-5 h-5 text-accent flex-shrink-0" />
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-main">Als Notiz</div>
|
||||
<div className="text-[11px] text-muted">Im Workspace</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={cards.length <= 1}
|
||||
onClick={() => handleSkipCard()}
|
||||
className={`min-h-[52px] px-3.5 py-2.5 rounded-xl bg-canvas hover:bg-panel-elevated border border-border text-left flex items-center gap-3 transition-colors cursor-pointer active:scale-98 touch-manipulation ${cards.length <= 1 ? 'opacity-40 cursor-not-allowed' : ''}`}
|
||||
>
|
||||
<SkipForward className="w-5 h-5 text-muted flex-shrink-0" />
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-main">Später</div>
|
||||
<div className="text-[11px] text-muted">Ans Ende schieben</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Primary Triage Decision Bar: Hidden on mobile (handled via Mobile Quick Actions / Swipe), shown on Desktop */}
|
||||
@@ -881,8 +913,23 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
||||
|
||||
{/* Waiting Quick Dump Queue List (Touch-sized & Swipeable Left/Right) */}
|
||||
{cards.length > 1 && (
|
||||
<div className="pt-2">
|
||||
<div className="flex items-center justify-between mb-2 px-1">
|
||||
<div className="pt-2" ref={queueRef}>
|
||||
{/* Phones: the queue stays folded so the card and its buttons fit on the screen */}
|
||||
{!showQueueMobile && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShowQueueMobile(true);
|
||||
window.setTimeout(() => queueRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 50);
|
||||
}}
|
||||
className="sm:hidden w-full min-h-[48px] px-3 rounded-xl border border-border bg-panel text-sm font-medium text-main flex items-center justify-center gap-2 touch-manipulation cursor-pointer"
|
||||
>
|
||||
<Layers className="w-4 h-4 text-accent" />
|
||||
<span>Warteschlange anzeigen ({cards.length - 1})</span>
|
||||
<ChevronDown className="w-4 h-4 text-muted" />
|
||||
</button>
|
||||
)}
|
||||
<div className={`${showQueueMobile ? 'flex' : 'hidden sm:flex'} items-center justify-between mb-2 px-1`}>
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-muted flex items-center gap-1.5">
|
||||
<Layers className="w-3.5 h-3.5 text-accent" />
|
||||
Warteschlange ({cards.length - 1})
|
||||
@@ -892,7 +939,7 @@ export const TriageDeck: React.FC<TriageDeckProps> = ({
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="panel-surface divide-y divide-border max-h-80 overflow-y-auto overflow-x-hidden">
|
||||
<div className={`${showQueueMobile ? 'block' : 'hidden sm:block'} panel-surface divide-y divide-border max-h-80 overflow-y-auto overflow-x-hidden`}>
|
||||
{cards.slice(1).map((item) => (
|
||||
<SwipeableQueueItem
|
||||
key={item.id}
|
||||
|
||||
Reference in new issue
Block a user