@moraby/app-launcher 0.1.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/AppLauncher.tsx","../src/icons.ts"],"sourcesContent":["// Main component\r\nexport { AppLauncher, AppLauncher as default } from './AppLauncher';\r\n\r\n// Types\r\nexport type { \r\n AppLauncherProps, \r\n AppItem, \r\n AppLauncherConfig,\r\n ResolvedApp,\r\n} from './types';\r\n\r\n// Icons (in case consumers want to use them)\r\nexport { iconMap, getIcon } from './icons';\r\n","'use client';\r\n\r\nimport React, { useState, useRef, useEffect } from 'react';\r\nimport { IoApps } from 'react-icons/io5';\r\nimport { AppLauncherProps, AppItem, ResolvedApp, AppLauncherConfig } from './types';\r\nimport { getIcon } from './icons';\r\nimport './styles.css';\r\n\r\n/**\r\n * A Google-style app launcher component\r\n *\r\n * @example\r\n * // With config URL\r\n * <AppLauncher configUrl=\"https://example.com/apps.json\" />\r\n *\r\n * @example\r\n * // With direct apps array\r\n * <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />\r\n */\r\nexport function AppLauncher({\r\n configUrl,\r\n apps: propApps,\r\n className,\r\n onAppClick,\r\n renderFooter,\r\n}: AppLauncherProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [apps, setApps] = useState<ResolvedApp[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Resolve apps from props or fetch from URL\r\n useEffect(() => {\r\n if (propApps) {\r\n setApps(propApps.map(resolveApp));\r\n return;\r\n }\r\n\r\n if (configUrl) {\r\n setLoading(true);\r\n setError(null);\r\n\r\n fetch(configUrl)\r\n .then((res) => {\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);\r\n return res.json();\r\n })\r\n .then((config: AppLauncherConfig) => {\r\n setApps(config.apps.map(resolveApp));\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n console.error('AppLauncher: Failed to load config', err);\r\n })\r\n .finally(() => setLoading(false));\r\n }\r\n }, [configUrl, propApps]);\r\n\r\n // Close on click outside\r\n useEffect(() => {\r\n function handleClickOutside(event: MouseEvent) {\r\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\r\n setIsOpen(false);\r\n }\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n }\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }, [isOpen]);\r\n\r\n // Close on Escape\r\n useEffect(() => {\r\n function handleEscape(event: KeyboardEvent) {\r\n if (event.key === 'Escape') setIsOpen(false);\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('keydown', handleEscape);\r\n }\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, [isOpen]);\r\n\r\n // Check if icon is a custom URL (path or full URL)\r\n function isCustomIconUrl(icon: string): boolean {\r\n return icon.startsWith('/') || icon.startsWith('http');\r\n }\r\n\r\n function resolveApp(app: AppItem): ResolvedApp {\r\n const isCustom = isCustomIconUrl(app.icon);\r\n return {\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: isCustom ? null : getIcon(app.icon),\r\n customIconUrl: isCustom ? app.icon : null,\r\n color: app.color,\r\n description: app.description,\r\n };\r\n }\r\n\r\n function handleAppClick(app: ResolvedApp) {\r\n if (onAppClick) {\r\n onAppClick({\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: app.customIconUrl || (app.icon?.name ?? 'FaRocket'),\r\n color: app.color,\r\n description: app.description,\r\n });\r\n } else {\r\n // Open in new tab\r\n window.open(app.url, '_blank', 'noopener,noreferrer');\r\n }\r\n }\r\n\r\n return (\r\n <div className={`app-launcher ${className || ''}`} ref={containerRef}>\r\n {/* Trigger Button */}\r\n <button\r\n className=\"app-launcher__trigger\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n aria-label=\"Open app launcher\"\r\n aria-expanded={isOpen}\r\n >\r\n <IoApps className=\"app-launcher__trigger-icon\" />\r\n </button>\r\n\r\n {/* Dropdown */}\r\n {isOpen && (\r\n <div className=\"app-launcher__dropdown\">\r\n {loading && <div className=\"app-launcher__loading\">Loading...</div>}\r\n\r\n {error && <div className=\"app-launcher__error\">{error}</div>}\r\n\r\n {!loading && !error && (\r\n <div className=\"app-launcher__grid\">\r\n {apps.map((app) => (\r\n <button\r\n key={app.id}\r\n className=\"app-launcher__item\"\r\n onClick={() => handleAppClick(app)}\r\n title={app.description || app.name}\r\n >\r\n <div className=\"app-launcher__icon-wrapper\">\r\n {app.customIconUrl ? (\r\n // Check if it's an SVG - use mask-image for color support\r\n app.customIconUrl.endsWith('.svg') ? (\r\n <div\r\n className=\"app-launcher__icon app-launcher__icon--svg\"\r\n style={{\r\n maskImage: `url(${app.customIconUrl})`,\r\n WebkitMaskImage: `url(${app.customIconUrl})`,\r\n backgroundColor: app.color === 'transparent' ? '#5f6368' : app.color,\r\n }}\r\n aria-label={app.name}\r\n />\r\n ) : (\r\n // Regular image for PNG, JPG, etc.\r\n <img\r\n src={app.customIconUrl}\r\n alt={app.name}\r\n className=\"app-launcher__icon app-launcher__icon--custom\"\r\n />\r\n )\r\n ) : app.icon ? (\r\n <app.icon className=\"app-launcher__icon\" style={{ color: app.color }} />\r\n ) : null}\r\n </div>\r\n <span className=\"app-launcher__name\">{app.name}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Custom footer (e.g., Settings button) */}\r\n {renderFooter && <div className=\"app-launcher__footer\">{renderFooter()}</div>}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default AppLauncher;\r\n","import { IconType } from 'react-icons';\r\n// prettier-ignore\r\nimport {\r\n FaGoogle, FaEnvelope, FaYoutube, FaCalendarAlt, FaMapMarkerAlt,\r\n FaFile, FaBookmark, FaTable, FaNewspaper, FaImage, FaRocket,\r\n FaHome, FaUser, FaCog, FaChartBar, FaShoppingCart, FaDatabase,\r\n FaCode, FaTerminal, FaGlobe, FaLock, FaKey, FaBell, FaHeart,\r\n FaStar, FaFolder, FaClipboard, FaCalculator, FaMusic, FaCamera,\r\n FaGamepad, FaPuzzlePiece, FaBriefcase, FaGraduationCap, FaPlane,\r\n FaCar, FaBicycle, FaUtensils, FaCoffee, FaGift,\r\n} from 'react-icons/fa';\r\nimport { FaTicketSimple } from 'react-icons/fa6';\r\nimport { GrDeliver } from 'react-icons/gr';\r\nimport { IoBusinessSharp, IoApps } from 'react-icons/io5';\r\nimport { MdOutlineSecurity, MdDashboard, MdAnalytics, MdEmail, MdWork } from 'react-icons/md';\r\nimport { SiGoogledrive, SiGooglemeet } from 'react-icons/si';\r\nimport { AiOutlineSecurityScan } from 'react-icons/ai';\r\n\r\n/**\r\n * Map of icon names to icon components\r\n */\r\n// prettier-ignore\r\nexport const iconMap: Record<string, IconType> = {\r\n // Business & Work\r\n FaBriefcase, IoBusinessSharp, MdWork, FaGraduationCap,\r\n // Security\r\n MdOutlineSecurity, FaLock, FaKey, AiOutlineSecurityScan,\r\n // Communication\r\n FaEnvelope, MdEmail, FaBell,\r\n // Media\r\n FaYoutube, FaMusic, FaCamera, FaImage, FaGamepad,\r\n // Productivity\r\n FaCalendarAlt, FaClipboard, FaCalculator, FaFolder, FaFile,\r\n FaBookmark, FaTable, FaNewspaper,\r\n // Navigation\r\n FaMapMarkerAlt, FaGlobe, FaHome,\r\n // Google\r\n FaGoogle, SiGoogledrive, SiGooglemeet,\r\n // Development\r\n FaCode, FaTerminal, FaDatabase, FaPuzzlePiece,\r\n // Analytics\r\n FaChartBar, MdDashboard, MdAnalytics,\r\n // Shopping\r\n FaShoppingCart, FaGift, FaTicketSimple,\r\n // Travel\r\n FaPlane, FaCar, FaBicycle, GrDeliver,\r\n // Food\r\n FaUtensils, FaCoffee,\r\n // General\r\n FaRocket, FaUser, FaCog, FaHeart, FaStar, IoApps,\r\n};\r\n\r\n/**\r\n * Get icon component by name\r\n */\r\nexport function getIcon(name: string): IconType {\r\n return iconMap[name] || FaRocket;\r\n}\r\n"],"mappings":"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","names":["import_io5"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/AppLauncher.tsx","../src/icons.ts","../src/AppSettings.tsx","../src/AddAppForm.tsx","../src/IconPicker.tsx"],"sourcesContent":["// Main component\r\nexport { AppLauncher, AppLauncher as default } from './AppLauncher';\r\n\r\n// Types\r\nexport type { AppLauncherProps, AppItem, AppLauncherConfig, ResolvedApp } from './types';\r\n\r\n// Icons (in case consumers want to use them)\r\nexport { iconMap, getIcon } from './icons';\r\n\r\n// Settings Component\r\nexport { AppSettings } from './AppSettings';\r\nexport type { AppSettingsProps } from './AppSettings';\r\n","'use client';\r\n\r\nimport React, { useState, useRef, useEffect } from 'react';\r\nimport { IoApps } from 'react-icons/io5';\r\nimport { AppLauncherProps, AppItem, ResolvedApp, AppLauncherConfig } from './types';\r\nimport { getIcon } from './icons';\r\nimport './styles.css';\r\n\r\n/**\r\n * A Google-style app launcher component\r\n *\r\n * @example\r\n * // With config URL\r\n * <AppLauncher configUrl=\"https://example.com/apps.json\" />\r\n *\r\n * @example\r\n * // With direct apps array\r\n * <AppLauncher apps={[{ id: '1', name: 'App', url: '/app', icon: 'FaRocket', color: '#4285F4' }]} />\r\n */\r\nexport function AppLauncher({\r\n configUrl,\r\n apps: propApps,\r\n className,\r\n onAppClick,\r\n renderFooter,\r\n}: AppLauncherProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n const [apps, setApps] = useState<ResolvedApp[]>([]);\r\n const [loading, setLoading] = useState(false);\r\n const [error, setError] = useState<string | null>(null);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n\r\n // Resolve apps from props or fetch from URL\r\n useEffect(() => {\r\n if (propApps) {\r\n setApps(propApps.map(resolveApp));\r\n return;\r\n }\r\n\r\n if (configUrl) {\r\n setLoading(true);\r\n setError(null);\r\n\r\n fetch(configUrl)\r\n .then((res) => {\r\n if (!res.ok) throw new Error(`Failed to fetch: ${res.status}`);\r\n return res.json();\r\n })\r\n .then((config: AppLauncherConfig) => {\r\n setApps(config.apps.map(resolveApp));\r\n })\r\n .catch((err) => {\r\n setError(err.message);\r\n console.error('AppLauncher: Failed to load config', err);\r\n })\r\n .finally(() => setLoading(false));\r\n }\r\n }, [configUrl, propApps]);\r\n\r\n // Close on click outside\r\n useEffect(() => {\r\n function handleClickOutside(event: MouseEvent) {\r\n if (containerRef.current && !containerRef.current.contains(event.target as Node)) {\r\n setIsOpen(false);\r\n }\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('mousedown', handleClickOutside);\r\n }\r\n return () => document.removeEventListener('mousedown', handleClickOutside);\r\n }, [isOpen]);\r\n\r\n // Close on Escape\r\n useEffect(() => {\r\n function handleEscape(event: KeyboardEvent) {\r\n if (event.key === 'Escape') setIsOpen(false);\r\n }\r\n\r\n if (isOpen) {\r\n document.addEventListener('keydown', handleEscape);\r\n }\r\n return () => document.removeEventListener('keydown', handleEscape);\r\n }, [isOpen]);\r\n\r\n // Check if icon is a custom URL (path or full URL)\r\n function isCustomIconUrl(icon: string): boolean {\r\n return icon.startsWith('/') || icon.startsWith('http');\r\n }\r\n\r\n function resolveApp(app: AppItem): ResolvedApp {\r\n const isCustom = isCustomIconUrl(app.icon);\r\n return {\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: isCustom ? null : getIcon(app.icon),\r\n customIconUrl: isCustom ? app.icon : null,\r\n color: app.color,\r\n description: app.description,\r\n };\r\n }\r\n\r\n function handleAppClick(app: ResolvedApp) {\r\n if (onAppClick) {\r\n onAppClick({\r\n id: app.id,\r\n name: app.name,\r\n url: app.url,\r\n icon: app.customIconUrl || (app.icon?.name ?? 'FaRocket'),\r\n color: app.color,\r\n description: app.description,\r\n });\r\n } else {\r\n // Open in new tab\r\n window.open(app.url, '_blank', 'noopener,noreferrer');\r\n }\r\n }\r\n\r\n return (\r\n <div className={`app-launcher ${className || ''}`} ref={containerRef}>\r\n {/* Trigger Button */}\r\n <button\r\n className=\"app-launcher__trigger\"\r\n onClick={() => setIsOpen(!isOpen)}\r\n aria-label=\"Open app launcher\"\r\n aria-expanded={isOpen}\r\n >\r\n <IoApps className=\"app-launcher__trigger-icon\" />\r\n </button>\r\n\r\n {/* Dropdown */}\r\n {isOpen && (\r\n <div className=\"app-launcher__dropdown\">\r\n {loading && <div className=\"app-launcher__loading\">Loading...</div>}\r\n\r\n {error && <div className=\"app-launcher__error\">{error}</div>}\r\n\r\n {!loading && !error && (\r\n <div className=\"app-launcher__grid\">\r\n {apps.map((app) => (\r\n <button\r\n key={app.id}\r\n className=\"app-launcher__item\"\r\n onClick={() => handleAppClick(app)}\r\n title={app.description || app.name}\r\n >\r\n <div className=\"app-launcher__icon-wrapper\">\r\n {app.customIconUrl ? (\r\n // Check if it's an SVG - use mask-image for color support\r\n app.customIconUrl.endsWith('.svg') ? (\r\n <div\r\n className=\"app-launcher__icon app-launcher__icon--svg\"\r\n style={{\r\n maskImage: `url(${app.customIconUrl})`,\r\n WebkitMaskImage: `url(${app.customIconUrl})`,\r\n backgroundColor: app.color === 'transparent' ? '#5f6368' : app.color,\r\n }}\r\n aria-label={app.name}\r\n />\r\n ) : (\r\n // Regular image for PNG, JPG, etc.\r\n <img\r\n src={app.customIconUrl}\r\n alt={app.name}\r\n className=\"app-launcher__icon app-launcher__icon--custom\"\r\n />\r\n )\r\n ) : app.icon ? (\r\n <app.icon className=\"app-launcher__icon\" style={{ color: app.color }} />\r\n ) : null}\r\n </div>\r\n <span className=\"app-launcher__name\">{app.name}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {/* Custom footer (e.g., Settings button) */}\r\n {renderFooter && <div className=\"app-launcher__footer\">{renderFooter()}</div>}\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nexport default AppLauncher;\r\n","import { IconType } from 'react-icons';\r\n// prettier-ignore\r\nimport {\r\n FaGoogle, FaEnvelope, FaYoutube, FaCalendarAlt, FaMapMarkerAlt,\r\n FaFile, FaBookmark, FaTable, FaNewspaper, FaImage, FaRocket,\r\n FaHome, FaUser, FaCog, FaChartBar, FaShoppingCart, FaDatabase,\r\n FaCode, FaTerminal, FaGlobe, FaLock, FaKey, FaBell, FaHeart,\r\n FaStar, FaFolder, FaClipboard, FaCalculator, FaMusic, FaCamera,\r\n FaGamepad, FaPuzzlePiece, FaBriefcase, FaGraduationCap, FaPlane,\r\n FaCar, FaBicycle, FaUtensils, FaCoffee, FaGift,\r\n} from 'react-icons/fa';\r\nimport { FaTicketSimple } from 'react-icons/fa6';\r\nimport { GrDeliver } from 'react-icons/gr';\r\nimport { IoBusinessSharp, IoApps } from 'react-icons/io5';\r\nimport { MdOutlineSecurity, MdDashboard, MdAnalytics, MdEmail, MdWork } from 'react-icons/md';\r\nimport { SiGoogledrive, SiGooglemeet } from 'react-icons/si';\r\nimport { AiOutlineSecurityScan } from 'react-icons/ai';\r\n\r\n/**\r\n * Map of icon names to icon components\r\n */\r\n// prettier-ignore\r\nexport const iconMap: Record<string, IconType> = {\r\n // Business & Work\r\n FaBriefcase, IoBusinessSharp, MdWork, FaGraduationCap,\r\n // Security\r\n MdOutlineSecurity, FaLock, FaKey, AiOutlineSecurityScan,\r\n // Communication\r\n FaEnvelope, MdEmail, FaBell,\r\n // Media\r\n FaYoutube, FaMusic, FaCamera, FaImage, FaGamepad,\r\n // Productivity\r\n FaCalendarAlt, FaClipboard, FaCalculator, FaFolder, FaFile,\r\n FaBookmark, FaTable, FaNewspaper,\r\n // Navigation\r\n FaMapMarkerAlt, FaGlobe, FaHome,\r\n // Google\r\n FaGoogle, SiGoogledrive, SiGooglemeet,\r\n // Development\r\n FaCode, FaTerminal, FaDatabase, FaPuzzlePiece,\r\n // Analytics\r\n FaChartBar, MdDashboard, MdAnalytics,\r\n // Shopping\r\n FaShoppingCart, FaGift, FaTicketSimple,\r\n // Travel\r\n FaPlane, FaCar, FaBicycle, GrDeliver,\r\n // Food\r\n FaUtensils, FaCoffee,\r\n // General\r\n FaRocket, FaUser, FaCog, FaHeart, FaStar, IoApps,\r\n};\r\n\r\n/**\r\n * Get icon component by name\r\n */\r\nexport function getIcon(name: string): IconType {\r\n return iconMap[name] || FaRocket;\r\n}\r\n","import React, { useState } from 'react';\r\nimport { FaTimes, FaPlus, FaEdit, FaTrash, FaDownload } from 'react-icons/fa';\r\nimport { AddAppForm } from './AddAppForm';\r\nimport { AppItem } from './types';\r\nimport { iconMap } from './icons';\r\n\r\nexport interface AppSettingsProps {\r\n isOpen: boolean;\r\n onClose: () => void;\r\n apps: AppItem[];\r\n defaultApps?: AppItem[];\r\n onAdd: (app: Omit<AppItem, 'id'>) => void;\r\n onUpdate: (id: string, app: Partial<Omit<AppItem, 'id'>>) => void;\r\n onDelete: (id: string) => void;\r\n}\r\n\r\nexport function AppSettings({\r\n isOpen,\r\n onClose,\r\n apps,\r\n defaultApps = [],\r\n onAdd,\r\n onUpdate,\r\n onDelete,\r\n}: AppSettingsProps) {\r\n const [showAddForm, setShowAddForm] = useState(false);\r\n const [editingApp, setEditingApp] = useState<AppItem | null>(null);\r\n\r\n if (!isOpen) return null;\r\n\r\n const handleAddSubmit = (data: Omit<AppItem, 'id'>) => {\r\n onAdd(data);\r\n setShowAddForm(false);\r\n };\r\n\r\n const handleEditSubmit = (data: Omit<AppItem, 'id'>) => {\r\n if (editingApp) {\r\n onUpdate(editingApp.id, data);\r\n setEditingApp(null);\r\n }\r\n };\r\n\r\n const handleDelete = (id: string) => {\r\n if (confirm('Are you sure you want to delete this app?')) {\r\n onDelete(id);\r\n }\r\n };\r\n\r\n const handleExportConfig = () => {\r\n // Combine default apps and user apps into exportable format\r\n const exportedApps = [\r\n // User apps first (they appear at top)\r\n ...apps,\r\n // Then default apps\r\n ...defaultApps.filter((da) => !apps.some((ua) => ua.id === da.id)), // Avoid duplicates if any\r\n ];\r\n\r\n const config = {\r\n version: '1.0',\r\n exportedAt: new Date().toISOString(),\r\n apps: exportedApps,\r\n };\r\n\r\n // Create and download JSON file\r\n const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });\r\n const url = URL.createObjectURL(blob);\r\n const a = document.createElement('a');\r\n a.href = url;\r\n a.download = 'app-launcher-config.json';\r\n document.body.appendChild(a);\r\n a.click();\r\n document.body.removeChild(a);\r\n URL.revokeObjectURL(url);\r\n };\r\n\r\n return (\r\n <div className=\"app-settings__overlay\">\r\n <div className=\"app-settings__modal\" onClick={(e) => e.stopPropagation()}>\r\n {/* Header */}\r\n <div className=\"app-settings__header\">\r\n <h2 className=\"app-settings__title\">\r\n {showAddForm ? 'Add New App' : editingApp ? 'Edit App' : 'Settings'}\r\n </h2>\r\n <button className=\"app-settings__close-button\" onClick={onClose} aria-label=\"Close\">\r\n <FaTimes />\r\n </button>\r\n </div>\r\n\r\n {/* Content */}\r\n <div className=\"app-settings__content\">\r\n {showAddForm ? (\r\n <AddAppForm\r\n onSubmit={handleAddSubmit}\r\n onCancel={() => setShowAddForm(false)}\r\n submitLabel=\"Add App\"\r\n />\r\n ) : editingApp ? (\r\n <AddAppForm\r\n initialData={editingApp}\r\n onSubmit={handleEditSubmit}\r\n onCancel={() => setEditingApp(null)}\r\n submitLabel=\"Save Changes\"\r\n />\r\n ) : (\r\n <>\r\n {/* Add App Button */}\r\n <button className=\"app-settings__add-button\" onClick={() => setShowAddForm(true)}>\r\n <FaPlus className=\"app-settings__add-icon\" />\r\n Add New App\r\n </button>\r\n\r\n {/* Apps List */}\r\n <div className=\"app-settings__list\">\r\n <h3 className=\"app-settings__section-title\">My Custom Apps</h3>\r\n\r\n {apps.length === 0 ? (\r\n <p className=\"app-settings__empty-message\">\r\n No custom apps yet. Click &quot;Add New App&quot; to get started.\r\n </p>\r\n ) : (\r\n <div className=\"app-settings__grid\">\r\n {apps.map((app) => {\r\n // Determine icon\r\n const isCustom = app.icon.startsWith('/') || app.icon.startsWith('http');\r\n const Icon = !isCustom ? iconMap[app.icon] || iconMap['FaRocket'] : null;\r\n\r\n return (\r\n <div key={app.id} className=\"app-settings__card\">\r\n <div className=\"app-settings__card-info\">\r\n <div\r\n className=\"app-settings__card-icon\"\r\n style={{ backgroundColor: app.color + '20' }}\r\n >\r\n {isCustom ? (\r\n app.icon.endsWith('.svg') ? (\r\n <div\r\n style={{\r\n width: 20,\r\n height: 20,\r\n maskImage: `url(${app.icon})`,\r\n WebkitMaskImage: `url(${app.icon})`,\r\n maskSize: 'contain',\r\n maskRepeat: 'no-repeat',\r\n maskPosition: 'center',\r\n WebkitMaskSize: 'contain',\r\n WebkitMaskRepeat: 'no-repeat',\r\n WebkitMaskPosition: 'center',\r\n backgroundColor:\r\n app.color === 'transparent' ? '#5f6368' : app.color,\r\n }}\r\n />\r\n ) : (\r\n <img\r\n src={app.icon}\r\n alt={app.name}\r\n style={{ width: 20, height: 20, objectFit: 'contain' }}\r\n />\r\n )\r\n ) : (\r\n Icon && <Icon style={{ color: app.color }} />\r\n )}\r\n </div>\r\n <div className=\"app-settings__card-details\">\r\n <span className=\"app-settings__card-name\">{app.name}</span>\r\n <span className=\"app-settings__card-url\">{app.url}</span>\r\n </div>\r\n </div>\r\n <div className=\"app-settings__card-actions\">\r\n <button\r\n className=\"app-settings__action-button\"\r\n onClick={() => setEditingApp(app)}\r\n title=\"Edit\"\r\n >\r\n <FaEdit />\r\n </button>\r\n <button\r\n className=\"app-settings__action-button app-settings__action-button--delete\"\r\n onClick={() => handleDelete(app.id)}\r\n title=\"Delete\"\r\n >\r\n <FaTrash />\r\n </button>\r\n </div>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Export Configuration */}\r\n <button className=\"app-settings__export-button\" onClick={handleExportConfig}>\r\n <FaDownload className=\"app-settings__export-icon\" />\r\n Export Configuration\r\n </button>\r\n\r\n {/* Info */}\r\n <div className=\"app-settings__info\">\r\n <p>\r\n Custom apps are saved in your browser. Use &quot;Export Configuration&quot; to\r\n share with other apps.\r\n </p>\r\n </div>\r\n </>\r\n )}\r\n </div>\r\n </div>\r\n </div>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { IconPicker } from './IconPicker';\r\nimport { iconMap } from './icons';\r\nimport { AppItem } from './types';\r\n\r\nexport interface AddAppFormProps {\r\n initialData?: Partial<AppItem>;\r\n onSubmit: (data: Omit<AppItem, 'id'>) => void;\r\n onCancel: () => void;\r\n submitLabel?: string;\r\n}\r\n\r\nconst presetColors = [\r\n '#4285F4', // Blue\r\n '#EA4335', // Red\r\n '#FBBC04', // Yellow\r\n '#34A853', // Green\r\n '#FF6D01', // Orange\r\n '#46BDC6', // Teal\r\n '#7B1FA2', // Purple\r\n '#E91E63', // Pink\r\n '#607D8B', // Gray\r\n '#795548', // Brown\r\n];\r\n\r\ntype IconMode = 'picker' | 'custom';\r\n\r\nexport function AddAppForm({\r\n initialData,\r\n onSubmit,\r\n onCancel,\r\n submitLabel = 'Add App',\r\n}: AddAppFormProps) {\r\n const [name, setName] = useState(initialData?.name || '');\r\n const [url, setUrl] = useState(initialData?.url || '');\r\n\r\n // Determine initial icon state\r\n const initialIcon = initialData?.icon || 'FaRocket';\r\n const isInitialCustom = initialIcon.startsWith('/') || initialIcon.startsWith('http');\r\n\r\n const [iconName, setIconName] = useState(isInitialCustom ? 'FaRocket' : initialIcon);\r\n const [customIconUrl, setCustomIconUrl] = useState(isInitialCustom ? initialIcon : '');\r\n const [iconMode, setIconMode] = useState<IconMode>(isInitialCustom ? 'custom' : 'picker');\r\n\r\n const [color, setColor] = useState(initialData?.color || '#4285F4');\r\n const [description, setDescription] = useState(initialData?.description || '');\r\n const [errors, setErrors] = useState<Record<string, string>>({});\r\n\r\n const SelectedIcon = iconMap[iconName] || iconMap['FaRocket'];\r\n\r\n const validate = (): boolean => {\r\n const newErrors: Record<string, string> = {};\r\n\r\n if (!name.trim()) {\r\n newErrors.name = 'Name is required';\r\n }\r\n\r\n if (!url.trim()) {\r\n newErrors.url = 'URL is required';\r\n } else if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('/')) {\r\n newErrors.url = 'URL must start with http://, https://, or /';\r\n }\r\n\r\n if (iconMode === 'custom' && customIconUrl.trim()) {\r\n if (\r\n !customIconUrl.startsWith('http://') &&\r\n !customIconUrl.startsWith('https://') &&\r\n !customIconUrl.startsWith('/')\r\n ) {\r\n newErrors.customIconUrl = 'Icon URL must start with http://, https://, or /';\r\n }\r\n }\r\n\r\n setErrors(newErrors);\r\n return Object.keys(newErrors).length === 0;\r\n };\r\n\r\n const handleSubmit = (e: React.FormEvent) => {\r\n e.preventDefault();\r\n\r\n if (!validate()) return;\r\n\r\n const finalIcon =\r\n iconMode === 'custom' && customIconUrl.trim() ? customIconUrl.trim() : iconName;\r\n\r\n onSubmit({\r\n name: name.trim(),\r\n url: url.trim(),\r\n icon: finalIcon,\r\n color,\r\n description: description.trim() || undefined,\r\n });\r\n };\r\n\r\n return (\r\n <form onSubmit={handleSubmit} className=\"app-launcher-form\">\r\n {/* Preview */}\r\n <div className=\"app-launcher-form__preview\">\r\n <div className=\"app-launcher-form__preview-icon\" style={{ backgroundColor: color + '20' }}>\r\n {iconMode === 'custom' && customIconUrl ? (\r\n customIconUrl.endsWith('.svg') ? (\r\n <div\r\n className=\"app-launcher-form__preview-svg\"\r\n style={{\r\n maskImage: `url(${customIconUrl})`,\r\n WebkitMaskImage: `url(${customIconUrl})`,\r\n backgroundColor: color === 'transparent' ? '#5f6368' : color,\r\n }}\r\n />\r\n ) : (\r\n <img\r\n src={customIconUrl}\r\n alt=\"Icon preview\"\r\n className=\"app-launcher-form__preview-img\"\r\n />\r\n )\r\n ) : (\r\n <SelectedIcon style={{ color, fontSize: 32 }} />\r\n )}\r\n </div>\r\n <span className=\"app-launcher-form__preview-name\">{name || 'App Name'}</span>\r\n </div>\r\n\r\n {/* Name Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-name\" className=\"app-launcher-form__label\">\r\n Name *\r\n </label>\r\n <input\r\n id=\"app-name\"\r\n type=\"text\"\r\n value={name}\r\n onChange={(e) => setName(e.target.value)}\r\n placeholder=\"My App\"\r\n className={`app-launcher-form__input ${errors.name ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.name && <span className=\"app-launcher-form__error\">{errors.name}</span>}\r\n </div>\r\n\r\n {/* URL Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-url\" className=\"app-launcher-form__label\">\r\n URL *\r\n </label>\r\n <input\r\n id=\"app-url\"\r\n type=\"text\"\r\n value={url}\r\n onChange={(e) => setUrl(e.target.value)}\r\n placeholder=\"https://myapp.com or /dashboard\"\r\n className={`app-launcher-form__input ${errors.url ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.url && <span className=\"app-launcher-form__error\">{errors.url}</span>}\r\n </div>\r\n\r\n {/* Description Input */}\r\n <div className=\"app-launcher-form__field\">\r\n <label htmlFor=\"app-description\" className=\"app-launcher-form__label\">\r\n Description\r\n </label>\r\n <input\r\n id=\"app-description\"\r\n type=\"text\"\r\n value={description}\r\n onChange={(e) => setDescription(e.target.value)}\r\n placeholder=\"Optional description (shown on hover)\"\r\n className=\"app-launcher-form__input\"\r\n />\r\n </div>\r\n\r\n {/* Color Picker */}\r\n <div className=\"app-launcher-form__field\">\r\n <label className=\"app-launcher-form__label\">Color</label>\r\n <div className=\"app-launcher-form__color-picker\">\r\n {presetColors.map((presetColor) => (\r\n <button\r\n key={presetColor}\r\n type=\"button\"\r\n className={`app-launcher-form__color-button ${\r\n color === presetColor ? 'app-launcher-form__color-button--selected' : ''\r\n }`}\r\n style={{ backgroundColor: presetColor }}\r\n onClick={() => setColor(presetColor)}\r\n title={presetColor}\r\n />\r\n ))}\r\n <input\r\n type=\"color\"\r\n value={color}\r\n onChange={(e) => setColor(e.target.value)}\r\n className=\"app-launcher-form__color-input\"\r\n title=\"Custom color\"\r\n />\r\n </div>\r\n </div>\r\n\r\n {/* Icon Selection */}\r\n <div className=\"app-launcher-form__field\">\r\n <label className=\"app-launcher-form__label\">Icon</label>\r\n\r\n {/* Icon Mode Toggle */}\r\n <div className=\"app-launcher-form__icon-mode\">\r\n <button\r\n type=\"button\"\r\n className={`app-launcher-form__mode-button ${iconMode === 'picker' ? 'app-launcher-form__mode-button--active' : ''}`}\r\n onClick={() => setIconMode('picker')}\r\n >\r\n Icon Picker\r\n </button>\r\n <button\r\n type=\"button\"\r\n className={`app-launcher-form__mode-button ${iconMode === 'custom' ? 'app-launcher-form__mode-button--active' : ''}`}\r\n onClick={() => setIconMode('custom')}\r\n >\r\n Custom URL\r\n </button>\r\n </div>\r\n\r\n {iconMode === 'picker' ? (\r\n <IconPicker selectedIcon={iconName} onSelect={setIconName} />\r\n ) : (\r\n <div className=\"app-launcher-form__custom-icon\">\r\n <input\r\n id=\"app-custom-icon\"\r\n type=\"text\"\r\n value={customIconUrl}\r\n onChange={(e) => setCustomIconUrl(e.target.value)}\r\n placeholder=\"/icons/my-icon.svg or https://cdn.example.com/icon.svg\"\r\n className={`app-launcher-form__input ${errors.customIconUrl ? 'app-launcher-form__input--error' : ''}`}\r\n />\r\n {errors.customIconUrl && (\r\n <span className=\"app-launcher-form__error\">{errors.customIconUrl}</span>\r\n )}\r\n <p className=\"app-launcher-form__hint\">\r\n Enter a path from your public folder (e.g., /icons/logo.svg) or a full URL\r\n </p>\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* Actions */}\r\n <div className=\"app-launcher-form__actions\">\r\n <button\r\n type=\"button\"\r\n onClick={onCancel}\r\n className=\"app-launcher-form__button app-launcher-form__button--cancel\"\r\n >\r\n Cancel\r\n </button>\r\n <button\r\n type=\"submit\"\r\n className=\"app-launcher-form__button app-launcher-form__button--submit\"\r\n >\r\n {submitLabel}\r\n </button>\r\n </div>\r\n </form>\r\n );\r\n}\r\n","import React, { useState } from 'react';\r\nimport { iconMap } from './icons';\r\n\r\ninterface IconPickerProps {\r\n selectedIcon: string;\r\n onSelect: (iconName: string) => void;\r\n}\r\n\r\n// Group icons by category/type based on their names or define categories manually\r\n// For simplicity in this package version, we'll list all available icons\r\nconst allIcons = Object.keys(iconMap);\r\n\r\nexport function IconPicker({ selectedIcon, onSelect }: IconPickerProps) {\r\n const [searchTerm, setSearchTerm] = useState('');\r\n\r\n const filteredIcons = allIcons.filter((name) =>\r\n name.toLowerCase().includes(searchTerm.toLowerCase())\r\n );\r\n\r\n return (\r\n <div className=\"app-launcher-icon-picker\">\r\n <input\r\n type=\"text\"\r\n placeholder=\"Search icons...\"\r\n value={searchTerm}\r\n onChange={(e) => setSearchTerm(e.target.value)}\r\n className=\"app-launcher-icon-picker__search\"\r\n />\r\n\r\n <div className=\"app-launcher-icon-picker__grid\">\r\n {filteredIcons.map((iconName) => {\r\n const Icon = iconMap[iconName];\r\n const isSelected = selectedIcon === iconName;\r\n\r\n return (\r\n <button\r\n key={iconName}\r\n className={`app-launcher-icon-picker__button ${\r\n isSelected ? 'app-launcher-icon-picker__button--selected' : ''\r\n }`}\r\n onClick={() => onSelect(iconName)}\r\n title={iconName}\r\n type=\"button\"\r\n >\r\n <Icon className=\"app-launcher-icon-picker__icon\" />\r\n </button>\r\n );\r\n })}\r\n </div>\r\n\r\n {filteredIcons.length === 0 && (\r\n <div className=\"app-launcher-icon-picker__empty\">No icons found</div>\r\n )}\r\n </div>\r\n );\r\n}\r\n"],"mappings":"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","names":["import_io5","import_react","import_fa","import_react","import_react","import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]}
package/dist/index.mjs CHANGED
@@ -259,8 +259,431 @@ function AppLauncher({
259
259
  ] })
