CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 6s
CI & Build / Python tests (push) Successful in 10s
CI & Build / Build & push image (push) Successful in 29s
Desktop (Tauri) / Tauri desktop (Linux) (push) Canceled after 2m36s
Desktop (Tauri) / Windows installer (cross-compiled) (push) Canceled after 2m36s
Desktop (Tauri) / Update manifest (push) Canceled after 0s
A finger cannot hover, and on a note card the hover toolbar is the only way to pin, colour or archive — so on a phone those notes could not be acted on at all. Same for deleting a checklist item, a saved view, an attachment or a preview. Marked rather than rewritten inline: one `.hover-reveal` class on the five elements and a single rule that says what it is for. The `group-hover:` reveal stays in the markup because the trigger differs per component (named groups); only the fallback is shared. `@media (hover: none)` asks the device directly, which is more honest than inferring from viewport width — a narrow window on a laptop still hovers, and a large tablet still doesn't. It sits after the Tailwind directives so it beats the opacity-0/pointer-events-none utilities on source order without !important. Tap targets follow the same shape: p-1.5 around an 18px icon lands near 30px, which is fine for a cursor and too small for a thumb. Bumped to 44px on coarse pointers only, so desktop chrome doesn't inflate. The drag grip is deliberately NOT revealed yet. Reordering still uses HTML5 drag-and-drop, which never fires from touch, so showing the handle would only promise something that does nothing. It comes with the pointer-events rewrite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
76 lines
2.3 KiB
Vue
76 lines
2.3 KiB
Vue
<script setup lang="ts">
|
||
import { ref } from "vue";
|
||
import { useNotesStore, type ChecklistItem } from "../stores/notes";
|
||
|
||
const props = defineProps<{ noteId: string; items: ChecklistItem[]; editable?: boolean }>();
|
||
const notes = useNotesStore();
|
||
const newItem = ref("");
|
||
|
||
async function addItem() {
|
||
const text = newItem.value.trim();
|
||
if (!text) return;
|
||
await notes.addItem(props.noteId, text);
|
||
newItem.value = "";
|
||
}
|
||
|
||
function toggle(item: ChecklistItem) {
|
||
void notes.updateItem(props.noteId, item.id, { checked: !item.checked });
|
||
}
|
||
|
||
function editText(item: ChecklistItem, value: string) {
|
||
if (value !== item.text) void notes.updateItem(props.noteId, item.id, { text: value });
|
||
}
|
||
|
||
function remove(item: ChecklistItem) {
|
||
void notes.deleteItem(props.noteId, item.id);
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="flex flex-col gap-1">
|
||
<div v-for="item in items" :key="item.id" class="group/item flex items-center gap-2">
|
||
<input
|
||
type="checkbox"
|
||
class="h-4 w-4 shrink-0 accent-brand"
|
||
:checked="item.checked"
|
||
@change="toggle(item)"
|
||
@click.stop
|
||
/>
|
||
<input
|
||
v-if="editable"
|
||
:value="item.text"
|
||
class="min-w-0 flex-1 bg-transparent text-sm outline-none"
|
||
:class="item.checked ? 'text-neutral-400 line-through' : ''"
|
||
@change="editText(item, ($event.target as HTMLInputElement).value)"
|
||
/>
|
||
<span
|
||
v-else
|
||
class="min-w-0 flex-1 truncate text-sm"
|
||
:class="item.checked ? 'text-neutral-400 line-through' : 'text-neutral-700 dark:text-neutral-300'"
|
||
>{{ item.text }}</span
|
||
>
|
||
<button
|
||
v-if="editable"
|
||
type="button"
|
||
class="hover-reveal text-neutral-300 opacity-0 hover:text-neutral-600 group-hover/item:opacity-100 dark:hover:text-neutral-200"
|
||
aria-label="Delete item"
|
||
@click="remove(item)"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
<form v-if="editable" class="mt-1 flex items-center gap-2" @submit.prevent="addItem">
|
||
<span class="h-4 w-4 shrink-0" />
|
||
<input
|
||
v-model="newItem"
|
||
type="text"
|
||
placeholder="+ List item"
|
||
class="min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-neutral-400"
|
||
/>
|
||
</form>
|
||
|
||
<p v-if="!editable && items.length === 0" class="text-sm italic text-neutral-400">Empty checklist</p>
|
||
</div>
|
||
</template>
|