Redesign add bookmark popup and improve icon picker

Updated the add bookmark popup with a more modern Material card style, clearer field labels, and improved layout. The icon picker now fetches the full list of Material Icons asynchronously from Google Fonts, with a fallback to a minimal static set for immediate use. Enhanced the icon grid UI and selection logic for better usability.
This commit is contained in:
2025-07-26 10:51:20 +12:00
parent cfd2ccf50d
commit f04968c854
4 changed files with 164 additions and 172 deletions
+107 -17
View File
@@ -202,48 +202,138 @@ body, html {
align-items: center;
justify-content: center;
z-index: 99;
backdrop-filter: blur(4px); /* add subtle blur behind the overlay */
}
.popup.hidden {
display: none;
}
/* Popup inner as white Material card */
.popup-inner {
background: var(--dark-purple);
padding: 2rem;
border-radius: 12px;
display: flex;
flex-direction: column;
gap: 1rem;
color: var(--text);
min-width: 300px;
gap: 1.5rem;
color: #222222;
min-width: 320px;
/* existing styling */
background: #ffffff;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,0.3);
padding: 1.5rem;
transition: transform 0.3s ease-out, opacity 0.3s ease-out;
transform: translateY(-10px) scale(0.95);
opacity: 0;
width: 500px; /* make popup wider */
max-width: 90vw; /* keep it responsive on small screens */
}
.popup-inner input {
/* animate in when not hidden */
.popup:not(.hidden) .popup-inner {
transform: translateY(0) scale(1);
opacity: 1;
}
/* dialog title */
.popup-inner h2 {
margin: 0 0 1rem;
font-size: 1.5rem;
text-align: center;
color: #333333;
}
/* field labels */
.popup-inner label {
display: block;
margin-bottom: 0.25rem;
font-size: 0.875rem;
color: #555555;
}
/* text/url/icon inputs */
.popup-inner input[type="text"],
.popup-inner input[type="url"] {
width: 100%;
background: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
padding: 0.75rem 1rem;
font-size: 1rem;
color: #222222;
margin-bottom: 1rem;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.popup-inner input[type="text"]:focus,
.popup-inner input[type="url"]:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(123,46,255,0.2);
}
/* icon-grid container */
.icon-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
gap: 8px;
max-height: 200px;
overflow-y: auto;
margin: 0 -0.5rem 1rem;
padding: 0.5rem;
background: var(--dark-blue);
border: none;
color: var(--text);
border-radius: 6px;
background: #fafafa;
border: 1px solid #eee;
border-radius: 4px;
}
/* individual icon items */
.icon-item {
background: #ffffff;
border: 1px solid #ddd;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
border-radius: 4px;
cursor: pointer;
padding: 4px;
text-align: center;
}
.icon-item:hover {
background: #f0f0f0;
}
/* action buttons container */
.popup-buttons {
display: flex;
justify-content: flex-end;
gap: 1rem;
gap: 0.75rem;
}
.popup-buttons button {
min-width: 80px;
padding: 0.5rem 1rem;
background: var(--primary);
font-size: 0.875rem;
border-radius: 4px;
border: none;
border-radius: 6px;
color: white;
cursor: pointer;
}
.popup-buttons button:hover {
background: var(--accent);
/* Cancel button */
#cancelBtn {
background: #e0e0e0;
color: #222222;
}
#cancelBtn:hover {
background: #d5d5d5;
}
/* Add button */
#saveBookmarkBtn {
background: var(--primary);
color: #ffffff;
}
#saveBookmarkBtn:hover {
background: #6a24e5;
}
/* Color Palette */
+17 -8
View File
@@ -30,18 +30,27 @@
<!-- Popup for adding a bookmark -->
<div id="addPopup" class="popup hidden">
<div class="popup-inner">
<h2>Add Bookmark</h2>
<!-- ICON GRID SELECTOR -->
<label>Icon:</label>
<input type="text" id="iconFilter" placeholder="Search icons…" class="icon-filter">
<h2>Add New Bookmark</h2>
<!-- Title field -->
<label for="titleInput">Title</label>
<input type="text" id="titleInput" placeholder="Enter title">
<!-- URL field -->
<label for="urlInput">URL</label>
<input type="url" id="urlInput" placeholder="https://example.com">
<!-- Icon picker -->
<label for="iconFilter">Icon</label>
<input type="text" id="iconFilter" class="icon-filter"
placeholder="Search for icon or enter emoji">
<div id="iconGrid" class="icon-grid"></div>
<input type="hidden" id="selectedIcon">
<!-- TITLE & URL -->
<input type="text" id="titleInput" placeholder="Title">
<input type="url" id="urlInput" placeholder="https://...">
<!-- action buttons -->
<div class="popup-buttons">
<button id="saveBookmarkBtn">Save</button>
<button id="cancelBtn">Cancel</button>
<button id="saveBookmarkBtn">Add</button>
</div>
</div>
</div>
+20 -5
View File
@@ -1,4 +1,4 @@
import { icons } from './icons.js';
import { icons as initialIcons, fetchAllIcons } from './icons.js';
const BOOKMARKS_KEY = 'steamos_browser_bookmarks';
@@ -13,7 +13,8 @@ const searchInput = document.getElementById('searchInput');
const iconFilter = document.getElementById('iconFilter');
const iconGrid = document.getElementById('iconGrid');
const selectedIconInput= document.getElementById('selectedIcon');
let selectedIcon = icons[0];
let selectedIcon = initialIcons[0];
let availableIcons = initialIcons;
let bookmarks = JSON.parse(localStorage.getItem(BOOKMARKS_KEY)) || [];
@@ -69,15 +70,17 @@ function renderBookmarks() {
// draw the icongrid, filtering by the search term
function renderIconGrid(filter = '') {
iconGrid.innerHTML = '';
icons
availableIcons
.filter(name => name.includes(filter))
.forEach(name => {
const span = document.createElement('span');
span.className = 'material-symbols-outlined icon-item';
span.textContent = name;
span.onclick = () => {
iconGrid.querySelectorAll('.icon-item')
.forEach(el => el.classList.remove('selected'));
const currentSelected = iconGrid.querySelector('.icon-item.selected');
if (currentSelected) {
currentSelected.classList.remove('selected');
}
span.classList.add('selected');
selectedIcon = name;
selectedIconInput.value = name;
@@ -96,6 +99,18 @@ iconFilter.addEventListener('input', () =>
// initial render
renderIconGrid();
// Asynchronously fetch all icons and update the grid
(async () => {
try {
const allIcons = await fetchAllIcons();
availableIcons = allIcons;
// Re-render with the full list, preserving any filter text
renderIconGrid(iconFilter.value.trim().toLowerCase());
} catch (error) {
console.error('Failed to fetch all icons:', error);
}
})();
saveBookmarkBtn.onclick = () => {
const title = titleInput.value.trim();
const url = urlInput.value.trim();
+20 -142
View File
@@ -1,143 +1,21 @@
export const icons = [
'home',
'star',
'bookmark',
'favorite',
'public',
'search',
'settings',
'3d_rotation',
'ac_unit',
'access_alarm',
'access_alarms',
'access_time',
'accessibility',
'accessibility_new',
'accessible',
'accessible_forward',
'account_balance',
'account_balance_wallet',
'account_box',
'account_circle',
'adb',
'add',
'add_a_photo',
'add_alarm',
'add_alert',
'add_box',
'add_business',
'add_call',
'add_circle',
'add_circle_outline',
'add_comment',
'add_home',
'add_ic_call',
'add_link',
'add_location',
'add_photo_alternate',
'add_road',
'add_shopping_cart',
'add_task',
'add_to_drive',
'add_to_home_screen',
'add_to_photos',
'add_to_queue',
'adjust',
'admin_panel_settings',
'agriculture',
'airline_seat_flat',
'airline_seat_flat_angled',
'airline_seat_individual_suite',
'airline_seat_legroom_extra',
'airline_seat_legroom_normal',
'airline_seat_legroom_reduced',
'airline_seat_recline_extra',
'airline_seat_recline_normal',
'airplanemode_active',
'airplanemode_inactive',
'airplay',
'airport_shuttle',
'alarm',
'alarm_add',
'alarm_off',
'alarm_on',
'album',
'align_horizontal_center',
'align_horizontal_left',
'align_horizontal_right',
'align_vertical_bottom',
'align_vertical_center',
'align_vertical_top',
'all_inbox',
'all_inclusive',
'all_out',
'alt_route',
'analytics',
'anchor',
'android',
'animation',
'announcement',
'apartment',
'api',
'app_blocking',
'app_registration',
'app_settings_alt',
'approval',
'apps',
'archive',
'area_chart',
'arrow_back',
'arrow_back_ios',
'arrow_circle_down',
'arrow_circle_up',
'arrow_downward',
'arrow_drop_down',
'arrow_drop_down_circle',
'arrow_drop_up',
'arrow_forward',
'arrow_forward_ios',
'arrow_left',
'arrow_right',
'arrow_right_alt',
'arrow_upward',
'art_track',
'article',
'aspect_ratio',
'assessment',
'assignment',
'assignment_ind',
'assignment_late',
'assignment_return',
'assignment_returned',
'assignment_turned_in',
'assistant',
'assistant_photo',
'atm',
'attach_email',
'attach_file',
'attach_money',
'attachment',
'attractions',
'attribution',
'audiotrack',
'auto_awesome',
'auto_awesome_mosaic',
'auto_delete',
'auto_fix_high',
'auto_fix_normal',
'auto_fix_off',
'auto_graph',
'auto_stories',
'autorenew',
'av_timer',
'baby_changing_station',
'backpack',
'backspace',
'backup',
'badge',
'zoom_in',
'zoom_out',
'zoom_out_map'
];
// This file is automatically generated from Google's Material Icons.
/**
* Fetches the full list of Material Icon names from Google Fonts.
* Returns an array of strings like ["3d_rotation","access_alarm",…]
*/
export async function fetchAllIcons() {
const res = await fetch("https://fonts.google.com/metadata/icons");
let txt = await res.text();
// strip the weird prefix )]}'\n
txt = txt.replace(/^\)\]\}'\s*/, "");
const json = JSON.parse(txt);
return json.icons.map(icon => icon.name);
}
//Icons from fonts.google.com/icons
// Fallback static array for immediate use (e.g. the "+" button and bookmark icons)
export const icons = [
'add',
'bookmark',
'star',
// …add any other icons your components expect synchronously…
];