feat: 添加新建任务按钮功能,聚焦待办列输入框
This commit is contained in:
@@ -46,6 +46,16 @@ describe('AppNav 导航', () => {
|
|||||||
expect(links[1]?.attributes('aria-current')).toBe('page')
|
expect(links[1]?.attributes('aria-current')).toBe('page')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('当前入口用主色(sky-700)铺底,与实心主按钮同一套色', async () => {
|
||||||
|
const { wrapper } = await setup('/today')
|
||||||
|
const [board, today] = wrapper.findAll('a')
|
||||||
|
|
||||||
|
expect(today?.classes()).toContain('bg-sky-700')
|
||||||
|
// 早先是接近黑的 slate-900,与页面主色脱节
|
||||||
|
expect(today?.classes()).not.toContain('bg-slate-900')
|
||||||
|
expect(board?.classes()).not.toContain('bg-sky-700')
|
||||||
|
})
|
||||||
|
|
||||||
it('点击可切换路由', async () => {
|
it('点击可切换路由', async () => {
|
||||||
const { wrapper, router } = await setup('/')
|
const { wrapper, router } = await setup('/')
|
||||||
|
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ const LINKS = [
|
|||||||
class="shrink-0 rounded-md px-3 py-1.5 text-sm whitespace-nowrap transition"
|
class="shrink-0 rounded-md px-3 py-1.5 text-sm whitespace-nowrap transition"
|
||||||
:class="
|
:class="
|
||||||
isExactActive
|
isExactActive
|
||||||
? 'bg-slate-900 font-medium text-white shadow-sm dark:bg-zinc-100 dark:text-zinc-900'
|
? 'bg-sky-700 font-medium text-white shadow-sm dark:bg-sky-500 dark:text-slate-950'
|
||||||
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100'
|
: 'text-slate-600 hover:bg-sky-50 hover:text-sky-800 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-100'
|
||||||
"
|
"
|
||||||
:aria-current="isExactActive ? 'page' : undefined"
|
:aria-current="isExactActive ? 'page' : undefined"
|
||||||
@click="navigate"
|
@click="navigate"
|
||||||
|
|||||||
@@ -189,6 +189,41 @@ describe('BoardToolbar 归档', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('BoardToolbar 主次按钮', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createPinia())
|
||||||
|
})
|
||||||
|
|
||||||
|
it('点击「新建任务」emit new-task', async () => {
|
||||||
|
const { wrapper } = setup()
|
||||||
|
|
||||||
|
await buttonByText(wrapper, '新建任务')?.trigger('click')
|
||||||
|
|
||||||
|
expect(wrapper.emitted('new-task')).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('实心主按钮只有「新建任务」,「批量添加」降为描边次要按钮', () => {
|
||||||
|
const { wrapper } = setup()
|
||||||
|
|
||||||
|
const primary = buttonByText(wrapper, '新建任务')
|
||||||
|
const batch = buttonByText(wrapper, '批量添加')
|
||||||
|
|
||||||
|
expect(primary?.classes()).toContain('bg-sky-700')
|
||||||
|
expect(primary?.classes()).toContain('text-white')
|
||||||
|
expect(batch?.classes()).toContain('bg-white')
|
||||||
|
expect(batch?.classes()).not.toContain('bg-sky-700')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('「新建任务」在平板上仍带文字,不退化成纯图标', () => {
|
||||||
|
const { wrapper } = setup()
|
||||||
|
|
||||||
|
const button = buttonByText(wrapper, '新建任务')
|
||||||
|
// 主操作只在手机(<640)收成图标
|
||||||
|
expect(button?.find('span').classes()).toContain('max-sm:hidden')
|
||||||
|
expect(button?.classes()).not.toContain('max-lg:w-9')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('BoardToolbar 批量添加', () => {
|
describe('BoardToolbar 批量添加', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setActivePinia(createPinia())
|
setActivePinia(createPinia())
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ defineProps<{ hiddenNote?: string; selecting?: boolean }>()
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
archive: []
|
archive: []
|
||||||
|
'new-task': []
|
||||||
'batch-add': []
|
'batch-add': []
|
||||||
'toggle-select': []
|
'toggle-select': []
|
||||||
'manage-tags': []
|
'manage-tags': []
|
||||||
@@ -177,11 +178,38 @@ defineExpose({ focusSearch })
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 操作:主操作(批量添加)+ 两个视图开关,全部平铺,不再收进菜单 -->
|
<!--
|
||||||
|
操作:主操作(新建任务)+ 三个次要操作(批量添加 / 多选 / 显示归档)。
|
||||||
|
主次靠「实心 vs 描边」表达:真正高频的是「再加一条」,批量录入是偶发动作,
|
||||||
|
早先把它做成唯一实心按钮,把用户最常做的事藏进了每列的输入框。
|
||||||
|
新建任务在 ≥640 一律带文字(主操作不该在平板上退化成一个小图标),
|
||||||
|
三个次要操作在 <1024 收成图标,靠 title / aria-label 保留可访问名。
|
||||||
|
-->
|
||||||
<div class="ml-auto flex shrink-0 items-center gap-1.5 max-sm:order-2">
|
<div class="ml-auto flex shrink-0 items-center gap-1.5 max-sm:order-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex h-9 items-center gap-1.5 rounded-lg bg-sky-700 px-3 text-[13px] font-medium whitespace-nowrap text-white transition hover:bg-sky-800 active:scale-[0.98] max-lg:w-9 max-lg:justify-center max-lg:px-0 dark:bg-sky-500 dark:text-slate-950 dark:hover:bg-sky-400"
|
class="flex h-9 items-center gap-1.5 rounded-lg bg-sky-700 px-3 text-[13px] font-medium whitespace-nowrap text-white transition hover:bg-sky-800 active:scale-[0.98] dark:bg-sky-500 dark:text-slate-950 dark:hover:bg-sky-400"
|
||||||
|
aria-label="新建任务"
|
||||||
|
title="新建任务(N)"
|
||||||
|
@click="emit('new-task')"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
class="size-4 shrink-0"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.7"
|
||||||
|
stroke-linecap="round"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path d="M10 4.5v11M4.5 10h11" />
|
||||||
|
</svg>
|
||||||
|
<span class="max-sm:hidden">新建任务</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex h-9 items-center gap-1.5 rounded-lg border border-slate-200 bg-white px-2.5 text-[13px] whitespace-nowrap text-slate-600 transition hover:border-slate-300 hover:bg-slate-50 max-lg:w-9 max-lg:justify-center max-lg:px-0 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-300 dark:hover:border-zinc-600 dark:hover:bg-zinc-800"
|
||||||
aria-label="批量添加"
|
aria-label="批量添加"
|
||||||
title="批量添加"
|
title="批量添加"
|
||||||
@click="emit('batch-add')"
|
@click="emit('batch-add')"
|
||||||
|
|||||||
@@ -249,34 +249,38 @@ function remove(): void {
|
|||||||
<TagInput v-model="tags" :suggestions="store.allTags" />
|
<TagInput v-model="tags" :suggestions="store.allTags" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section
|
<!--
|
||||||
class="flex flex-col gap-2 rounded-md border border-slate-200 bg-slate-50/70 px-3 py-2.5 dark:border-zinc-800 dark:bg-zinc-800/40"
|
时间:标题与「标签」同级平铺,明细单独用一个描边框圈起来。
|
||||||
aria-labelledby="task-time-heading"
|
只靠字号(标题 text-sm / 明细 text-xs)分层,框只负责把只读信息
|
||||||
>
|
和上方的可编辑字段区分开,不参与层级表达。
|
||||||
|
-->
|
||||||
|
<section class="flex flex-col gap-1.5" aria-labelledby="task-time-heading">
|
||||||
<h3 id="task-time-heading" class="text-sm font-medium text-slate-600 dark:text-zinc-400">
|
<h3 id="task-time-heading" class="text-sm font-medium text-slate-600 dark:text-zinc-400">
|
||||||
时间
|
时间
|
||||||
</h3>
|
</h3>
|
||||||
<dl class="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-xs">
|
<dl
|
||||||
|
class="grid grid-cols-[auto_1fr] items-baseline gap-x-3 gap-y-0.5 rounded-md border border-slate-300 px-3 py-2 text-xs dark:border-zinc-700"
|
||||||
|
>
|
||||||
<dt class="text-slate-400 dark:text-zinc-500">创建</dt>
|
<dt class="text-slate-400 dark:text-zinc-500">创建</dt>
|
||||||
<dd data-role="created-at" class="text-slate-600 dark:text-zinc-300">
|
<dd data-role="created-at" class="tabular-nums text-slate-600 dark:text-zinc-300">
|
||||||
{{ createdAtText ?? '—' }}
|
{{ createdAtText ?? '—' }}
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt class="text-slate-400 dark:text-zinc-500">完成</dt>
|
<dt class="text-slate-400 dark:text-zinc-500">完成</dt>
|
||||||
<dd data-role="completed-at" class="text-slate-600 dark:text-zinc-300">
|
<dd data-role="completed-at" class="tabular-nums text-slate-600 dark:text-zinc-300">
|
||||||
<span :class="completedAtText ? '' : 'text-slate-400 dark:text-zinc-500'">
|
<span :class="completedAtText ? '' : 'text-slate-400 dark:text-zinc-500'">
|
||||||
{{ completedLabel }}
|
{{ completedLabel }}
|
||||||
</span>
|
</span>
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt class="text-slate-400 dark:text-zinc-500">更新</dt>
|
<dt class="text-slate-400 dark:text-zinc-500">更新</dt>
|
||||||
<dd data-role="updated-at" class="text-slate-600 dark:text-zinc-300">
|
<dd data-role="updated-at" class="tabular-nums text-slate-600 dark:text-zinc-300">
|
||||||
{{ updatedAtText ?? '—' }}
|
{{ updatedAtText ?? '—' }}
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<template v-if="archivedAtText">
|
<template v-if="archivedAtText">
|
||||||
<dt class="text-slate-400 dark:text-zinc-500">归档</dt>
|
<dt class="text-slate-400 dark:text-zinc-500">归档</dt>
|
||||||
<dd data-role="archived-at" class="text-slate-600 dark:text-zinc-300">
|
<dd data-role="archived-at" class="tabular-nums text-slate-600 dark:text-zinc-300">
|
||||||
{{ archivedAtText }}
|
{{ archivedAtText }}
|
||||||
</dd>
|
</dd>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -437,6 +437,23 @@ describe('BoardView 快捷键', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('BoardView 新建任务', () => {
|
||||||
|
function buttonByText(wrapper: ReturnType<typeof mountBoard>, text: string) {
|
||||||
|
return wrapper.findAll('button').find((node) => node.text().includes(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
it('点击工具栏「新建任务」聚焦待办列的快速新增输入框', async () => {
|
||||||
|
const { pinia } = setup()
|
||||||
|
const wrapper = mountBoard(pinia)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
await buttonByText(wrapper, '新建任务')?.trigger('click')
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(document.activeElement).toBe(wrapper.find('input[aria-label="添加任务"]').element)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('BoardView 键盘移动', () => {
|
describe('BoardView 键盘移动', () => {
|
||||||
type Direction = 'left' | 'right' | 'up' | 'down'
|
type Direction = 'left' | 'right' | 'up' | 'down'
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,15 @@ useKeyboardShortcuts({
|
|||||||
'/': whenNoPanel(() => toolbarRef.value?.focusSearch()),
|
'/': whenNoPanel(() => toolbarRef.value?.focusSearch()),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 「新建任务」主按钮:焦点落到第一列(待办)的快速添加输入框 ——
|
||||||
|
* 与按 N 同一条路径,不再另造一套弹窗,保持「在列内就地录入」的心智。
|
||||||
|
*/
|
||||||
|
function focusNewTask(): void {
|
||||||
|
if (ui.anyPanelOpen) return
|
||||||
|
columnRefs.get(0)?.focusQuickAdd()
|
||||||
|
}
|
||||||
|
|
||||||
function toggleTask(id: string): void {
|
function toggleTask(id: string): void {
|
||||||
const task = store.activeTasks.find((item) => item.id === id)
|
const task = store.activeTasks.find((item) => item.id === id)
|
||||||
if (!task) return
|
if (!task) return
|
||||||
@@ -318,6 +327,7 @@ watch(filter, () => {
|
|||||||
:hidden-note="hiddenNote"
|
:hidden-note="hiddenNote"
|
||||||
:selecting="selecting"
|
:selecting="selecting"
|
||||||
@archive="archiveCompleted"
|
@archive="archiveCompleted"
|
||||||
|
@new-task="focusNewTask"
|
||||||
@batch-add="batchOpen = true"
|
@batch-add="batchOpen = true"
|
||||||
@toggle-select="toggleSelectMode"
|
@toggle-select="toggleSelectMode"
|
||||||
@manage-tags="ui.openTags()"
|
@manage-tags="ui.openTags()"
|
||||||
|
|||||||
Reference in New Issue
Block a user