260
260
  ] });
261
261
  }
262
+
263
+ // src/AppSettings.tsx
264
+ import { useState as useState4 } from "react";
265
+ import { FaTimes, FaPlus, FaEdit, FaTrash, FaDownload } from "react-icons/fa";
266
+
267
+ // src/AddAppForm.tsx
268
+ import { useState as useState3 } from "react";
269
+
270
+ // src/IconPicker.tsx
271
+ import { useState as useState2 } from "react";
272
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
273
+ var allIcons = Object.keys(iconMap);
274
+ function IconPicker({ selectedIcon, onSelect }) {
275
+ const [searchTerm, setSearchTerm] = useState2("");
276
+ const filteredIcons = allIcons.filter(
277
+ (name) => name.toLowerCase().includes(searchTerm.toLowerCase())
278
+ );
279
+ return /* @__PURE__ */ jsxs2("div", { className: "app-launcher-icon-picker", children: [
280
+ /* @__PURE__ */ jsx2(
281
+ "input",
282
+ {
283
+ type: "text",
284
+ placeholder: "Search icons...",
285
+ value: searchTerm,
286
+ onChange: (e) => setSearchTerm(e.target.value),
287
+ className: "app-launcher-icon-picker__search"
288
+ }
289
+ ),
290
+ /* @__PURE__ */ jsx2("div", { className: "app-launcher-icon-picker__grid", children: filteredIcons.map((iconName) => {
291
+ const Icon = iconMap[iconName];
292
+ const isSelected = selectedIcon === iconName;
293
+ return /* @__PURE__ */ jsx2(
294
+ "button",
295
+ {
296
+ className: `app-launcher-icon-picker__button ${isSelected ? "app-launcher-icon-picker__button--selected" : ""}`,
297
+ onClick: () => onSelect(iconName),
298
+ title: iconName,
299
+ type: "button",
300
+ children: /* @__PURE__ */ jsx2(Icon, { className: "app-launcher-icon-picker__icon" })
301
+ },
302
+ iconName
303
+ );
304
+ }) }),
305
+ filteredIcons.length === 0 && /* @__PURE__ */ jsx2("div", { className: "app-launcher-icon-picker__empty", children: "No icons found" })
306
+ ] });
307
+ }
308
+
309
+ // src/AddAppForm.tsx
310
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
311
+ var presetColors = [
312
+ "#4285F4",
313
+ // Blue
314
+ "#EA4335",
315
+ // Red
316
+ "#FBBC04",
317
+ // Yellow
318
+ "#34A853",
319
+ // Green
320
+ "#FF6D01",
321
+ // Orange
322
+ "#46BDC6",
323
+ // Teal
324
+ "#7B1FA2",
325
+ // Purple
326
+ "#E91E63",
327
+ // Pink
328
+ "#607D8B",
329
+ // Gray
330
+ "#795548"
331
+ // Brown
332
+ ];
333
+ function AddAppForm({
334
+ initialData,
335
+ onSubmit,
336
+ onCancel,
337
+ submitLabel = "Add App"
338
+ }) {
339
+ const [name, setName] = useState3(initialData?.name || "");
340
+ const [url, setUrl] = useState3(initialData?.url || "");
341
+ const initialIcon = initialData?.icon || "FaRocket";
342
+ const isInitialCustom = initialIcon.startsWith("/") || initialIcon.startsWith("http");
343
+ const [iconName, setIconName] = useState3(isInitialCustom ? "FaRocket" : initialIcon);
344
+ const [customIconUrl, setCustomIconUrl] = useState3(isInitialCustom ? initialIcon : "");
345
+ const [iconMode, setIconMode] = useState3(isInitialCustom ? "custom" : "picker");
346
+ const [color, setColor] = useState3(initialData?.color || "#4285F4");
347
+ const [description, setDescription] = useState3(initialData?.description || "");
348
+ const [errors, setErrors] = useState3({});
349
+ const SelectedIcon = iconMap[iconName] || iconMap["FaRocket"];
350
+ const validate = () => {
351
+ const newErrors = {};
352
+ if (!name.trim()) {
353
+ newErrors.name = "Name is required";
354
+ }
355
+ if (!url.trim()) {
356
+ newErrors.url = "URL is required";
357
+ } else if (!url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("/")) {
358
+ newErrors.url = "URL must start with http://, https://, or /";
359
+ }
360
+ if (iconMode === "custom" && customIconUrl.trim()) {
361
+ if (!customIconUrl.startsWith("http://") && !customIconUrl.startsWith("https://") && !customIconUrl.startsWith("/")) {
362
+ newErrors.customIconUrl = "Icon URL must start with http://, https://, or /";
363
+ }
364
+ }
365
+ setErrors(newErrors);
366
+ return Object.keys(newErrors).length === 0;
367
+ };
368
+ const handleSubmit = (e) => {
369
+ e.preventDefault();
370
+ if (!validate()) return;
371
+ const finalIcon = iconMode === "custom" && customIconUrl.trim() ? customIconUrl.trim() : iconName;
372
+ onSubmit({
373
+ name: name.trim(),
374
+ url: url.trim(),
375
+ icon: finalIcon,
376
+ color,
377
+ description: description.trim() || void 0
378
+ });
379
+ };
380
+ return /* @__PURE__ */ jsxs3("form", { onSubmit: handleSubmit, className: "app-launcher-form", children: [
381
+ /* @__PURE__ */ jsxs3("div", { className: "app-launcher-form__preview", children: [
382
+ /* @__PURE__ */ jsx3("div", { className: "app-launcher-form__preview-icon", style: { backgroundColor: color + "20" }, children: iconMode === "custom" && customIconUrl ? customIconUrl.endsWith(".svg") ? /* @__PURE__ */ jsx3(
383
+ "div",
384
+ {
385
+ className: "app-launcher-form__preview-svg",
386
+ style: {
387
+ maskImage: `url(${customIconUrl})`,
388
+ WebkitMaskImage: `url(${customIconUrl})`,
389
+ backgroundColor: color === "transparent" ? "#5f6368" : color
390
+ }
391
+ }
392
+ ) : /* @__PURE__ */ jsx3(
393
+ "img",
394
+ {
395
+ src: customIconUrl,
396
+ alt: "Icon preview",
397
+ className: "app-launcher-form__preview-img"
398
+ }
399
+ ) : /* @__PURE__ */ jsx3(SelectedIcon, { style: { color, fontSize: 32 } }) }),
400
+ /* @__PURE__ */ jsx3("span", { className: "app-launcher-form__preview-name", children: name || "App Name" })
401
+ ] }),
402
+ /* @__PURE__ */ jsxs3("div", { className: "app-launcher-form__field", children: [
403
+ /* @__PURE__ */ jsx3("label", { htmlFor: "app-name", className: "app-launcher-form__label", children: "Name *" }),
404
+ /* @__PURE__ */ jsx3(
405
+ "input",
406
+ {
407
+ id: "app-name",
408
+ type: "text",
409
+ value: name,
410
+ onChange: (e) => setName(e.target.value),
411
+ placeholder: "My App",
412
+ className: `app-launcher-form__input ${errors.name ? "app-launcher-form__input--error" : ""}`
413
+ }
414
+ ),
415
+ errors.name && /* @__PURE__ */ jsx3("span", { className: "app-launcher-form__error", children: errors.name })
416
+ ] }),
417
+ /* @__PURE__ */ jsxs3("div", { className: "app-launcher-form__field", children: [
418
+ /* @__PURE__ */ jsx3("label", { htmlFor: "app-url", className: "app-launcher-form__label", children: "URL *" }),
419
+ /* @__PURE__ */ jsx3(
420
+ "input",
421
+ {
422
+ id: "app-url",
423
+ type: "text",
424
+ value: url,
425
+ onChange: (e) => setUrl(e.target.value),
426
+ placeholder: "https://myapp.com or /dashboard",
427
+ className: `app-launcher-form__input ${errors.url ? "app-launcher-form__input--error" : ""}`
428
+ }
429
+ ),
430
+ errors.url && /* @__PURE__ */ jsx3("span", { className: "app-launcher-form__error", children: errors.url })
431
+ ] }),
432
+ /* @__PURE__ */ jsxs3("div", { className: "app-launcher-form__field", children: [
433
+ /* @__PURE__ */ jsx3("label", { htmlFor: "app-description", className: "app-launcher-form__label", children: "Description" }),
434
+ /* @__PURE__ */ jsx3(
435
+ "input",
436
+ {
437
+ id: "app-description",
438
+ type: "text",
439
+ value: description,
440
+ onChange: (e) => setDescription(e.target.value),
441
+ placeholder: "Optional description (shown on hover)",
442
+ className: "app-launcher-form__input"
443
+ }
444
+ )
445
+ ] }),
446
+ /* @__PURE__ */ jsxs3("div", { className: "app-launcher-form__field", children: [
447
+ /* @__PURE__ */ jsx3("label", { className: "app-launcher-form__label", children: "Color" }),
448
+ /* @__PURE__ */ jsxs3("div", { className: "app-launcher-form__color-picker", children: [
449
+ presetColors.map((presetColor) => /* @__PURE__ */ jsx3(
450
+ "button",
451
+ {
452
+ type: "button",
453
+ className: `app-launcher-form__color-button ${color === presetColor ? "app-launcher-form__color-button--selected" : ""}`,
454
+ style: { backgroundColor: presetColor },
455
+ onClick: () => setColor(presetColor),
456
+ title: presetColor
457
+ },
458
+ presetColor
459
+ )),
460
+ /* @__PURE__ */ jsx3(
461
+ "input",
462
+ {
463
+ type: "color",
464
+ value: color,
465
+ onChange: (e) => setColor(e.target.value),
466
+ className: "app-launcher-form__color-input",
467
+ title: "Custom color"
468
+ }
469
+ )
470
+ ] })
471
+ ] }),
472
+ /* @__PURE__ */ jsxs3("div", { className: "app-launcher-form__field", children: [
473
+ /* @__PURE__ */ jsx3("label", { className: "app-launcher-form__label", children: "Icon" }),
474
+ /* @__PURE__ */ jsxs3("div", { className: "app-launcher-form__icon-mode", children: [
475
+ /* @__PURE__ */ jsx3(
476
+ "button",
477
+ {
478
+ type: "button",
479
+ className: `app-launcher-form__mode-button ${iconMode === "picker" ? "app-launcher-form__mode-button--active" : ""}`,
480
+ onClick: () => setIconMode("picker"),
481
+ children: "Icon Picker"
482
+ }
483
+ ),
484
+ /* @__PURE__ */ jsx3(
485
+ "button",
486
+ {
487
+ type: "button",
488
+ className: `app-launcher-form__mode-button ${iconMode === "custom" ? "app-launcher-form__mode-button--active" : ""}`,
489
+ onClick: () => setIconMode("custom"),
490
+ children: "Custom URL"
491
+ }
492
+ )
493
+ ] }),
494
+ iconMode === "picker" ? /* @__PURE__ */ jsx3(IconPicker, { selectedIcon: iconName, onSelect: setIconName }) : /* @__PURE__ */ jsxs3("div", { className: "app-launcher-form__custom-icon", children: [
495
+ /* @__PURE__ */ jsx3(
496
+ "input",
497
+ {
498
+ id: "app-custom-icon",
499
+ type: "text",
500
+ value: customIconUrl,
501
+ onChange: (e) => setCustomIconUrl(e.target.value),
502
+ placeholder: "/icons/my-icon.svg or https://cdn.example.com/icon.svg",
503
+ className: `app-launcher-form__input ${errors.customIconUrl ? "app-launcher-form__input--error" : ""}`
504
+ }
505
+ ),
506
+ errors.customIconUrl && /* @__PURE__ */ jsx3("span", { className: "app-launcher-form__error", children: errors.customIconUrl }),
507
+ /* @__PURE__ */ jsx3("p", { className: "app-launcher-form__hint", children: "Enter a path from your public folder (e.g., /icons/logo.svg) or a full URL" })
508
+ ] })
509
+ ] }),
510
+ /* @__PURE__ */ jsxs3("div", { className: "app-launcher-form__actions", children: [
511
+ /* @__PURE__ */ jsx3(
512
+ "button",
513
+ {
514
+ type: "button",
515
+ onClick: onCancel,
516
+ className: "app-launcher-form__button app-launcher-form__button--cancel",
517
+ children: "Cancel"
518
+ }
519
+ ),
520
+ /* @__PURE__ */ jsx3(
521
+ "button",
522
+ {
523
+ type: "submit",
524
+ className: "app-launcher-form__button app-launcher-form__button--submit",
525
+ children: submitLabel
526
+ }
527
+ )
528
+ ] })
529
+ ] });
530
+ }
531
+
532
+ // src/AppSettings.tsx
533
+ import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
534
+ function AppSettings({
535
+ isOpen,
536
+ onClose,
537
+ apps,
538
+ defaultApps = [],
539
+ onAdd,
540
+ onUpdate,
541
+ onDelete
542
+ }) {
543
+ const [showAddForm, setShowAddForm] = useState4(false);
544
+ const [editingApp, setEditingApp] = useState4(null);
545
+ if (!isOpen) return null;
546
+ const handleAddSubmit = (data) => {
547
+ onAdd(data);
548
+ setShowAddForm(false);
549
+ };
550
+ const handleEditSubmit = (data) => {
551
+ if (editingApp) {
552
+ onUpdate(editingApp.id, data);
553
+ setEditingApp(null);
554
+ }
555
+ };
556
+ const handleDelete = (id) => {
557
+ if (confirm("Are you sure you want to delete this app?")) {
558
+ onDelete(id);
559
+ }
560
+ };
561
+ const handleExportConfig = () => {
562
+ const exportedApps = [
563
+ // User apps first (they appear at top)
564
+ ...apps,
565
+ // Then default apps
566
+ ...defaultApps.filter((da) => !apps.some((ua) => ua.id === da.id))
567
+ // Avoid duplicates if any
568
+ ];
569
+ const config = {
570
+ version: "1.0",
571
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
572
+ apps: exportedApps
573
+ };
574
+ const blob = new Blob([JSON.stringify(config, null, 2)], { type: "application/json" });
575
+ const url = URL.createObjectURL(blob);
576
+ const a = document.createElement("a");
577
+ a.href = url;
578
+ a.download = "app-launcher-config.json";
579
+ document.body.appendChild(a);
580
+ a.click();
581
+ document.body.removeChild(a);
582
+ URL.revokeObjectURL(url);
583
+ };
584
+ return /* @__PURE__ */ jsx4("div", { className: "app-settings__overlay", children: /* @__PURE__ */ jsxs4("div", { className: "app-settings__modal", onClick: (e) => e.stopPropagation(), children: [
585
+ /* @__PURE__ */ jsxs4("div", { className: "app-settings__header", children: [
586
+ /* @__PURE__ */ jsx4("h2", { className: "app-settings__title", children: showAddForm ? "Add New App" : editingApp ? "Edit App" : "Settings" }),
587
+ /* @__PURE__ */ jsx4("button", { className: "app-settings__close-button", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx4(FaTimes, {}) })
588
+ ] }),
589
+ /* @__PURE__ */ jsx4("div", { className: "app-settings__content", children: showAddForm ? /* @__PURE__ */ jsx4(
590
+ AddAppForm,
591
+ {
592
+ onSubmit: handleAddSubmit,
593
+ onCancel: () => setShowAddForm(false),
594
+ submitLabel: "Add App"
595
+ }
596
+ ) : editingApp ? /* @__PURE__ */ jsx4(
597
+ AddAppForm,
598
+ {
599
+ initialData: editingApp,
600
+ onSubmit: handleEditSubmit,
601
+ onCancel: () => setEditingApp(null),
602
+ submitLabel: "Save Changes"
603
+ }
604
+ ) : /* @__PURE__ */ jsxs4(Fragment, { children: [
605
+ /* @__PURE__ */ jsxs4("button", { className: "app-settings__add-button", onClick: () => setShowAddForm(true), children: [
606
+ /* @__PURE__ */ jsx4(FaPlus, { className: "app-settings__add-icon" }),
607
+ "Add New App"
608
+ ] }),
609
+ /* @__PURE__ */ jsxs4("div", { className: "app-settings__list", children: [
610
+ /* @__PURE__ */ jsx4("h3", { className: "app-settings__section-title", children: "My Custom Apps" }),
611
+ apps.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "app-settings__empty-message", children: 'No custom apps yet. Click "Add New App" to get started.' }) : /* @__PURE__ */ jsx4("div", { className: "app-settings__grid", children: apps.map((app) => {
612
+ const isCustom = app.icon.startsWith("/") || app.icon.startsWith("http");
613
+ const Icon = !isCustom ? iconMap[app.icon] || iconMap["FaRocket"] : null;
614
+ return /* @__PURE__ */ jsxs4("div", { className: "app-settings__card", children: [
615
+ /* @__PURE__ */ jsxs4("div", { className: "app-settings__card-info", children: [
616
+ /* @__PURE__ */ jsx4(
617
+ "div",
618
+ {
619
+ className: "app-settings__card-icon",
620
+ style: { backgroundColor: app.color + "20" },
621
+ children: isCustom ? app.icon.endsWith(".svg") ? /* @__PURE__ */ jsx4(
622
+ "div",
623
+ {
624
+ style: {
625
+ width: 20,
626
+ height: 20,
627
+ maskImage: `url(${app.icon})`,
628
+ WebkitMaskImage: `url(${app.icon})`,
629
+ maskSize: "contain",
630
+ maskRepeat: "no-repeat",
631
+ maskPosition: "center",
632
+ WebkitMaskSize: "contain",
633
+ WebkitMaskRepeat: "no-repeat",
634
+ WebkitMaskPosition: "center",
635
+ backgroundColor: app.color === "transparent" ? "#5f6368" : app.color
636
+ }
637
+ }
638
+ ) : /* @__PURE__ */ jsx4(
639
+ "img",
640
+ {
641
+ src: app.icon,
642
+ alt: app.name,
643
+ style: { width: 20, height: 20, objectFit: "contain" }
644
+ }
645
+ ) : Icon && /* @__PURE__ */ jsx4(Icon, { style: { color: app.color } })
646
+ }
647
+ ),
648
+ /* @__PURE__ */ jsxs4("div", { className: "app-settings__card-details", children: [
649
+ /* @__PURE__ */ jsx4("span", { className: "app-settings__card-name", children: app.name }),
650
+ /* @__PURE__ */ jsx4("span", { className: "app-settings__card-url", children: app.url })
651
+ ] })
652
+ ] }),
653
+ /* @__PURE__ */ jsxs4("div", { className: "app-settings__card-actions", children: [
654
+ /* @__PURE__ */ jsx4(
655
+ "button",
656
+ {
657
+ className: "app-settings__action-button",
658
+ onClick: () => setEditingApp(app),
659
+ title: "Edit",
660
+ children: /* @__PURE__ */ jsx4(FaEdit, {})
661
+ }
662
+ ),
663
+ /* @__PURE__ */ jsx4(
664
+ "button",
665
+ {
666
+ className: "app-settings__action-button app-settings__action-button--delete",
667
+ onClick: () => handleDelete(app.id),
668
+ title: "Delete",
669
+ children: /* @__PURE__ */ jsx4(FaTrash, {})
670
+ }
671
+ )
672
+ ] })
673
+ ] }, app.id);
674
+ }) })
675
+ ] }),
676
+ /* @__PURE__ */ jsxs4("button", { className: "app-settings__export-button", onClick: handleExportConfig, children: [
677
+ /* @__PURE__ */ jsx4(FaDownload, { className: "app-settings__export-icon" }),
678
+ "Export Configuration"
679
+ ] }),
680
+ /* @__PURE__ */ jsx4("div", { className: "app-settings__info", children: /* @__PURE__ */ jsx4("p", { children: 'Custom apps are saved in your browser. Use "Export Configuration" to share with other apps.' }) })
681
+ ] }) })
682
+ ] }) });
683
+ }
262
684
  export {
263
685
  AppLauncher,
686
+ AppSettings,
264
687
  AppLauncher as default,
265
688
  getIcon,
266
689
  iconMap