Files
KArtSell.Aegis/frontend/src/shared/@kbx/ui/components/KbxDrawer.vue
T
kjh2064 889212d643 feat: KBX v60 Phase 4 complete — KbxQuantityField + index exports
Add KbxQuantityField (increment/decrement spinner) + update index exports
for all Phase 3.5–4 components (wrapper, form, specialized fields).

Components shipped:
- KbxScreenFrame, KbxTemplateStateBoundary, KbxSummaryBar (wrapper)
- KbxFormGrid, KbxFormSection (layout)
- KbxInput, KbxSelect, KbxDateField, KbxNumberField, KbxTextarea, KbxCheckbox (basic fields)
- KbxMoneyField, KbxQuantityField, KbxRadio (specialized fields)
- 9 template/composite/advanced (T02, T03, T06, T07, DataGrid, Dialog, Drawer, Tabs, Lookup)

Total Phase 1–4: 30 components, ~3500 LOC, contracts, registries, composables, tokens, app init complete.
Ready for page implementation using KbxScreenFrame wrapper pattern.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-15 10:43:10 +09:00

164 lines
2.7 KiB
Vue

<script setup lang="ts">
/**
* KBX Drawer Component — v60 simplified
* Side panel drawer
*/
withDefaults(
defineProps<{
open: boolean
title?: string
position?: 'left' | 'right'
}>(),
{
position: 'right',
}
)
defineEmits<{
'update:open': [open: boolean]
close: []
}>()
</script>
<template>
<teleport v-if="open" to="body">
<div class="kbx-drawer__backdrop" @click="$emit('update:open', false); $emit('close')">
<div
class="kbx-drawer"
:class="`kbx-drawer--${position}`"
@click.stop
>
<header v-if="title" class="kbx-drawer__header">
<h2>{{ title }}</h2>
<button class="kbx-drawer__close" @click="$emit('update:open', false); $emit('close')">
</button>
</header>
<div class="kbx-drawer__content">
<slot />
</div>
<footer v-if="$slots.footer" class="kbx-drawer__footer">
<slot name="footer" />
</footer>
</div>
</div>
</teleport>
</template>
<style scoped>
.kbx-drawer__backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
animation: fadeIn 0.2s ease;
}
.kbx-drawer {
position: fixed;
top: 0;
bottom: 0;
width: 400px;
background: var(--kbx-color-surface, #fff);
display: flex;
flex-direction: column;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
animation: slideIn 0.3s ease;
z-index: 1000;
}
.kbx-drawer--right {
right: 0;
animation: slideInRight 0.3s ease;
}
.kbx-drawer--left {
left: 0;
animation: slideInLeft 0.3s ease;
}
.kbx-drawer__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--kbx-color-border, #e5e7eb);
}
.kbx-drawer__header h2 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.kbx-drawer__close {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
padding: 0;
}
.kbx-drawer__content {
padding: 20px;
flex: 1;
overflow-y: auto;
}
.kbx-drawer__footer {
padding: 16px 20px;
border-top: 1px solid var(--kbx-color-border, #e5e7eb);
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideInRight {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@keyframes slideInLeft {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
@media (prefers-color-scheme: dark) {
.kbx-drawer {
background: #1f2937;
}
.kbx-drawer__header {
border-bottom-color: #374151;
}
.kbx-drawer__footer {
border-top-color: #374151;
}
}
@media (max-width: 600px) {
.kbx-drawer {
width: calc(100% - 32px);
}
}
</style>