1217 lines
32 KiB
Vue
1217 lines
32 KiB
Vue
<script setup lang="ts">
|
||
definePageMeta({
|
||
layout: false,
|
||
})
|
||
|
||
interface ComponentItem {
|
||
name: string
|
||
path: string
|
||
category: string
|
||
description?: string
|
||
sampleProps?: Record<string, any>
|
||
hasPreview?: boolean
|
||
previewNotes?: string
|
||
}
|
||
|
||
const selectedCategory = ref<string>('all')
|
||
const searchQuery = ref('')
|
||
const selectedComponent = ref<ComponentItem | null>(null)
|
||
const showCode = ref(false)
|
||
const currentPage = ref(1)
|
||
|
||
// 컴포넌트 목록 정의
|
||
const components: ComponentItem[] = [
|
||
// Atoms - Buttons
|
||
{
|
||
name: 'Button Circle',
|
||
path: 'atoms/Button/Circle',
|
||
category: 'atoms',
|
||
description: '원형 버튼 컴포넌트',
|
||
hasPreview: true,
|
||
sampleProps: { type: 'button', srOnly: '버튼' },
|
||
},
|
||
// {
|
||
// name: 'Button Play',
|
||
// path: 'atoms/Button/Play',
|
||
// category: 'atoms',
|
||
// description: '플레이 버튼 컴포넌트',
|
||
// hasPreview: true,
|
||
// sampleProps: { srOnly: '재생' },
|
||
// },
|
||
// {
|
||
// name: 'Button Variant',
|
||
// path: 'atoms/Button/Variant',
|
||
// category: 'atoms',
|
||
// description: '다양한 버튼 변형',
|
||
// hasPreview: true,
|
||
// sampleProps: { variant: 'filled', disabled: false },
|
||
// },
|
||
|
||
// // Atoms - Icons
|
||
// {
|
||
// name: 'Icon Arrow Right',
|
||
// path: 'atoms/icons/ArrowRightLine',
|
||
// category: 'atoms',
|
||
// description: '오른쪽 화살표 아이콘',
|
||
// hasPreview: true,
|
||
// sampleProps: { size: 24, color: '#333333' },
|
||
// },
|
||
// {
|
||
// name: 'Icon Calendar',
|
||
// path: 'atoms/icons/CalendarLine',
|
||
// category: 'atoms',
|
||
// description: '캘린더 아이콘',
|
||
// hasPreview: true,
|
||
// sampleProps: { size: 24, color: '#333333' },
|
||
// },
|
||
// {
|
||
// name: 'Icon Check',
|
||
// path: 'atoms/icons/CheckLine',
|
||
// category: 'atoms',
|
||
// description: '체크 아이콘',
|
||
// hasPreview: true,
|
||
// sampleProps: { size: 24, color: '#10b981' },
|
||
// },
|
||
// {
|
||
// name: 'Icon Close',
|
||
// path: 'atoms/icons/CloseLine',
|
||
// category: 'atoms',
|
||
// description: '닫기 아이콘',
|
||
// hasPreview: true,
|
||
// sampleProps: { size: 24, color: '#ef4444' },
|
||
// },
|
||
// {
|
||
// name: 'Icon Download',
|
||
// path: 'atoms/icons/DownloadLine',
|
||
// category: 'atoms',
|
||
// description: '다운로드 아이콘',
|
||
// hasPreview: true,
|
||
// sampleProps: { size: 24, color: '#333333' },
|
||
// },
|
||
// {
|
||
// name: 'Icon Menu',
|
||
// path: 'atoms/icons/MenuBoldLine',
|
||
// category: 'atoms',
|
||
// description: '메뉴 아이콘',
|
||
// hasPreview: true,
|
||
// sampleProps: { size: 24, color: '#333333' },
|
||
// },
|
||
// {
|
||
// name: 'Icon Search',
|
||
// path: 'atoms/icons/SearchLine',
|
||
// category: 'atoms',
|
||
// description: '검색 아이콘',
|
||
// hasPreview: true,
|
||
// sampleProps: { size: 24, color: '#333333' },
|
||
// },
|
||
// {
|
||
// name: 'Icon Share',
|
||
// path: 'atoms/icons/ShareLine',
|
||
// category: 'atoms',
|
||
// description: '공유 아이콘',
|
||
// hasPreview: true,
|
||
// sampleProps: { size: 24, color: '#333333' },
|
||
// },
|
||
// {
|
||
// name: 'Icon Star',
|
||
// path: 'atoms/icons/StarFill',
|
||
// category: 'atoms',
|
||
// description: '별 아이콘',
|
||
// hasPreview: true,
|
||
// sampleProps: { size: 24, color: '#fbbf24' },
|
||
// },
|
||
// {
|
||
// name: 'Logo Stove',
|
||
// path: 'atoms/icons/LogoStove',
|
||
// category: 'atoms',
|
||
// description: '스토브 로고',
|
||
// hasPreview: true,
|
||
// sampleProps: { size: 32 },
|
||
// },
|
||
|
||
// // Atoms - Form Elements
|
||
// {
|
||
// name: 'Input',
|
||
// path: 'atoms/Input',
|
||
// category: 'atoms',
|
||
// description: '입력 필드 컴포넌트',
|
||
// hasPreview: true,
|
||
// sampleProps: { modelValue: '', placeholder: '텍스트를 입력하세요', useClearButton: true },
|
||
// },
|
||
// {
|
||
// name: 'Select',
|
||
// path: 'atoms/Select',
|
||
// category: 'atoms',
|
||
// description: '선택 박스 컴포넌트',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Select 컴포넌트는 복잡한 옵션 데이터가 필요합니다',
|
||
// },
|
||
|
||
// // Atoms - Media
|
||
// {
|
||
// name: 'Image',
|
||
// path: 'atoms/Img',
|
||
// category: 'atoms',
|
||
// description: '이미지 컴포넌트',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Image 컴포넌트는 실제 이미지 경로가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Video',
|
||
// path: 'atoms/Video',
|
||
// category: 'atoms',
|
||
// description: '비디오 컴포넌트',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Video 컴포넌트는 실제 비디오 경로가 필요합니다',
|
||
// },
|
||
|
||
// // Atoms - Loading
|
||
// {
|
||
// name: 'Loading Full',
|
||
// path: 'atoms/loading/Full',
|
||
// category: 'atoms',
|
||
// description: '전체 화면 로딩',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Full 로딩은 전체 화면을 차지하므로 미리보기가 제한됩니다',
|
||
// },
|
||
// {
|
||
// name: 'Loading Local',
|
||
// path: 'atoms/loading/Local',
|
||
// category: 'atoms',
|
||
// description: '로컬 로딩',
|
||
// hasPreview: true,
|
||
// sampleProps: {},
|
||
// },
|
||
|
||
// // Blocks
|
||
// {
|
||
// name: 'Card News',
|
||
// path: 'blocks/CardNews',
|
||
// category: 'blocks',
|
||
// description: '카드 뉴스 컴포넌트',
|
||
// hasPreview: true,
|
||
// sampleProps: {
|
||
// title: '샘플 카드 제목',
|
||
// description: '카드 뉴스의 설명입니다',
|
||
// imgPath: '/images/sample/sample01.jpg',
|
||
// url: '#',
|
||
// },
|
||
// },
|
||
// {
|
||
// name: 'Date Picker',
|
||
// path: 'blocks/DatePicker',
|
||
// category: 'blocks',
|
||
// description: '날짜 선택기',
|
||
// hasPreview: false,
|
||
// previewNotes: 'DatePicker는 복잡한 날짜 데이터와 상태 관리가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Language Switcher',
|
||
// path: 'blocks/LanguageSwitcher',
|
||
// category: 'blocks',
|
||
// description: '언어 전환기',
|
||
// hasPreview: true,
|
||
// sampleProps: {},
|
||
// },
|
||
// {
|
||
// name: 'Pagination',
|
||
// path: 'blocks/Pagination',
|
||
// category: 'blocks',
|
||
// description: '페이지네이션',
|
||
// hasPreview: true,
|
||
// sampleProps: { totalCount: 100, currentPage: 1, pageSize: 10, pageBlock: 10, useMinMax: true },
|
||
// },
|
||
// {
|
||
// name: 'LNB',
|
||
// path: 'blocks/Lnb',
|
||
// category: 'blocks',
|
||
// description: '로컬 네비게이션 바',
|
||
// hasPreview: false,
|
||
// previewNotes: 'LNB는 페이지 데이터와 라우팅이 필요합니다',
|
||
// },
|
||
|
||
// // Blocks - Buttons
|
||
// {
|
||
// name: 'Home Button',
|
||
// path: 'blocks/Button/Home',
|
||
// category: 'blocks',
|
||
// description: '홈 버튼',
|
||
// hasPreview: true,
|
||
// sampleProps: {},
|
||
// },
|
||
// {
|
||
// name: 'Launcher Button',
|
||
// path: 'blocks/Button/Launcher',
|
||
// category: 'blocks',
|
||
// description: '런처 버튼',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Launcher 버튼은 게임 데이터가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Scroll Top Button',
|
||
// path: 'blocks/Button/ScrollTop',
|
||
// category: 'blocks',
|
||
// description: '맨 위로 버튼',
|
||
// hasPreview: true,
|
||
// sampleProps: {},
|
||
// },
|
||
// {
|
||
// name: 'SNS Button',
|
||
// path: 'blocks/Button/Sns',
|
||
// category: 'blocks',
|
||
// description: 'SNS 버튼',
|
||
// hasPreview: false,
|
||
// previewNotes: 'SNS 버튼은 SNS 데이터 배열이 필요합니다',
|
||
// },
|
||
|
||
// // Blocks - Modals
|
||
// {
|
||
// name: 'Modal Alert',
|
||
// path: 'blocks/modal/Alert',
|
||
// category: 'blocks',
|
||
// description: '알림 모달',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Modal은 Modal Store와 함께 사용됩니다',
|
||
// },
|
||
// {
|
||
// name: 'Modal Confirm',
|
||
// path: 'blocks/modal/Confirm',
|
||
// category: 'blocks',
|
||
// description: '확인 모달',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Modal은 Modal Store와 함께 사용됩니다',
|
||
// },
|
||
// {
|
||
// name: 'Modal Content',
|
||
// path: 'blocks/modal/Content',
|
||
// category: 'blocks',
|
||
// description: '콘텐츠 모달',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Modal은 Modal Store와 함께 사용됩니다',
|
||
// },
|
||
// {
|
||
// name: 'Modal Layer',
|
||
// path: 'blocks/modal/Layer',
|
||
// category: 'blocks',
|
||
// description: '레이어 모달',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Modal은 Modal Store와 함께 사용됩니다',
|
||
// },
|
||
// {
|
||
// name: 'Modal Toast',
|
||
// path: 'blocks/modal/Toast',
|
||
// category: 'blocks',
|
||
// description: '토스트 모달',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Modal은 Modal Store와 함께 사용됩니다',
|
||
// },
|
||
// {
|
||
// name: 'Modal YouTube',
|
||
// path: 'blocks/modal/YouTube',
|
||
// category: 'blocks',
|
||
// description: '유튜브 모달',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Modal은 Modal Store와 함께 사용됩니다',
|
||
// },
|
||
|
||
// // Blocks - Slides
|
||
// {
|
||
// name: 'Slide Default',
|
||
// path: 'blocks/slide/Default',
|
||
// category: 'blocks',
|
||
// description: '기본 슬라이드',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Slide는 슬라이드 데이터 배열이 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Slide Center Focus',
|
||
// path: 'blocks/slide/CenterFocus',
|
||
// category: 'blocks',
|
||
// description: '중앙 포커스 슬라이드',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Slide는 슬라이드 데이터 배열이 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Slide Center Highlight',
|
||
// path: 'blocks/slide/CenterHighlight',
|
||
// category: 'blocks',
|
||
// description: '중앙 하이라이트 슬라이드',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Slide는 슬라이드 데이터 배열이 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Slide Fade',
|
||
// path: 'blocks/slide/Fade',
|
||
// category: 'blocks',
|
||
// description: '페이드 슬라이드',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Slide는 슬라이드 데이터 배열이 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Slide Thumbnail',
|
||
// path: 'blocks/slide/Thumbnail',
|
||
// category: 'blocks',
|
||
// description: '썸네일 슬라이드',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Slide는 슬라이드 데이터 배열이 필요합니다',
|
||
// },
|
||
|
||
// // Layouts
|
||
// {
|
||
// name: 'Header',
|
||
// path: 'layouts/Header',
|
||
// category: 'layouts',
|
||
// description: '헤더',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Layout 컴포넌트는 전체 레이아웃 구조가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Footer',
|
||
// path: 'layouts/Footer',
|
||
// category: 'layouts',
|
||
// description: '푸터',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Layout 컴포넌트는 전체 레이아웃 구조가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Main',
|
||
// path: 'layouts/Main',
|
||
// category: 'layouts',
|
||
// description: '메인 레이아웃',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Layout 컴포넌트는 전체 레이아웃 구조가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Stove Header',
|
||
// path: 'layouts/StoveHeader',
|
||
// category: 'layouts',
|
||
// description: '스토브 헤더',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Layout 컴포넌트는 전체 레이아웃 구조가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Event Navigation',
|
||
// path: 'layouts/EventNavigation',
|
||
// category: 'layouts',
|
||
// description: '이벤트 네비게이션',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Layout 컴포넌트는 전체 레이아웃 구조가 필요합니다',
|
||
// },
|
||
|
||
// // Widgets
|
||
// {
|
||
// name: 'Background',
|
||
// path: 'widgets/Background',
|
||
// category: 'widgets',
|
||
// description: '배경 위젯',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Widget 컴포넌트는 복잡한 데이터 구조가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Button List',
|
||
// path: 'widgets/ButtonList',
|
||
// category: 'widgets',
|
||
// description: '버튼 리스트',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Widget 컴포넌트는 복잡한 데이터 구조가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Description',
|
||
// path: 'widgets/Description',
|
||
// category: 'widgets',
|
||
// description: '설명',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Widget 컴포넌트는 복잡한 데이터 구조가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Main Title',
|
||
// path: 'widgets/MainTitle',
|
||
// category: 'widgets',
|
||
// description: '메인 타이틀',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Widget 컴포넌트는 복잡한 데이터 구조가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Sub Title',
|
||
// path: 'widgets/SubTitle',
|
||
// category: 'widgets',
|
||
// description: '서브 타이틀',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Widget 컴포넌트는 복잡한 데이터 구조가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Fix Main Title',
|
||
// path: 'widgets/FixMainTitle',
|
||
// category: 'widgets',
|
||
// description: '고정 메인 타이틀',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Widget 컴포넌트는 복잡한 데이터 구조가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Fix Sub Title',
|
||
// path: 'widgets/FixSubTitle',
|
||
// category: 'widgets',
|
||
// description: '고정 서브 타이틀',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Widget 컴포넌트는 복잡한 데이터 구조가 필요합니다',
|
||
// },
|
||
// {
|
||
// name: 'Video Play',
|
||
// path: 'widgets/VideoPlay',
|
||
// category: 'widgets',
|
||
// description: '비디오 플레이',
|
||
// hasPreview: false,
|
||
// previewNotes: 'Widget 컴포넌트는 복잡한 데이터 구조가 필요합니다',
|
||
// },
|
||
]
|
||
|
||
const categories = [
|
||
{ value: 'all', label: '전체', count: components.length },
|
||
{ value: 'atoms', label: 'Atoms', count: components.filter(c => c.category === 'atoms').length },
|
||
{ value: 'blocks', label: 'Blocks', count: components.filter(c => c.category === 'blocks').length },
|
||
{ value: 'layouts', label: 'Layouts', count: components.filter(c => c.category === 'layouts').length },
|
||
{ value: 'widgets', label: 'Widgets', count: components.filter(c => c.category === 'widgets').length },
|
||
]
|
||
|
||
// 필터링된 컴포넌트 목록
|
||
const filteredComponents = computed(() => {
|
||
let filtered = components
|
||
|
||
if (selectedCategory.value !== 'all') {
|
||
filtered = filtered.filter(c => c.category === selectedCategory.value)
|
||
}
|
||
|
||
if (searchQuery.value) {
|
||
const query = searchQuery.value.toLowerCase()
|
||
filtered = filtered.filter(c =>
|
||
c.name.toLowerCase().includes(query) ||
|
||
c.description?.toLowerCase().includes(query)
|
||
)
|
||
}
|
||
|
||
return filtered
|
||
})
|
||
|
||
// 카테고리별로 그룹화
|
||
const groupedComponents = computed(() => {
|
||
const groups: Record<string, ComponentItem[]> = {}
|
||
|
||
filteredComponents.value.forEach(component => {
|
||
if (!groups[component.category]) {
|
||
groups[component.category] = []
|
||
}
|
||
groups[component.category].push(component)
|
||
})
|
||
|
||
return groups
|
||
})
|
||
|
||
const selectComponent = (component: ComponentItem) => {
|
||
selectedComponent.value = component
|
||
showCode.value = false
|
||
}
|
||
|
||
const closeDetail = () => {
|
||
selectedComponent.value = null
|
||
showCode.value = false
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="component-viewer">
|
||
<!-- Header -->
|
||
<header class="viewer-header">
|
||
<div class="container">
|
||
<h1 class="title">
|
||
🎨 Component Viewer
|
||
</h1>
|
||
<p class="subtitle">
|
||
프로젝트의 모든 컴포넌트를 한눈에 확인하세요
|
||
</p>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="container">
|
||
<div class="viewer-content">
|
||
<!-- Sidebar -->
|
||
<aside class="sidebar">
|
||
<!-- Search -->
|
||
<div class="search-box">
|
||
<input
|
||
v-model="searchQuery"
|
||
type="text"
|
||
placeholder="컴포넌트 검색..."
|
||
class="search-input"
|
||
/>
|
||
</div>
|
||
|
||
<!-- Categories -->
|
||
<nav class="categories">
|
||
<button
|
||
v-for="category in categories"
|
||
:key="category.value"
|
||
:class="['category-btn', { active: selectedCategory === category.value }]"
|
||
@click="selectedCategory = category.value"
|
||
>
|
||
{{ category.label }}
|
||
<span class="count">{{ category.count }}</span>
|
||
</button>
|
||
</nav>
|
||
|
||
<!-- Component List -->
|
||
<div class="component-list">
|
||
<template v-for="(items, category) in groupedComponents" :key="category">
|
||
<div class="category-group">
|
||
<h3 class="category-title">
|
||
{{ category.toUpperCase() }}
|
||
</h3>
|
||
<button
|
||
v-for="component in items"
|
||
:key="component.path"
|
||
:class="['component-item', { active: selectedComponent?.path === component.path }]"
|
||
@click="selectComponent(component)"
|
||
>
|
||
<span class="component-name">{{ component.name }}</span>
|
||
<span v-if="component.description" class="component-desc">
|
||
{{ component.description }}
|
||
</span>
|
||
</button>
|
||
</div>
|
||
</template>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- Main Content -->
|
||
<main class="main-content">
|
||
<div v-if="!selectedComponent" class="empty-state">
|
||
<div class="empty-icon">📦</div>
|
||
<h2 class="empty-title">컴포넌트를 선택해주세요</h2>
|
||
<p class="empty-text">
|
||
왼쪽 목록에서 컴포넌트를 선택하면<br />
|
||
미리보기와 상세 정보를 확인할 수 있습니다
|
||
</p>
|
||
</div>
|
||
|
||
<div v-else class="component-detail">
|
||
<!-- Detail Header -->
|
||
<div class="detail-header">
|
||
<div>
|
||
<h2 class="detail-title">{{ selectedComponent.name }}</h2>
|
||
<p v-if="selectedComponent.description" class="detail-desc">
|
||
{{ selectedComponent.description }}
|
||
</p>
|
||
<code class="detail-path">{{ selectedComponent.path }}</code>
|
||
</div>
|
||
<button class="close-btn" @click="closeDetail">
|
||
✕
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Preview Info -->
|
||
<div class="preview-info">
|
||
<div class="info-badge">
|
||
<span class="badge-label">Category:</span>
|
||
<span class="badge-value">{{ selectedComponent.category }}</span>
|
||
</div>
|
||
<div class="info-badge">
|
||
<span class="badge-label">Path:</span>
|
||
<span class="badge-value">layers/components/{{ selectedComponent.path }}.vue</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Preview Section -->
|
||
<div v-if="selectedComponent.hasPreview" class="preview-section">
|
||
<h3 class="section-title">👁️ 미리보기</h3>
|
||
<div class="preview-container">
|
||
<!-- Button Circle -->
|
||
<template v-if="selectedComponent.path === 'atoms/Button/Circle'">
|
||
<AtomsButtonCircle v-bind="selectedComponent.sampleProps">
|
||
<AtomsIconsStove />
|
||
</AtomsButtonCircle>
|
||
</template>
|
||
|
||
<!-- Button Play -->
|
||
<template v-else-if="selectedComponent.path === 'atoms/Button/Play'">
|
||
<AtomsButtonPlay v-bind="selectedComponent.sampleProps" />
|
||
</template>
|
||
|
||
<!-- Button Variant -->
|
||
<template v-else-if="selectedComponent.path === 'atoms/Button/Variant'">
|
||
<div class="flex flex-col gap-4">
|
||
<AtomsButtonVariant variant="filled">Filled Button</AtomsButtonVariant>
|
||
<AtomsButtonVariant variant="outlined">Outlined Button</AtomsButtonVariant>
|
||
<AtomsButtonVariant variant="filled" :disabled="true">Disabled Button</AtomsButtonVariant>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- Icons -->
|
||
<template v-else-if="selectedComponent.path.includes('atoms/icons/')">
|
||
<component :is="'Atoms' + selectedComponent.path.replace('atoms/', '').replace(/\//g, '')" v-bind="selectedComponent.sampleProps" />
|
||
</template>
|
||
|
||
<!-- Input -->
|
||
<template v-else-if="selectedComponent.path === 'atoms/Input'">
|
||
<div class="w-full max-w-md">
|
||
<AtomsInput
|
||
model-value=""
|
||
:placeholder="selectedComponent.sampleProps?.placeholder as string"
|
||
:use-clear-button="selectedComponent.sampleProps?.useClearButton as boolean"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- Loading Local -->
|
||
<template v-else-if="selectedComponent.path === 'atoms/loading/Local'">
|
||
<div class="w-20 h-20 relative">
|
||
<AtomsLoadingLocal />
|
||
</div>
|
||
</template>
|
||
|
||
<!-- Card News -->
|
||
<template v-else-if="selectedComponent.path === 'blocks/CardNews'">
|
||
<div class="w-full max-w-sm h-64">
|
||
<BlocksCardNews
|
||
:title="selectedComponent.sampleProps?.title as string"
|
||
:description="selectedComponent.sampleProps?.description as string"
|
||
:img-path="selectedComponent.sampleProps?.imgPath as string"
|
||
:url="selectedComponent.sampleProps?.url as string"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- Language Switcher -->
|
||
<template v-else-if="selectedComponent.path === 'blocks/LanguageSwitcher'">
|
||
<BlocksLanguageSwitcher />
|
||
</template>
|
||
|
||
<!-- Pagination -->
|
||
<template v-else-if="selectedComponent.path === 'blocks/Pagination'">
|
||
<BlocksPagination
|
||
:total-count="selectedComponent.sampleProps?.totalCount as number"
|
||
:current-page="currentPage"
|
||
:page-size="selectedComponent.sampleProps?.pageSize as number"
|
||
:page-block="selectedComponent.sampleProps?.pageBlock as number"
|
||
:use-min-max="selectedComponent.sampleProps?.useMinMax as boolean"
|
||
@update:page="currentPage = $event"
|
||
/>
|
||
</template>
|
||
|
||
<!-- Home Button -->
|
||
<template v-else-if="selectedComponent.path === 'blocks/Button/Home'">
|
||
<BlocksButtonHome />
|
||
</template>
|
||
|
||
<!-- Scroll Top Button -->
|
||
<template v-else-if="selectedComponent.path === 'blocks/Button/ScrollTop'">
|
||
<BlocksButtonScrollTop />
|
||
</template>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Preview Not Available -->
|
||
<div v-else class="preview-section">
|
||
<h3 class="section-title">👁️ 미리보기</h3>
|
||
<div class="preview-unavailable">
|
||
<div class="unavailable-icon">🚧</div>
|
||
<p class="unavailable-text">
|
||
{{ selectedComponent.previewNotes || '이 컴포넌트는 미리보기를 제공하지 않습니다' }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Props Information -->
|
||
<div v-if="selectedComponent.sampleProps && Object.keys(selectedComponent.sampleProps).length > 0" class="props-section">
|
||
<h3 class="section-title">⚙️ Props 예제</h3>
|
||
<div class="props-table">
|
||
<div v-for="(value, key) in selectedComponent.sampleProps" :key="key" class="prop-row">
|
||
<span class="prop-key">{{ key }}</span>
|
||
<span class="prop-value">{{ JSON.stringify(value) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- Import Path -->
|
||
<div class="usage-section">
|
||
<h3 class="section-title">📥 Import</h3>
|
||
<div class="code-block">
|
||
<pre><code>// Auto-imported (Nuxt layers)
|
||
// 자동으로 import 됩니다</code></pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Notes -->
|
||
<div class="notes-section">
|
||
<h3 class="section-title">💡 참고사항</h3>
|
||
<ul class="notes-list">
|
||
<li>이 컴포넌트는 Nuxt layers를 통해 자동으로 import됩니다</li>
|
||
<li>실제 사용 시 필요한 props를 전달해야 합니다</li>
|
||
<li>자세한 props 정보는 컴포넌트 파일을 참고하세요</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.component-viewer {
|
||
min-height: 100vh;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
}
|
||
|
||
.viewer-header {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
backdrop-filter: blur(10px);
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||
padding: 2rem 0;
|
||
color: white;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
padding: 0 2rem;
|
||
}
|
||
|
||
.title {
|
||
font-size: 2.5rem;
|
||
font-weight: 700;
|
||
margin: 0 0 0.5rem 0;
|
||
}
|
||
|
||
.subtitle {
|
||
font-size: 1.125rem;
|
||
opacity: 0.9;
|
||
margin: 0;
|
||
}
|
||
|
||
.viewer-content {
|
||
display: grid;
|
||
grid-template-columns: 320px 1fr;
|
||
gap: 2rem;
|
||
padding: 2rem 0;
|
||
min-height: calc(100vh - 200px);
|
||
}
|
||
|
||
/* Sidebar */
|
||
.sidebar {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 1.5rem;
|
||
height: fit-content;
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
position: sticky;
|
||
top: 2rem;
|
||
max-height: calc(100vh - 4rem);
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.search-box {
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.search-input {
|
||
width: 100%;
|
||
padding: 0.75rem;
|
||
border: 2px solid #e5e7eb;
|
||
border-radius: 8px;
|
||
font-size: 0.875rem;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.search-input:focus {
|
||
outline: none;
|
||
border-color: #667eea;
|
||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||
}
|
||
|
||
.categories {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
margin-bottom: 1.5rem;
|
||
padding-bottom: 1.5rem;
|
||
border-bottom: 2px solid #f3f4f6;
|
||
}
|
||
|
||
.category-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0.75rem 1rem;
|
||
border: none;
|
||
background: #f9fafb;
|
||
border-radius: 8px;
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
color: #374151;
|
||
}
|
||
|
||
.category-btn:hover {
|
||
background: #f3f4f6;
|
||
}
|
||
|
||
.category-btn.active {
|
||
background: #667eea;
|
||
color: white;
|
||
}
|
||
|
||
.count {
|
||
font-size: 0.75rem;
|
||
padding: 0.25rem 0.5rem;
|
||
background: rgba(0, 0, 0, 0.1);
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.component-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1.5rem;
|
||
}
|
||
|
||
.category-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.category-title {
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
color: #9ca3af;
|
||
margin: 0 0 0.5rem 0;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
|
||
.component-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
padding: 0.75rem;
|
||
border: none;
|
||
background: white;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
text-align: left;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.component-item:hover {
|
||
background: #f9fafb;
|
||
border-color: #667eea;
|
||
}
|
||
|
||
.component-item.active {
|
||
background: #ede9fe;
|
||
border-color: #667eea;
|
||
}
|
||
|
||
.component-name {
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
color: #111827;
|
||
}
|
||
|
||
.component-desc {
|
||
font-size: 0.75rem;
|
||
color: #6b7280;
|
||
margin-top: 0.25rem;
|
||
}
|
||
|
||
/* Main Content */
|
||
.main-content {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 2rem;
|
||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 400px;
|
||
text-align: center;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 4rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.empty-title {
|
||
font-size: 1.5rem;
|
||
font-weight: 600;
|
||
color: #111827;
|
||
margin: 0 0 0.5rem 0;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 1rem;
|
||
color: #6b7280;
|
||
margin: 0;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* Component Detail */
|
||
.component-detail {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2rem;
|
||
}
|
||
|
||
.detail-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
padding-bottom: 1.5rem;
|
||
border-bottom: 2px solid #f3f4f6;
|
||
}
|
||
|
||
.detail-title {
|
||
font-size: 2rem;
|
||
font-weight: 700;
|
||
color: #111827;
|
||
margin: 0 0 0.5rem 0;
|
||
}
|
||
|
||
.detail-desc {
|
||
font-size: 1rem;
|
||
color: #6b7280;
|
||
margin: 0 0 0.75rem 0;
|
||
}
|
||
|
||
.detail-path {
|
||
display: inline-block;
|
||
padding: 0.5rem 1rem;
|
||
background: #f3f4f6;
|
||
border-radius: 6px;
|
||
font-size: 0.875rem;
|
||
color: #667eea;
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
.close-btn {
|
||
padding: 0.5rem;
|
||
border: none;
|
||
background: #f3f4f6;
|
||
border-radius: 6px;
|
||
font-size: 1.25rem;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
width: 36px;
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.close-btn:hover {
|
||
background: #e5e7eb;
|
||
}
|
||
|
||
.preview-info {
|
||
display: flex;
|
||
gap: 1rem;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.info-badge {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.75rem 1rem;
|
||
background: #f9fafb;
|
||
border-radius: 8px;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.badge-label {
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.badge-value {
|
||
font-size: 0.875rem;
|
||
color: #111827;
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
.usage-section,
|
||
.notes-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 1.25rem;
|
||
font-weight: 600;
|
||
color: #111827;
|
||
margin: 0;
|
||
}
|
||
|
||
.code-block {
|
||
background: #1f2937;
|
||
border-radius: 8px;
|
||
padding: 1.5rem;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.code-block pre {
|
||
margin: 0;
|
||
}
|
||
|
||
.code-block code {
|
||
font-family: 'Courier New', monospace;
|
||
font-size: 0.875rem;
|
||
color: #10b981;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.notes-list {
|
||
margin: 0;
|
||
padding-left: 1.5rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.notes-list li {
|
||
font-size: 0.875rem;
|
||
color: #6b7280;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* Preview Section */
|
||
.preview-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.preview-container {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 200px;
|
||
padding: 2rem;
|
||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
||
border-radius: 12px;
|
||
border: 2px dashed #d1d5db;
|
||
}
|
||
|
||
.preview-unavailable {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 150px;
|
||
padding: 2rem;
|
||
background: #f9fafb;
|
||
border-radius: 12px;
|
||
border: 2px dashed #e5e7eb;
|
||
}
|
||
|
||
.unavailable-icon {
|
||
font-size: 3rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.unavailable-text {
|
||
font-size: 0.875rem;
|
||
color: #6b7280;
|
||
text-align: center;
|
||
margin: 0;
|
||
}
|
||
|
||
/* Props Section */
|
||
.props-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.props-table {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
background: #f9fafb;
|
||
border-radius: 8px;
|
||
padding: 1rem;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.prop-row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 0.75rem;
|
||
background: white;
|
||
border-radius: 6px;
|
||
border: 1px solid #e5e7eb;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.prop-key {
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
color: #667eea;
|
||
font-family: 'Courier New', monospace;
|
||
}
|
||
|
||
.prop-value {
|
||
font-size: 0.875rem;
|
||
color: #374151;
|
||
font-family: 'Courier New', monospace;
|
||
background: #f3f4f6;
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* Scrollbar Styling */
|
||
.sidebar::-webkit-scrollbar {
|
||
width: 6px;
|
||
}
|
||
|
||
.sidebar::-webkit-scrollbar-track {
|
||
background: #f3f4f6;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.sidebar::-webkit-scrollbar-thumb {
|
||
background: #d1d5db;
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.sidebar::-webkit-scrollbar-thumb:hover {
|
||
background: #9ca3af;
|
||
}
|
||
|
||
@media (max-width: 1024px) {
|
||
.viewer-content {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.sidebar {
|
||
position: relative;
|
||
top: 0;
|
||
max-height: none;
|
||
}
|
||
|
||
.prop-row {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
}
|
||
}
|
||
</style>
|
||
|