added templates and fixed UX for virtual keyboard opening when opening a task

This commit is contained in:
2026-01-31 17:35:22 -06:00
parent 2e17ffee27
commit 064aca3ced
4 changed files with 544 additions and 151 deletions
+71 -6
View File
@@ -1,6 +1,6 @@
import { type Component, createSignal, createEffect, onCleanup, onMount, For } from "solid-js";
import { Search, Command, Clock, ArrowUpCircle, Plus } from "lucide-solid";
import { addTask, calculateDateFromUrgency, calculateUrgencyFromDate, store, upsertMultipleTagDefinitions } from "@/store";
import { type Component, createSignal, createEffect, onCleanup, onMount, For, Show } from "solid-js";
import { addTask, calculateDateFromUrgency, calculateUrgencyFromDate, store, upsertMultipleTagDefinitions, type TaskTemplate } from "@/store";
import { Search, Command, Clock, ArrowUpCircle, Plus, Copy, ChevronDown } from "lucide-solid";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
@@ -8,6 +8,8 @@ import { TextField, TextFieldInput } from "@/components/ui/textfield";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
import { TagPicker } from "@/components/TagPicker";
import { lazy, Suspense } from "solid-js";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { toast } from "solid-sonner";
const TaskEditor = lazy(() => import("./TaskEditor").then(m => ({ default: m.TaskEditor })));
@@ -219,6 +221,29 @@ export const QuickEntry: Component = () => {
if (instance) instance.commands.setContent("");
};
const applyTemplate = (template: TaskTemplate) => {
setInput(template.title);
setPriority(template.priority.toString());
setUrgency(template.urgency.toString());
setTags([...(template.tags || [])]);
setDescription(template.content || "");
// Sync urgency to date
const level = template.urgency;
const newDateIso = calculateDateFromUrgency(level);
const dateObj = new Date(newDateIso);
const localIso = new Date(dateObj.getTime() - (dateObj.getTimezoneOffset() * 60000)).toISOString().slice(0, 16);
setDateString(localIso);
// Update editor if ready
const instance = editorInstance();
if (instance) {
instance.commands.setContent(template.content || "");
}
toast.info(`Applied template: ${template.name}`);
};
return (
<>
<Button
@@ -352,9 +377,49 @@ export const QuickEntry: Component = () => {
<span class="hidden sm:inline">K to focus Enter to Add</span>
<span class="sm:hidden">Enter to Add</span>
</div>
<Button size="sm" onClick={parseAndAdd} disabled={!input() || !priority() || !urgency()}>
Create Task
</Button>
<div class="flex items-center gap-2">
<Show when={(store.templates || []).length > 0}>
<Popover>
<PopoverTrigger
as={Button}
variant="ghost"
size="sm"
class="h-9 px-3 gap-2 hover:bg-primary/10 hover:text-primary text-muted-foreground transition-all rounded-xl border border-border/40"
>
<Copy size={14} />
<span class="text-[10px] font-bold uppercase tracking-wider hidden sm:inline">Use Template</span>
<ChevronDown size={12} class="opacity-50" />
</PopoverTrigger>
<PopoverContent class="w-64 p-2 bg-card border-border shadow-xl">
<div class="space-y-1">
<For each={store.templates}>
{(template) => (
<button
class="w-full flex items-center gap-3 p-2 rounded-lg hover:bg-muted transition-colors text-left group"
onClick={() => applyTemplate(template)}
>
<div class="w-8 h-8 rounded-lg bg-primary/10 flex items-center justify-center text-primary shrink-0 group-hover:bg-primary group-hover:text-primary-foreground transition-all">
<Copy size={14} />
</div>
<div class="min-w-0">
<p class="text-sm font-bold truncate tracking-tight">{template.name}</p>
<p class="text-[9px] uppercase font-black text-muted-foreground opacity-60 tracking-widest">
P:{template.priority} / U:{template.urgency}
</p>
</div>
</button>
)}
</For>
</div>
</PopoverContent>
</Popover>
</Show>
<Button size="sm" onClick={parseAndAdd} disabled={!input() || !priority() || !urgency()}>
Create Task
</Button>
</div>
</div>
</div>
</div>
+41 -13
View File
@@ -1,8 +1,8 @@
import { type Component, createEffect, createSignal, For } from "solid-js";
import { Sheet, SheetContent } from "@/components/ui/sheet";
import { type Task, removeTask, restoreTask, updateTask } from "@/store";
import { type Task, removeTask, restoreTask, updateTask, saveTaskAsTemplate } from "@/store";
import { Select, SelectContent, SelectItem, SelectTrigger } from "@/components/ui/select";
import { ArrowUpCircle, Clock, Calendar, Type, Trash2, X } from "lucide-solid";
import { ArrowUpCircle, Clock, Calendar, Type, Trash2, X, Copy } from "lucide-solid";
import { calculateDateFromUrgency, calculateUrgencyFromDate } from "@/store";
import { Button } from "./ui/button";
import { TagPicker } from "./TagPicker";
@@ -81,7 +81,16 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
return (
<Sheet open={props.isOpen} onOpenChange={(open) => !open && props.onClose()}>
<SheetContent hideCloseButton class="w-full sm:max-w-2xl px-0 sm:px-0 flex flex-col h-full bg-background border-l border-border shadow-2xl">
<SheetContent
hideCloseButton
onOpenAutoFocus={(e) => {
// Prevent auto-focus on mobile to avoid keyboard popup
if (window.innerWidth < 768) {
e.preventDefault();
}
}}
class="w-full sm:max-w-2xl px-0 sm:px-0 flex flex-col h-full bg-background border-l border-border shadow-2xl"
>
{/* Header Area */}
<div class="px-6 pt-6 pb-2 shrink-0">
<textarea
@@ -263,16 +272,35 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
<span class="text-xs font-bold uppercase tracking-wider">Delete</span>
</Button>
{/* Close button (Mobile only) */}
<Button
variant="ghost"
size="sm"
class="md:hidden h-9 px-3 gap-2 hover:bg-muted text-muted-foreground transition-all rounded-xl border border-border/40"
onClick={() => props.onClose()}
>
<X size={16} />
<span class="text-xs font-bold uppercase tracking-wider">Close</span>
</Button>
<div class="flex items-center gap-2">
{/* Save as Template (Desktop + Mobile) */}
<Button
variant="ghost"
size="sm"
class="h-9 px-3 gap-2 hover:bg-primary/10 hover:text-primary text-muted-foreground transition-all rounded-xl border border-border/40"
onClick={async () => {
const name = prompt("Enter a name for this template:");
if (name) {
await saveTaskAsTemplate(props.task.id, name);
toast.success(`Template "${name}" created`);
}
}}
>
<Copy size={16} />
<span class="text-xs font-bold uppercase tracking-wider">Template</span>
</Button>
{/* Close button (Mobile only) */}
<Button
variant="ghost"
size="sm"
class="md:hidden h-9 px-3 gap-2 hover:bg-muted text-muted-foreground transition-all rounded-xl border border-border/40"
onClick={() => props.onClose()}
>
<X size={16} />
<span class="text-xs font-bold uppercase tracking-wider">Close</span>
</Button>
</div>
</div>
</SheetContent>
</Sheet>