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:
+107
-17
@@ -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
@@ -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
@@ -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 icon‐grid, 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
@@ -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…
|
||||
];
|
||||
Reference in New Issue
Block a user