M2 labels frontend: sidebar shell + label filter, chips, picker, manage
- AppShell: persistent left sidebar (Notes · labels · Archive · Trash) + top bar (site name, admin Settings, sign out); BoardView now renders inside it. - labels store (list/create/rename/delete); Note gains labels[]; notes store gains setLabels + label-aware reconcile + /api/notes?label= loading. - /label/:id route → label-filtered board. - LabelPicker (tag a note, create-on-the-fly) in the editor; label chips shown on cards and in the editor; LabelsModal to create/rename/delete labels. - api client PUT; new icons (note/tag/pencil/plus/check); nav-link styles. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useLabelsStore } from "../stores/labels";
|
||||
import Icon from "./Icon.vue";
|
||||
|
||||
const emit = defineEmits<{ (e: "close"): void }>();
|
||||
const labels = useLabelsStore();
|
||||
const newName = ref("");
|
||||
|
||||
async function add() {
|
||||
const name = newName.value.trim();
|
||||
if (!name) return;
|
||||
await labels.create(name);
|
||||
newName.value = "";
|
||||
}
|
||||
|
||||
async function rename(id: string, value: string) {
|
||||
const name = value.trim();
|
||||
if (name) await labels.rename(id, name);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="fixed inset-0 z-40 flex items-start justify-center overflow-y-auto bg-black/40 p-4 pt-[12vh]"
|
||||
@mousedown.self="emit('close')"
|
||||
>
|
||||
<div
|
||||
class="w-full max-w-sm rounded-xl border border-neutral-200 bg-white shadow-xl dark:border-neutral-700 dark:bg-neutral-900"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
@keydown.esc="emit('close')"
|
||||
>
|
||||
<div class="flex items-center justify-between border-b border-neutral-100 px-4 py-3 dark:border-neutral-800">
|
||||
<h2 class="text-sm font-semibold">Edit labels</h2>
|
||||
<button type="button" class="icon-btn" aria-label="Close" @click="emit('close')"><Icon name="close" /></button>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 p-4">
|
||||
<form class="flex items-center gap-2" @submit.prevent="add">
|
||||
<Icon name="plus" />
|
||||
<input
|
||||
v-model="newName"
|
||||
type="text"
|
||||
placeholder="Create label"
|
||||
class="flex-1 rounded-md border border-neutral-300 bg-white px-2 py-1.5 text-sm outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:bg-neutral-800"
|
||||
/>
|
||||
</form>
|
||||
<ul class="flex flex-col gap-1 pt-1">
|
||||
<li v-for="lb in labels.items" :key="lb.id" class="flex items-center gap-2">
|
||||
<Icon name="tag" />
|
||||
<input
|
||||
:value="lb.name"
|
||||
class="flex-1 rounded-md bg-transparent px-2 py-1.5 text-sm outline-none focus-visible:ring-2 focus-visible:ring-brand"
|
||||
@change="rename(lb.id, ($event.target as HTMLInputElement).value)"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="icon-btn"
|
||||
title="Delete label"
|
||||
aria-label="Delete label"
|
||||
@click="labels.remove(lb.id)"
|
||||
>
|
||||
<Icon name="trash" />
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-if="!labels.items.length" class="py-2 text-center text-xs text-neutral-400">
|
||||
No labels yet — create one above.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user