This commit is contained in:
Tomas Dvorak
2025-05-30 12:02:20 +02:00
parent fc782105b9
commit d0e1db92fc
+210 -83
View File
@@ -194,18 +194,94 @@
border-color: var(--secondary-color); border-color: var(--secondary-color);
transform: translateY(-2px); transform: translateY(-2px);
} }
/* Icon Picker Styles - Enhanced */ /* Full Screen Icon Picker Modal */
#iconDropdown { #iconPickerModal {
position: absolute; display: none;
width: 100%; position: fixed;
max-width: 500px; 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; background: white;
border: 1px solid #e5e7eb; border-radius: 1rem;
border-radius: 0.5rem; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
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;
overflow: hidden; 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 { #iconSearch {
@@ -229,13 +305,8 @@
#iconList { #iconList {
display: grid; display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr)); grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 0.75rem; gap: 1rem;
padding: 1rem;
max-height: 400px;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: #cbd5e0 #f1f5f9;
} }
#iconList::-webkit-scrollbar { #iconList::-webkit-scrollbar {
@@ -274,9 +345,9 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 0.75rem 0.5rem; padding: 1.5rem 0.5rem;
border: 1px solid #e5e7eb; border: 1px solid #e5e7eb;
border-radius: 0.5rem; border-radius: 0.75rem;
background: white; background: white;
cursor: pointer; cursor: pointer;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
@@ -285,37 +356,38 @@
} }
.icon-option i { .icon-option i {
font-size: 1.5rem; font-size: 2rem;
color: #4b5563; color: #4b5563;
margin-bottom: 0.25rem; margin-bottom: 0.5rem;
transition: all 0.2s ease; transition: all 0.2s ease;
pointer-events: none; pointer-events: none;
} }
.icon-option .icon-name { .icon-option .icon-name {
font-size: 0.625rem; font-size: 0.75rem;
color: #6b7280; color: #6b7280;
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
width: 100%; width: 100%;
opacity: 0; opacity: 1;
transform: translateY(5px); transform: translateY(0);
transition: all 0.2s ease; transition: all 0.2s ease;
pointer-events: none; pointer-events: none;
font-weight: 500;
} }
.icon-option:hover { .icon-option:hover {
background-color: #f3f4f6; background-color: #f8fafc;
border-color: #d1d5db; border-color: var(--primary-color);
transform: translateY(-2px); 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 { .icon-option:hover i {
color: var(--primary-color); color: var(--primary-color);
transform: scale(1.1); transform: scale(1.15);
} }
.icon-option:hover .icon-name { .icon-option:hover .icon-name {
@@ -326,6 +398,8 @@
.icon-option.active { .icon-option.active {
background-color: var(--primary-color); background-color: var(--primary-color);
border-color: var(--primary-hover); border-color: var(--primary-hover);
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(59, 130, 246, 0.3);
} }
.icon-option.active i, .icon-option.active i,
@@ -334,23 +408,44 @@
} }
/* Responsive adjustments */ /* Responsive adjustments */
@media (max-width: 768px) { @media (max-width: 1200px) {
#iconList { #iconList {
grid-template-columns: repeat(6, minmax(0, 1fr)); grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
gap: 0.5rem; }
padding: 0.75rem; }
@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 { .icon-option i {
font-size: 1.25rem; font-size: 1.75rem;
} }
} }
@media (max-width: 480px) { @media (max-width: 480px) {
#iconList { #iconList {
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(auto-fill, minmax(70px, 1fr));
gap: 0.5rem; 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,15 +1061,8 @@
<label class="block text-sm font-medium text-gray-700 mb-2">Ikona</label> <label class="block text-sm font-medium text-gray-700 mb-2">Ikona</label>
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<div class="relative flex-1"> <div class="relative flex-1">
<input type="text" id="appIcon" name="appIcon" class="form-control w-full" placeholder="Vyberte ikonu"> <input type="text" id="appIcon" name="appIcon" class="form-control w-full cursor-pointer" placeholder="Vyberte ikonu" readonly>
<div id="iconDropdown" class="absolute z-10 w-full mt-1 bg-white border border-gray-200 rounded-md shadow-lg hidden"> <input type="hidden" id="appIconClass">
<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> </div>
<div> <div>
<button type="button" id="customIconBtn" class="btn btn-secondary whitespace-nowrap"> <button type="button" id="customIconBtn" class="btn btn-secondary whitespace-nowrap">
@@ -1113,6 +1201,26 @@
</div> </div>
</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> <script>
// Get token and check authentication // Get token and check authentication
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
@@ -1466,12 +1574,12 @@ document.addEventListener('DOMContentLoaded', () => {
dropArea.addEventListener(eventName, unhighlight, false); dropArea.addEventListener(eventName, unhighlight, false);
}); });
function highlight(e) { function highlight() {
dropArea.classList.add('highlight'); dropArea.classList.add('dragover');
} }
function unhighlight(e) { function unhighlight() {
dropArea.classList.remove('highlight'); dropArea.classList.remove('dragover');
} }
// Handle drop // Handle drop
@@ -2069,12 +2177,13 @@ function openAddAppModal() {
document.getElementById('appModalTitle').textContent = 'Přidat aplikaci'; document.getElementById('appModalTitle').textContent = 'Přidat aplikaci';
// Select an icon // Select an icon
function selectIcon(iconClass) { function selectIcon(iconClass) {
const iconInput = document.getElementById('appIcon'); const iconInput = document.getElementById('appIcon');
const selectedIcon = document.getElementById('selectedIcon'); const selectedIcon = document.getElementById('selectedIcon');
const iconPreview = document.getElementById('iconPreview'); const iconPreview = document.getElementById('iconPreview');
const customIconPreview = document.getElementById('customIconPreview'); const customIconPreview = document.getElementById('customIconPreview');
const iconClassInput = document.getElementById('appIconClass'); const iconClassInput = document.getElementById('appIconClass');
const iconPickerModal = document.getElementById('iconPickerModal');
if (iconInput && selectedIcon && iconClassInput) { if (iconInput && selectedIcon && iconClassInput) {
// Hide any custom icon preview // Hide any custom icon preview
@@ -2098,22 +2207,16 @@ function openAddAppModal() {
.map(word => word.charAt(0).toUpperCase() + word.slice(1)) .map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' '); .join(' ');
// Scroll the selected icon into view in the dropdown // Highlight the selected icon in the modal
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 => { document.querySelectorAll('.icon-option').forEach(option => {
option.classList.toggle('active', option.getAttribute('data-icon') === iconClass); option.classList.toggle('active', option.getAttribute('data-icon') === iconClass);
}); });
}
// Hide the dropdown after a short delay // Close the modal after a short delay
setTimeout(() => { setTimeout(() => {
const dropdown = document.getElementById('iconDropdown'); if (iconPickerModal) {
if (dropdown) { iconPickerModal.classList.remove('show');
dropdown.classList.add('hidden'); document.body.style.overflow = '';
} }
}, 200); }, 200);
@@ -2317,39 +2420,63 @@ const iconCategories = {
// Initialize icon picker with enhanced functionality // Initialize icon picker with enhanced functionality
function initIconPicker() { function initIconPicker() {
const iconList = document.getElementById('iconList'); const iconInput = document.getElementById('appIcon');
if (!iconList) return; const iconPickerModal = document.getElementById('iconPickerModal');
const closeButton = document.getElementById('closeIconPicker');
renderIcons('');
// Add event listener for icon search
const iconSearch = document.getElementById('iconSearch'); 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) { if (iconSearch) {
iconSearch.addEventListener('input', function() { iconSearch.addEventListener('input', function() {
renderIcons(this.value.toLowerCase()); renderIcons(this.value.toLowerCase());
}); });
// Focus the search input when dropdown is shown // Prevent click events on search input from closing the modal
const iconInput = document.getElementById('appIcon'); iconSearch.addEventListener('click', function(e) {
if (iconInput) { e.stopPropagation();
iconInput.addEventListener('focus', function() {
setTimeout(() => {
iconSearch.focus();
}, 100);
}); });
} }
}
// Close dropdown when clicking outside // Initial render of icons
document.addEventListener('click', function(event) { renderIcons('');
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')) { // Close modal when pressing Escape key
dropdown.classList.add('hidden'); document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && iconPickerModal.classList.contains('show')) {
iconPickerModal.classList.remove('show');
document.body.style.overflow = '';
} }
}); });
} }