This commit is contained in:
Tomas Dvorak
2025-05-30 12:02:20 +02:00
parent fc782105b9
commit d0e1db92fc
+253 -126
View File
@@ -194,18 +194,94 @@
border-color: var(--secondary-color);
transform: translateY(-2px);
}
/* Icon Picker Styles - Enhanced */
#iconDropdown {
position: absolute;
width: 100%;
max-width: 500px;
/* Full Screen Icon Picker Modal */
#iconPickerModal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.8);
z-index: 9999;
padding: 2rem;
overflow-y: auto;
}
#iconPickerModal.show {
display: block;
}
#iconPickerContainer {
max-width: 1200px;
margin: 2rem auto;
background: white;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
z-index: 1000;
margin-top: 0.5rem;
border-radius: 1rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
overflow: hidden;
max-height: calc(100vh - 4rem);
display: flex;
flex-direction: column;
}
#iconPickerHeader {
padding: 1.5rem;
border-bottom: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
align-items: center;
background: #f9fafb;
}
#iconPickerHeader h3 {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
color: #111827;
}
#closeIconPicker {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #6b7280;
padding: 0.5rem;
border-radius: 0.375rem;
transition: all 0.2s;
}
#closeIconPicker:hover {
background: #e5e7eb;
color: #111827;
}
#iconSearchContainer {
padding: 1.5rem;
background: white;
border-bottom: 1px solid #e5e7eb;
}
#iconSearch {
width: 100%;
padding: 1rem 1.25rem;
border: 2px solid #e5e7eb;
border-radius: 0.75rem;
font-size: 1.125rem;
transition: all 0.2s;
}
#iconSearch:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
}
#iconListContainer {
flex: 1;
overflow-y: auto;
padding: 1.5rem;
background: white;
}
#iconSearch {
@@ -229,13 +305,8 @@
#iconList {
display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr));
gap: 0.75rem;
padding: 1rem;
max-height: 400px;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: #cbd5e0 #f1f5f9;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 1rem;
}
#iconList::-webkit-scrollbar {
@@ -274,9 +345,9 @@
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0.75rem 0.5rem;
padding: 1.5rem 0.5rem;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
border-radius: 0.75rem;
background: white;
cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
@@ -285,37 +356,38 @@
}
.icon-option i {
font-size: 1.5rem;
font-size: 2rem;
color: #4b5563;
margin-bottom: 0.25rem;
margin-bottom: 0.5rem;
transition: all 0.2s ease;
pointer-events: none;
}
.icon-option .icon-name {
font-size: 0.625rem;
font-size: 0.75rem;
color: #6b7280;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
opacity: 0;
transform: translateY(5px);
opacity: 1;
transform: translateY(0);
transition: all 0.2s ease;
pointer-events: none;
font-weight: 500;
}
.icon-option:hover {
background-color: #f3f4f6;
border-color: #d1d5db;
background-color: #f8fafc;
border-color: var(--primary-color);
transform: translateY(-2px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.icon-option:hover i {
color: var(--primary-color);
transform: scale(1.1);
transform: scale(1.15);
}
.icon-option:hover .icon-name {
@@ -326,6 +398,8 @@
.icon-option.active {
background-color: var(--primary-color);
border-color: var(--primary-hover);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(59, 130, 246, 0.3);
}
.icon-option.active i,
@@ -334,23 +408,44 @@
}
/* Responsive adjustments */
@media (max-width: 768px) {
@media (max-width: 1200px) {
#iconList {
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 0.5rem;
padding: 0.75rem;
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
}
}
@media (max-width: 768px) {
#iconPickerContainer {
margin: 0.5rem;
max-height: calc(100vh - 1rem);
}
#iconList {
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
gap: 0.75rem;
}
.icon-option i {
font-size: 1.25rem;
font-size: 1.75rem;
}
}
@media (max-width: 480px) {
#iconList {
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
gap: 0.5rem;
padding: 0.5rem;
}
.icon-option {
padding: 1rem 0.25rem;
}
.icon-option i {
font-size: 1.5rem;
}
.icon-option .icon-name {
font-size: 0.6875rem;
}
}
@@ -966,16 +1061,9 @@
<label class="block text-sm font-medium text-gray-700 mb-2">Ikona</label>
<div class="flex items-center space-x-4">
<div class="relative flex-1">
<input type="text" id="appIcon" name="appIcon" class="form-control w-full" placeholder="Vyberte ikonu">
<div id="iconDropdown" class="absolute z-10 w-full mt-1 bg-white border border-gray-200 rounded-md shadow-lg hidden">
<div class="p-2 border-b border-gray-200">
<input type="text" id="iconSearch" class="form-control text-sm w-full" placeholder="Hledat ikony...">
</div>
<div id="iconList" class="max-h-48 overflow-y-auto p-2 grid grid-cols-6 gap-2">
<!-- Icons will be populated by JavaScript -->
</div>
</div>
</div>
<input type="text" id="appIcon" name="appIcon" class="form-control w-full cursor-pointer" placeholder="Vyberte ikonu" readonly>
<input type="hidden" id="appIconClass">
</div>
<div>
<button type="button" id="customIconBtn" class="btn btn-secondary whitespace-nowrap">
<i class="fas fa-upload mr-1"></i> Vlastní
@@ -1113,6 +1201,26 @@
</div>
</div>
<!-- Icon Picker Modal -->
<div id="iconPickerModal" class="fixed inset-0 z-50 hidden">
<div id="iconPickerContainer" class="bg-white rounded-xl shadow-2xl overflow-hidden flex flex-col">
<div id="iconPickerHeader" class="bg-white border-b border-gray-200 px-6 py-4">
<h3 class="text-xl font-semibold text-gray-900">Vyberte ikonu</h3>
<button id="closeIconPicker" class="text-gray-400 hover:text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<div id="iconSearchContainer" class="px-6 py-4 border-b border-gray-200">
<input type="text" id="iconSearch" class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" placeholder="Hledat ikony..." autocomplete="off">
</div>
<div id="iconListContainer" class="flex-1 overflow-y-auto">
<div id="iconList" class="p-6">
<!-- Icons will be populated by JavaScript -->
</div>
</div>
</div>
</div>
<script>
// Get token and check authentication
const token = localStorage.getItem('token');
@@ -1466,12 +1574,12 @@ document.addEventListener('DOMContentLoaded', () => {
dropArea.addEventListener(eventName, unhighlight, false);
});
function highlight(e) {
dropArea.classList.add('highlight');
function highlight() {
dropArea.classList.add('dragover');
}
function unhighlight(e) {
dropArea.classList.remove('highlight');
function unhighlight() {
dropArea.classList.remove('dragover');
}
// Handle drop
@@ -2069,59 +2177,54 @@ function openAddAppModal() {
document.getElementById('appModalTitle').textContent = 'Přidat aplikaci';
// Select an icon
function selectIcon(iconClass) {
const iconInput = document.getElementById('appIcon');
const selectedIcon = document.getElementById('selectedIcon');
const iconPreview = document.getElementById('iconPreview');
const customIconPreview = document.getElementById('customIconPreview');
const iconClassInput = document.getElementById('appIconClass');
if (iconInput && selectedIcon && iconClassInput) {
// Hide any custom icon preview
if (customIconPreview) {
customIconPreview.classList.add('hidden');
customIconPreview.src = '';
}
// Show the Font Awesome icon
if (selectedIcon) {
selectedIcon.className = `fas ${iconClass} text-2xl text-blue-600`;
selectedIcon.classList.remove('hidden');
}
// Update the hidden input with the icon class
iconClassInput.value = iconClass;
// Update the visible input with a friendly name
const iconName = iconClass.replace('fa-', '').replace(/-/g, ' ');
iconInput.value = iconName.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
// Scroll the selected icon into view in the dropdown
const selectedOption = document.querySelector(`.icon-option[data-icon="${iconClass}"]`);
if (selectedOption) {
selectedOption.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
// Highlight the selected icon
document.querySelectorAll('.icon-option').forEach(option => {
option.classList.toggle('active', option.getAttribute('data-icon') === iconClass);
});
}
// Hide the dropdown after a short delay
setTimeout(() => {
const dropdown = document.getElementById('iconDropdown');
if (dropdown) {
dropdown.classList.add('hidden');
}
}, 200);
// Update the preview
if (iconPreview) {
iconPreview.className = 'w-12 h-12 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center mr-3';
}
function selectIcon(iconClass) {
const iconInput = document.getElementById('appIcon');
const selectedIcon = document.getElementById('selectedIcon');
const iconPreview = document.getElementById('iconPreview');
const customIconPreview = document.getElementById('customIconPreview');
const iconClassInput = document.getElementById('appIconClass');
const iconPickerModal = document.getElementById('iconPickerModal');
if (iconInput && selectedIcon && iconClassInput) {
// Hide any custom icon preview
if (customIconPreview) {
customIconPreview.classList.add('hidden');
customIconPreview.src = '';
}
// Show the Font Awesome icon
if (selectedIcon) {
selectedIcon.className = `fas ${iconClass} text-2xl text-blue-600`;
selectedIcon.classList.remove('hidden');
}
// Update the hidden input with the icon class
iconClassInput.value = iconClass;
// Update the visible input with a friendly name
const iconName = iconClass.replace('fa-', '').replace(/-/g, ' ');
iconInput.value = iconName.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
// Highlight the selected icon in the modal
document.querySelectorAll('.icon-option').forEach(option => {
option.classList.toggle('active', option.getAttribute('data-icon') === iconClass);
});
// Close the modal after a short delay
setTimeout(() => {
if (iconPickerModal) {
iconPickerModal.classList.remove('show');
document.body.style.overflow = '';
}
}, 200);
// Update the preview
if (iconPreview) {
iconPreview.className = 'w-12 h-12 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center mr-3';
}
}
}
// Reset icon selection
@@ -2317,39 +2420,63 @@ const iconCategories = {
// Initialize icon picker with enhanced functionality
function initIconPicker() {
const iconList = document.getElementById('iconList');
if (!iconList) return;
renderIcons('');
// Add event listener for icon search
const iconInput = document.getElementById('appIcon');
const iconPickerModal = document.getElementById('iconPickerModal');
const closeButton = document.getElementById('closeIconPicker');
const iconSearch = document.getElementById('iconSearch');
if (!iconInput || !iconPickerModal) return;
// Open modal when clicking the icon input
iconInput.addEventListener('click', function(e) {
e.preventDefault();
iconPickerModal.classList.add('show');
document.body.style.overflow = 'hidden';
// Focus search input after a short delay to ensure modal is visible
setTimeout(() => {
if (iconSearch) {
iconSearch.focus();
}
}, 100);
});
// Close modal when clicking the close button
if (closeButton) {
closeButton.addEventListener('click', function() {
iconPickerModal.classList.remove('show');
document.body.style.overflow = '';
});
}
// Close modal when clicking on the overlay (outside the modal)
iconPickerModal.addEventListener('click', function(e) {
if (e.target === iconPickerModal) {
iconPickerModal.classList.remove('show');
document.body.style.overflow = '';
}
});
// Handle search functionality
if (iconSearch) {
iconSearch.addEventListener('input', function() {
renderIcons(this.value.toLowerCase());
});
// Focus the search input when dropdown is shown
const iconInput = document.getElementById('appIcon');
if (iconInput) {
iconInput.addEventListener('focus', function() {
setTimeout(() => {
iconSearch.focus();
}, 100);
});
}
// Prevent click events on search input from closing the modal
iconSearch.addEventListener('click', function(e) {
e.stopPropagation();
});
}
// Close dropdown when clicking outside
document.addEventListener('click', function(event) {
const dropdown = document.getElementById('iconDropdown');
const iconInput = document.getElementById('appIcon');
const isClickInside = dropdown?.contains(event.target) ||
event.target === iconInput ||
event.target === document.getElementById('iconSearch');
if (!isClickInside && dropdown && !dropdown.classList.contains('hidden')) {
dropdown.classList.add('hidden');
// Initial render of icons
renderIcons('');
// Close modal when pressing Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && iconPickerModal.classList.contains('show')) {
iconPickerModal.classList.remove('show');
document.body.style.overflow = '';
}
});
}