mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-07-10 01:39:25 +00:00
feat(kimi-web): consistent rules for the right-side detail layer
The transient detail panels (thinking, compaction summary, subagent detail, mobile file/media preview) now share one set of rules: - the aside is a labelled role=complementary region (aria-hidden when collapsed), - Escape closes whichever panel is open — handled in App on the capture phase so it takes precedence over the conversation's "Esc interrupts a run" handler instead of firing both, - every close button has an aria-label (not just a title) and a focus-visible ring; thinking/subagent close targets bumped to 28x28, - FilePreview toolbar buttons get focus-visible rings too.
This commit is contained in:
parent
22f6c1d265
commit
f5caf4cca7
6 changed files with 53 additions and 8 deletions
|
|
@ -1,6 +1,6 @@
|
|||
<!-- apps/kimi-web/src/App.vue -->
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, onMounted, provide, ref, watch } from 'vue';
|
||||
import { computed, nextTick, onMounted, onUnmounted, provide, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Sidebar from './components/Sidebar.vue';
|
||||
import ResizeHandle from './components/ResizeHandle.vue';
|
||||
|
|
@ -75,8 +75,33 @@ function openOnboarding(): void {
|
|||
|
||||
onMounted(() => {
|
||||
void client.load();
|
||||
// Capture-phase so Escape closes the side detail layer BEFORE the
|
||||
// conversation pane's bubble-phase handler interrupts a running prompt.
|
||||
document.addEventListener('keydown', onGlobalKeydown, true);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', onGlobalKeydown, true);
|
||||
});
|
||||
|
||||
// Escape closes whichever transient right-side detail panel is open (thinking,
|
||||
// compaction summary, subagent detail, or the mobile file/media preview).
|
||||
function closeOpenSidePanel(): boolean {
|
||||
if (thinkingVisible.value) { closeThinkingPanel(); return true; }
|
||||
if (compactionPanelVisible.value) { closeCompactionPanel(); return true; }
|
||||
if (agentPanelVisible.value) { closeAgentPanel(); return true; }
|
||||
if (sidePreviewVisible.value) { closeFilePreview(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
function onGlobalKeydown(e: KeyboardEvent): void {
|
||||
if (e.key !== 'Escape') return;
|
||||
if (closeOpenSidePanel()) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Layout: resizable session column. ResizeHandle owns the column width (with
|
||||
// localStorage persistence); we mirror it here to drive the App grid.
|
||||
|
|
@ -849,6 +874,9 @@ function openPr(url: string): void {
|
|||
v-if="!isMobile || sidePanelVisible"
|
||||
class="global-preview"
|
||||
:class="{ open: sidePanelVisible, mobile: isMobile, 'no-anim': panelDragging }"
|
||||
role="complementary"
|
||||
:aria-label="t('layout.detailPanelAria')"
|
||||
:aria-hidden="!sidePanelVisible"
|
||||
>
|
||||
<ThinkingPanel
|
||||
v-if="thinkingVisible"
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ watch(
|
|||
<span class="ap-title">{{ t('common.preview') }}</span>
|
||||
<span class="ap-sub">{{ member.name }}</span>
|
||||
<span class="ap-phase" :class="`phase-${member.phase}`">{{ phaseLabel(member.phase) }}</span>
|
||||
<button type="button" class="ap-close" :title="t('thinking.close')" @click="emit('close')">
|
||||
<button type="button" class="ap-close" :title="t('thinking.close')" :aria-label="t('thinking.close')" @click="emit('close')">
|
||||
<svg viewBox="0 0 12 12" width="11" height="11" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><line x1="2" y1="2" x2="10" y2="10"/><line x1="10" y1="2" x2="2" y2="10"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -134,8 +134,8 @@ watch(
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
|
|
@ -146,6 +146,10 @@ watch(
|
|||
background: var(--hover);
|
||||
color: var(--ink);
|
||||
}
|
||||
.ap-close:focus-visible {
|
||||
outline: 2px solid var(--blue);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.ap-body {
|
||||
flex: 1;
|
||||
|
|
|
|||
|
|
@ -439,7 +439,7 @@ function truncatePath(path: string, maxLen = 55): string {
|
|||
<svg v-if="!copied" viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="9" height="9" rx="1.5"/><path d="M6 1h7a1 1 0 0 1 1 1v7"/></svg>
|
||||
<svg v-else viewBox="0 0 16 16" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3,8 6.5,11.5 13,5"/></svg>
|
||||
</button>
|
||||
<button v-if="closable" type="button" class="fp-close" :title="t('filePreview.close')" @click="emit('close')">
|
||||
<button v-if="closable" type="button" class="fp-close" :title="t('filePreview.close')" :aria-label="t('filePreview.close')" @click="emit('close')">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -685,6 +685,13 @@ function truncatePath(path: string, maxLen = 55): string {
|
|||
color: var(--blue2);
|
||||
border-color: var(--bd);
|
||||
}
|
||||
.fp-action:focus-visible,
|
||||
.fp-icon-btn:focus-visible,
|
||||
.fp-close:focus-visible,
|
||||
.fp-seg-btn:focus-visible {
|
||||
outline: 2px solid var(--blue);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
.fp-action.copied {
|
||||
color: var(--ok);
|
||||
border-color: color-mix(in srgb, var(--ok) 35%, var(--bg));
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ watch(
|
|||
<div class="tp-header">
|
||||
<span class="tp-title">{{ t('common.preview') }}</span>
|
||||
<span class="tp-sub">{{ subtitle ?? t('thinking.panelTitle') }}</span>
|
||||
<button type="button" class="tp-close" :title="t('thinking.close')" @click="emit('close')">
|
||||
<button type="button" class="tp-close" :title="t('thinking.close')" :aria-label="t('thinking.close')" @click="emit('close')">
|
||||
<svg viewBox="0 0 12 12" width="11" height="11" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><line x1="2" y1="2" x2="10" y2="10"/><line x1="10" y1="2" x2="2" y2="10"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -95,8 +95,8 @@ watch(
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
|
|
@ -107,6 +107,10 @@ watch(
|
|||
background: var(--hover);
|
||||
color: var(--ink);
|
||||
}
|
||||
.tp-close:focus-visible {
|
||||
outline: 2px solid var(--blue);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.tp-body {
|
||||
flex: 1;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export default {
|
||||
resizeHandleAria: 'Resize sidebar width',
|
||||
resizePreviewAria: 'Resize preview panel width',
|
||||
detailPanelAria: 'Detail panel',
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export default {
|
||||
resizeHandleAria: '调整侧栏宽度',
|
||||
resizePreviewAria: '调整预览面板宽度',
|
||||
detailPanelAria: '详情面板',
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue