S1: frontend infra — AsyncState/EmptyState + useNoteEditor, adopted in BoardView
M9 section S1, commit 4 (frontend). Introduce the shared UI primitives the 7 data views + 5 editor hosts were hand-rolling: - components/AsyncState.vue: the loading / error(+Retry) wrapper (emits `retry`). - components/EmptyState.vue: the centered title/subtitle "nothing here" block. - composables/useNoteEditor.ts: the editing/open/close/navigate glue every editor host duplicated, as one controller (onClose hook + local-list resolution for [[wiki-link]] navigation). BoardView adopts all three: its three hand-rolled loading/error/empty blocks collapse into <AsyncState> + <EmptyState>, and its editor glue into useNoteEditor. The other views + editor hosts adopt these in the Organize (S3) and Auth (S5) sections. Behavior-preserving. Frontend has no local typecheck (rule 10); CI's vue-tsc is the gate. 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,27 @@
|
||||
<script setup lang="ts">
|
||||
// The loading / error(+retry) wrapper every data view hand-rolled. While `loading`
|
||||
// shows a spinner line; on `error` shows the message + a Retry button (emits
|
||||
// `retry`); otherwise renders the default slot (the caller decides empty vs content).
|
||||
defineProps<{ loading: boolean; error?: string; errorTitle?: string }>();
|
||||
defineEmits<{ (e: "retry"): void }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="loading" class="py-24 text-center text-sm text-neutral-400">Loading…</div>
|
||||
|
||||
<div v-else-if="error" class="py-24 text-center">
|
||||
<h2 class="text-lg font-semibold text-neutral-700 dark:text-neutral-200">
|
||||
{{ errorTitle ?? "Something went wrong" }}
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-neutral-400">{{ error }}</p>
|
||||
<button
|
||||
type="button"
|
||||
class="mt-3 rounded-md border border-neutral-300 px-3 py-1.5 text-sm hover:bg-neutral-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand dark:border-neutral-700 dark:hover:bg-neutral-800"
|
||||
@click="$emit('retry')"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<slot v-else />
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
// The centered "nothing here" block every data view renders. Title + optional
|
||||
// subtitle; an optional default slot for a call-to-action beneath.
|
||||
defineProps<{ title: string; subtitle?: string }>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="py-24 text-center">
|
||||
<h2 class="text-lg font-semibold text-neutral-700 dark:text-neutral-200">{{ title }}</h2>
|
||||
<p v-if="subtitle" class="mt-1 text-sm text-neutral-400">{{ subtitle }}</p>
|
||||
<div v-if="$slots.default" class="mt-3"><slot /></div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user