/** * src/static/js/icons.js — v1.5.0 Phase 1 * * Local inline Lucide SVG icons (ISC license, https://lucide.dev/license). * 49 icons covering the v1.5.0 IA redesign + v1.4.6.11 emoji → SVG migration. * * Usage: * import { icon } from './icons.js'; * container.innerHTML = `${icon('home', {size: 20})} Home`; * button.innerHTML = icon('save', {size: 16, aria: 'Save'}); // role=img + Save * * All icons share Lucide canonical wrapper: * * * Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather * (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022. * Permission to use, copy, modify, and/or distribute this software for any purpose with or * without fee is hereby granted, provided that the above copyright notice and this permission * notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS. ISC License — https://lucide.dev/license */ /* eslint-disable */ const escapeAttr = (s) => String(s) .replace(/&/g, '&') .replace(/"/g, '"') .replace(//g, '>'); const ICONS = { // Navigation / Layout 'home': '', 'menu': '', 'chevron-down': '', 'chevron-right': '', 'x': '', // Network / Communication 'wifi': '', 'ethernet-port': '', 'globe': '', 'radio-tower': '', 'radio': '', 'flag': '', 'server': '', 'link': '', 'cable': '', 'route': '', 'smartphone': '', 'plug-zap': '', // Data 'bar-chart-3': '', 'clipboard-list': '', 'list': '', 'share-2': '', 'file-text': '', 'folder': '', 'package': '', 'box': '', 'puzzle': '', // Actions 'save': '', 'download': '', 'upload': '', 'trash-2': '', 'refresh-cw': '', 'radar': '', // Status 'check-circle-2': '', 'x-circle': '', 'alert-triangle': '', 'activity': '', 'hourglass': '', // Tool/Config 'wrench': '', 'settings': '', 'sliders-horizontal': '', 'gauge': '', 'factory': '', 'building-2': '', // Security / Vision 'eye': '', 'eye-off': '', 'lock': '', // Theme 'moon': '', 'sun': '', // System 'terminal-square': '', 'cpu': '', 'monitor': '', 'book-open': '', }; /** * Render an inline SVG icon. * * @param {string} name ICONS key * @param {Object} [opts] * @param {number} [opts.size=16] * @param {string} [opts.class=""] * @param {string} [opts.aria] if set → role="img" + ; else aria-hidden="true" * @returns {string} SVG HTML string, or '' if name unknown */ export function icon(name, opts = {}) { const body = ICONS[name]; if (!body) { if (typeof console !== 'undefined') console.warn('[icon] unknown:', name); return ''; } const size = Number(opts.size) || 16; // XSS-safe: strip dangerous chars from class option const cls = (typeof opts.class === 'string' ? opts.class : '').replace(/[<>"']/g, ''); let ariaAttrs, titleElement; if (opts.aria) { ariaAttrs = `role=\"img\"`; titleElement = `<title>${escapeAttr(opts.aria)}`; } else { ariaAttrs = `aria-hidden=\"true\"`; titleElement = ''; } return `${titleElement}${body}`; } /** All ICONS keys (for testing/introspection). */ export const ICON_NAMES = Object.keys(ICONS);