235 lines
12 KiB
PHP
235 lines
12 KiB
PHP
{{--
|
|
아이콘 픽커 공유 partial
|
|
사용법: @include('partials.icon-picker', ['currentIcon' => $currentIcon ?? '', 'theme' => 'blue'])
|
|
$theme: 'blue' (일반 메뉴) 또는 'purple' (글로벌 메뉴)
|
|
--}}
|
|
@php
|
|
$theme = $theme ?? 'blue';
|
|
$currentIcon = $currentIcon ?? '';
|
|
|
|
// 테마별 색상 클래스
|
|
$themeColors = [
|
|
'blue' => [
|
|
'hover_border' => 'hover:border-blue-300',
|
|
'hover_text' => 'group-hover:text-blue-600',
|
|
'selected_bg' => 'bg-blue-50',
|
|
'selected_border' => 'border-blue-400',
|
|
'focus_ring' => 'focus:ring-blue-500',
|
|
'category_text' => 'text-blue-600',
|
|
'category_bg' => 'bg-blue-50',
|
|
],
|
|
'purple' => [
|
|
'hover_border' => 'hover:border-purple-300',
|
|
'hover_text' => 'group-hover:text-purple-600',
|
|
'selected_bg' => 'bg-purple-50',
|
|
'selected_border' => 'border-purple-400',
|
|
'focus_ring' => 'focus:ring-purple-500',
|
|
'category_text' => 'text-purple-600',
|
|
'category_bg' => 'bg-purple-50',
|
|
],
|
|
];
|
|
$c = $themeColors[$theme] ?? $themeColors['blue'];
|
|
|
|
// 카테고리별 아이콘 그룹
|
|
$iconCategories = [
|
|
'기본' => ['home', 'folder', 'folder-open', 'folder-plus', 'menu', 'layout-dashboard', 'dashboard', 'layout', 'grid', 'list', 'table'],
|
|
'문서/결재' => ['file-text', 'file-check', 'file-edit', 'file-plus', 'file-minus', 'file-x', 'file-search', 'file-signature', 'document-text', 'inbox', 'clipboard-check', 'clipboard-list'],
|
|
'필기/편집' => ['pen-tool', 'pen', 'pencil', 'edit-2', 'eraser', 'type', 'highlighter'],
|
|
'게시판/보기' => ['layout-list', 'eye'],
|
|
'품목/재고' => ['package', 'box', 'archive', 'cube', 'warehouse', 'layers'],
|
|
'데이터' => ['database'],
|
|
'사용자/인사' => ['users', 'user', 'user-group', 'user-plus', 'user-minus', 'user-check'],
|
|
'건물/회사' => ['building', 'building-2', 'landmark'],
|
|
'일정/시간' => ['calendar', 'calendar-check', 'calendar-days', 'clock', 'history'],
|
|
'금융/회계' => ['dollar-sign', 'credit-card', 'trending-up', 'trending-down', 'receipt', 'briefcase', 'wallet', 'banknote', 'cash'],
|
|
'차트/리포트' => ['bar-chart-3', 'chart-bar', 'pie-chart', 'activity'],
|
|
'커뮤니케이션' => ['headphones', 'megaphone', 'help-circle', 'message-circle', 'bell', 'mail', 'send', 'share', 'share-2', 'phone', 'smartphone'],
|
|
'보안/권한' => ['shield', 'shield-check', 'lock', 'key', 'award', 'fingerprint', 'log-in', 'log-out'],
|
|
'상태/알림' => ['alert-circle', 'alert-triangle', 'check-circle', 'check-square', 'x-circle', 'arrow-up-circle', 'arrow-down-circle', 'info'],
|
|
'검색/필터' => ['search', 'filter', 'zoom-in'],
|
|
'CRUD' => ['plus', 'plus-circle', 'minus', 'minus-circle', 'trash', 'trash-2', 'copy', 'scissors'],
|
|
'설정/도구' => ['cog', 'settings', 'sliders', 'adjustments', 'wrench', 'tool', 'hammer'],
|
|
'개발/코드' => ['code', 'terminal', 'server', 'beaker', 'cpu', 'hard-drive', 'brain-circuit'],
|
|
'인터넷/클라우드' => ['globe', 'wifi', 'cloud', 'cloud-upload', 'cloud-download'],
|
|
'링크/전송' => ['link', 'link-2', 'unlink', 'download', 'upload', 'external-link'],
|
|
'미디어' => ['image', 'camera', 'video', 'music', 'volume-2'],
|
|
'디바이스' => ['monitor', 'printer'],
|
|
'즐겨찾기/표시' => ['heart', 'star', 'bookmark', 'flag', 'thumbs-up'],
|
|
'유틸리티' => ['calculator', 'palette', 'map-pin', 'map', 'tag', 'collection', 'sparkles', 'lightning-bolt', 'puzzle'],
|
|
'물류/운송' => ['truck', 'car', 'shopping-cart', 'shopping-bag', 'gift'],
|
|
'생산' => ['factory'],
|
|
'방향' => ['compass', 'navigation', 'target', 'crosshair', 'chevron-right', 'chevron-down', 'chevron-up', 'chevron-left'],
|
|
'기호' => ['percent', 'hash', 'at-sign'],
|
|
'에너지' => ['zap', 'battery', 'power'],
|
|
'테마' => ['sun', 'moon'],
|
|
'UI' => ['more-horizontal', 'more-vertical', 'maximize', 'minimize', 'refresh-cw', 'rotate-cw'],
|
|
'교육' => ['book', 'book-open', 'graduation-cap'],
|
|
];
|
|
|
|
// 전체 아이콘 flat 리스트 (중복 제거)
|
|
$allIcons = collect($iconCategories)->flatten()->unique()->values()->toArray();
|
|
@endphp
|
|
|
|
<div class="mb-4">
|
|
<label for="icon" class="block text-sm font-medium text-gray-700 mb-2">
|
|
아이콘
|
|
</label>
|
|
<div class="flex items-center gap-3 mb-2">
|
|
<div id="icon-preview" class="w-12 h-12 flex items-center justify-center border border-gray-300 rounded-lg bg-gray-50 text-gray-600">
|
|
@if($currentIcon)
|
|
<x-sidebar.menu-icon :icon="$currentIcon" class="w-6 h-6" />
|
|
@else
|
|
<x-sidebar.menu-icon icon="menu" class="w-6 h-6" />
|
|
@endif
|
|
</div>
|
|
<input type="text" name="icon" id="icon"
|
|
value="{{ $currentIcon }}"
|
|
class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 {{ $c['focus_ring'] }}"
|
|
placeholder="아래에서 선택하거나 직접 입력">
|
|
</div>
|
|
|
|
<!-- 아이콘 선택 영역 -->
|
|
<div class="border border-gray-200 rounded-lg bg-gray-50 overflow-hidden">
|
|
<!-- 검색 바 -->
|
|
<div class="p-3 border-b border-gray-200 bg-white">
|
|
<div class="relative">
|
|
<input type="text" id="icon-search"
|
|
class="w-full pl-9 pr-4 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 {{ $c['focus_ring'] }}"
|
|
placeholder="아이콘 이름으로 검색 (예: file, user, chart...)">
|
|
<svg class="absolute left-3 top-2.5 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<circle cx="11" cy="11" r="8" stroke-width="2"></circle>
|
|
<path stroke-linecap="round" stroke-width="2" d="M21 21l-4.35-4.35"></path>
|
|
</svg>
|
|
<span id="icon-search-count" class="absolute right-3 top-2.5 text-xs text-gray-400"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 아이콘 그리드 -->
|
|
<div class="p-3 max-h-80 overflow-y-auto" id="icon-grid-container">
|
|
@foreach($iconCategories as $categoryName => $categoryIcons)
|
|
<div class="icon-category mb-3" data-category="{{ $categoryName }}">
|
|
<p class="text-[10px] font-semibold {{ $c['category_text'] }} {{ $c['category_bg'] }} inline-block px-2 py-0.5 rounded mb-1.5">{{ $categoryName }}</p>
|
|
<div class="grid grid-cols-6 sm:grid-cols-9 md:grid-cols-13 gap-1">
|
|
@foreach($categoryIcons as $iconKey)
|
|
<button type="button"
|
|
onclick="selectIcon('{{ $iconKey }}')"
|
|
class="icon-btn flex flex-col items-center gap-0.5 p-2 hover:bg-white hover:shadow-sm rounded-lg transition cursor-pointer border border-transparent {{ $c['hover_border'] }} group {{ $currentIcon === $iconKey ? $c['selected_bg'] . ' ' . $c['selected_border'] : '' }}"
|
|
data-icon="{{ $iconKey }}"
|
|
title="{{ $iconKey }}">
|
|
<x-sidebar.menu-icon :icon="$iconKey" class="w-5 h-5 text-gray-500 {{ $c['hover_text'] }}" />
|
|
<span class="text-[9px] text-gray-400 {{ $c['hover_text'] }} truncate max-w-full leading-none">{{ $iconKey }}</span>
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
<p id="icon-no-results" class="hidden text-sm text-gray-400 text-center py-6">검색 결과가 없습니다</p>
|
|
</div>
|
|
|
|
<!-- 하단 정보 -->
|
|
<div class="px-3 py-2 border-t border-gray-200 bg-white flex justify-between items-center">
|
|
<span class="text-[10px] text-gray-400">총 {{ count($allIcons) }}개 아이콘</span>
|
|
<button type="button" id="icon-clear-search" class="text-[10px] text-gray-400 hover:text-gray-600 hidden">검색 초기화</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@pushOnce('scripts')
|
|
<script>
|
|
(function() {
|
|
// 아이콘 검색
|
|
const searchInput = document.getElementById('icon-search');
|
|
const searchCount = document.getElementById('icon-search-count');
|
|
const noResults = document.getElementById('icon-no-results');
|
|
const clearBtn = document.getElementById('icon-clear-search');
|
|
|
|
if (!searchInput) return;
|
|
|
|
searchInput.addEventListener('input', function() {
|
|
const query = this.value.toLowerCase().trim();
|
|
const categories = document.querySelectorAll('.icon-category');
|
|
let totalVisible = 0;
|
|
|
|
if (!query) {
|
|
// 검색어 없으면 전부 표시
|
|
categories.forEach(cat => {
|
|
cat.classList.remove('hidden');
|
|
cat.querySelectorAll('.icon-btn').forEach(btn => btn.classList.remove('hidden'));
|
|
});
|
|
searchCount.textContent = '';
|
|
noResults.classList.add('hidden');
|
|
clearBtn.classList.add('hidden');
|
|
return;
|
|
}
|
|
|
|
clearBtn.classList.remove('hidden');
|
|
|
|
categories.forEach(cat => {
|
|
const categoryName = cat.dataset.category.toLowerCase();
|
|
const buttons = cat.querySelectorAll('.icon-btn');
|
|
let categoryVisible = 0;
|
|
|
|
buttons.forEach(btn => {
|
|
const iconName = btn.dataset.icon;
|
|
if (iconName.includes(query) || categoryName.includes(query)) {
|
|
btn.classList.remove('hidden');
|
|
categoryVisible++;
|
|
totalVisible++;
|
|
} else {
|
|
btn.classList.add('hidden');
|
|
}
|
|
});
|
|
|
|
cat.classList.toggle('hidden', categoryVisible === 0);
|
|
});
|
|
|
|
searchCount.textContent = totalVisible + '개';
|
|
noResults.classList.toggle('hidden', totalVisible > 0);
|
|
});
|
|
|
|
// 검색 초기화 버튼
|
|
if (clearBtn) {
|
|
clearBtn.addEventListener('click', function() {
|
|
searchInput.value = '';
|
|
searchInput.dispatchEvent(new Event('input'));
|
|
searchInput.focus();
|
|
});
|
|
}
|
|
|
|
// 아이콘 선택
|
|
window.selectIcon = function(iconKey) {
|
|
document.getElementById('icon').value = iconKey;
|
|
updateIconPreview(iconKey);
|
|
|
|
// 선택 상태 표시 - 테마에 맞게
|
|
const selectedBg = '{{ $c['selected_bg'] }}';
|
|
const selectedBorder = '{{ $c['selected_border'] }}';
|
|
document.querySelectorAll('.icon-btn').forEach(btn => {
|
|
btn.classList.remove(selectedBg, selectedBorder);
|
|
if (btn.dataset.icon === iconKey) {
|
|
btn.classList.add(selectedBg, selectedBorder);
|
|
}
|
|
});
|
|
};
|
|
|
|
// 아이콘 미리보기 업데이트
|
|
window.updateIconPreview = function(iconKey) {
|
|
const preview = document.getElementById('icon-preview');
|
|
const iconBtn = document.querySelector('.icon-btn[data-icon="' + iconKey + '"]');
|
|
if (iconBtn) {
|
|
const svg = iconBtn.querySelector('svg');
|
|
if (svg) {
|
|
const clonedSvg = svg.cloneNode(true);
|
|
clonedSvg.classList.remove('w-5', 'h-5');
|
|
clonedSvg.classList.add('w-6', 'h-6');
|
|
preview.innerHTML = '';
|
|
preview.appendChild(clonedSvg);
|
|
}
|
|
} else if (iconKey) {
|
|
preview.innerHTML = '<span class="text-xs text-gray-400">' + iconKey + '</span>';
|
|
}
|
|
};
|
|
})();
|
|
</script>
|
|
@endPushOnce
|