Files
KArtSell.Aegis/docs/Design/kbx-foundation-v36/packages/kbx-ui/src/components/KbxUtilityRail.vue
T

97 lines
4.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>