feat: 添加新建任务按钮功能,聚焦待办列输入框
This commit is contained in:
@@ -46,6 +46,16 @@ describe('AppNav 导航', () => {
|
||||
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 () => {
|
||||
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="
|
||||
isExactActive
|
||||
? 'bg-slate-900 font-medium text-white shadow-sm dark:bg-zinc-100 dark:text-zinc-900'
|
||||
: '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'
|
||||
? 'bg-sky-700 font-medium text-white shadow-sm dark:bg-sky-500 dark:text-slate-950'
|
||||
: '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"
|
||||
@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 批量添加', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia())
|
||||
|
||||
@@ -14,6 +14,7 @@ defineProps<{ hiddenNote?: string; selecting?: boolean }>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
archive: []
|
||||
'new-task': []
|
||||
'batch-add': []
|
||||
'toggle-select': []
|
||||
'manage-tags': []
|
||||
@@ -177,11 +178,38 @@ defineExpose({ focusSearch })
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 操作:主操作(批量添加)+ 两个视图开关,全部平铺,不再收进菜单 -->
|
||||
<!--
|
||||
操作:主操作(新建任务)+ 三个次要操作(批量添加 / 多选 / 显示归档)。
|
||||
主次靠「实心 vs 描边」表达:真正高频的是「再加一条」,批量录入是偶发动作,
|
||||
早先把它做成唯一实心按钮,把用户最常做的事藏进了每列的输入框。
|
||||
新建任务在 ≥640 一律带文字(主操作不该在平板上退化成一个小图标),
|
||||
三个次要操作在 <1024 收成图标,靠 title / aria-label 保留可访问名。
|
||||
-->
|
||||
<div class="ml-auto flex shrink-0 items-center gap-1.5 max-sm:order-2">
|
||||
<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="批量添加"
|
||||
title="批量添加"
|
||||
@click="emit('batch-add')"
|
||||
|
||||
@@ -249,34 +249,38 @@ function remove(): void {
|
||||
<TagInput v-model="tags" :suggestions="store.allTags" />
|
||||
</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>
|
||||
<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>
|
||||
<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 ?? '—' }}
|
||||
</dd>
|
||||
|
||||
<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'">
|
||||
{{ completedLabel }}
|
||||
</span>
|
||||
</dd>
|
||||
|
||||
<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 ?? '—' }}
|
||||
</dd>
|
||||
|
||||
<template v-if="archivedAtText">
|
||||
<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 }}
|
||||
</dd>
|
||||
</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 键盘移动', () => {
|
||||
type Direction = 'left' | 'right' | 'up' | 'down'
|
||||
|
||||
|
||||
@@ -141,6 +141,15 @@ useKeyboardShortcuts({
|
||||
'/': whenNoPanel(() => toolbarRef.value?.focusSearch()),
|
||||
})
|
||||
|
||||
/**
|
||||
* 「新建任务」主按钮:焦点落到第一列(待办)的快速添加输入框 ——
|
||||
* 与按 N 同一条路径,不再另造一套弹窗,保持「在列内就地录入」的心智。
|
||||
*/
|
||||
function focusNewTask(): void {
|
||||
if (ui.anyPanelOpen) return
|
||||
columnRefs.get(0)?.focusQuickAdd()
|
||||
}
|
||||
|
||||
function toggleTask(id: string): void {
|
||||
const task = store.activeTasks.find((item) => item.id === id)
|
||||
if (!task) return
|
||||
@@ -318,6 +327,7 @@ watch(filter, () => {
|
||||
:hidden-note="hiddenNote"
|
||||
:selecting="selecting"
|
||||
@archive="archiveCompleted"
|
||||
@new-task="focusNewTask"
|
||||
@batch-add="batchOpen = true"
|
||||
@toggle-select="toggleSelectMode"
|
||||
@manage-tags="ui.openTags()"
|
||||
|
||||
Reference in New Issue
Block a user