V13-FE-011: finalize search list layout slice
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { KbxAiAnswer, KbxAiScreenContext, KbxHelpContent, KbxSuggestionContext, KbxSuggestionRequest } from '@kbx/contracts'
|
||||
import KbxHelpPanel from './KbxHelpPanel.vue'
|
||||
import KbxAiAssistant from './KbxAiAssistant.vue'
|
||||
import KbxUserSuggestionPanel from './KbxUserSuggestionPanel.vue'
|
||||
|
||||
type UtilityTab = 'help' | 'ai' | 'suggestion'
|
||||
|
||||
const props = defineProps<{
|
||||
help?: KbxHelpContent
|
||||
aiContext?: KbxAiScreenContext
|
||||
aiAnswer?: KbxAiAnswer | null
|
||||
aiLoading?: boolean
|
||||
aiError?: string
|
||||
aiCurrentScreenLabel?: string
|
||||
aiQuickQuestions?: string[]
|
||||
suggestionContext?: KbxSuggestionContext
|
||||
suggestionSubmitting?: boolean
|
||||
openTab?: UtilityTab | null
|
||||
triggerless?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
aiAsk: [question: string]
|
||||
aiAction: [actionId: string]
|
||||
aiOpenProposal: []
|
||||
suggestionSubmit: [request: KbxSuggestionRequest]
|
||||
close: []
|
||||
}>()
|
||||
|
||||
const open = ref(false)
|
||||
const active = ref<UtilityTab>('help')
|
||||
const availableTabs = computed(() => [
|
||||
props.help && 'help',
|
||||
props.aiContext && 'ai',
|
||||
props.suggestionContext && 'suggestion',
|
||||
].filter(Boolean) as UtilityTab[])
|
||||
|
||||
function show(tab: UtilityTab) { active.value = tab; open.value = true }
|
||||
function close(){ open.value=false; emit('close') }
|
||||
watch(() => props.openTab, tab => { if(tab)show(tab); else if(props.triggerless)open.value=false }, { immediate:true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="kbx-utility-rail">
|
||||
<nav v-if="!triggerless" class="kbx-utility-rail__buttons" aria-label="화면 보조기능">
|
||||
<button v-if="help" type="button" @click="show('help')">도움말</button>
|
||||
<button v-if="aiContext" type="button" @click="show('ai')">AI</button>
|
||||
<button v-if="suggestionContext" type="button" @click="show('suggestion')">제안</button>
|
||||
</nav>
|
||||
|
||||
<div v-if="open" class="kbx-utility-rail__backdrop" @click.self="close">
|
||||
<aside class="kbx-utility-rail__panel" role="dialog" aria-modal="false" aria-label="화면 보조기능">
|
||||
<header class="kbx-utility-rail__header">
|
||||
<nav>
|
||||
<button v-for="tab in availableTabs" :key="tab" type="button" :aria-current="active === tab ? 'page' : undefined" @click="active = tab">
|
||||
{{ tab === 'help' ? '도움말' : tab === 'ai' ? 'AI' : '제안' }}
|
||||
</button>
|
||||
</nav>
|
||||
<button type="button" aria-label="닫기" @click="close">×</button>
|
||||
</header>
|
||||
|
||||
<KbxHelpPanel v-if="active === 'help' && help" :content="help" />
|
||||
<KbxAiAssistant
|
||||
v-else-if="active === 'ai' && aiContext"
|
||||
:context="aiContext"
|
||||
:answer="aiAnswer"
|
||||
:loading="aiLoading"
|
||||
:error="aiError"
|
||||
:current-screen-label="aiCurrentScreenLabel"
|
||||
:quick-questions="aiQuickQuestions"
|
||||
@ask="emit('aiAsk', $event)"
|
||||
@action="emit('aiAction', $event)"
|
||||
@open-proposal="emit('aiOpenProposal')"
|
||||
/>
|
||||
<KbxUserSuggestionPanel
|
||||
v-else-if="active === 'suggestion' && suggestionContext"
|
||||
:context="suggestionContext"
|
||||
:submitting="suggestionSubmitting"
|
||||
@submit="emit('suggestionSubmit', $event)"
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.kbx-utility-rail__buttons{display:flex;align-items:center;gap:var(--kbx-space-1)}
|
||||
.kbx-utility-rail__buttons button,.kbx-utility-rail__header button{min-height:var(--kbx-control-sm);padding:0 var(--kbx-space-2);border:var(--kbx-border-width) solid var(--kbx-color-border);border-radius:var(--kbx-radius-sm);background:var(--kbx-color-surface);color:var(--kbx-color-text)}
|
||||
.kbx-utility-rail__backdrop{position:fixed;inset:0;z-index:1000;background:var(--kbx-color-overlay);display:flex;justify-content:flex-end}
|
||||
.kbx-utility-rail__panel{width:min(var(--kbx-utility-panel-width),94vw);height:100%;overflow:auto;background:var(--kbx-color-surface);border-left:var(--kbx-border-width) solid var(--kbx-color-border);box-shadow:var(--kbx-shadow-overlay)}
|
||||
.kbx-utility-rail__header{position:sticky;top:0;z-index:2;display:flex;justify-content:space-between;gap:var(--kbx-space-2);padding:var(--kbx-space-2);border-bottom:var(--kbx-border-width) solid var(--kbx-color-border);background:var(--kbx-color-surface)}
|
||||
.kbx-utility-rail__header nav{display:flex;gap:var(--kbx-space-1)}
|
||||
.kbx-utility-rail__header [aria-current="page"]{border-color:var(--kbx-color-primary);color:var(--kbx-color-primary);font-weight:600}
|
||||
</style>
|
||||
Reference in New Issue
Block a user