mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-01 16:12:02 +00:00
430 lines
19 KiB
TypeScript
430 lines
19 KiB
TypeScript
'use client';
|
|
|
|
// Icons chosen by name at runtime: sidebar apps, message-list tabs (a plugin
|
|
// API), and the admin's default apps. Names are stored as `tabler:<name>`.
|
|
// Names saved before the switch to Tabler are Lucide names (`"Globe"`); they
|
|
// are translated on read, so old settings and existing plugins keep working.
|
|
//
|
|
// The popular icons ship in the main bundle. Any other name loads the full
|
|
// Tabler set once, lazily, the first time it is rendered or the picker's
|
|
// "show all" list opens.
|
|
|
|
import { forwardRef, useEffect, useState } from 'react';
|
|
import type { TablerIcon } from '@tabler/icons-react';
|
|
import {
|
|
IconAddressBook,
|
|
IconAperture,
|
|
IconApple,
|
|
IconArchive,
|
|
IconAtom,
|
|
IconAward,
|
|
IconBeer,
|
|
IconBell,
|
|
IconBike,
|
|
IconBolt,
|
|
IconBook,
|
|
IconBookmark,
|
|
IconBox,
|
|
IconBraces,
|
|
IconBrain,
|
|
IconBrandTrello,
|
|
IconBrandYoutube,
|
|
IconBrush,
|
|
IconBug,
|
|
IconBuilding,
|
|
IconBuildingBank,
|
|
IconBuildingStore,
|
|
IconBuildingWarehouse,
|
|
IconBulb,
|
|
IconBus,
|
|
IconCake,
|
|
IconCalendar,
|
|
IconCamera,
|
|
IconCar,
|
|
IconChartBar,
|
|
IconChartColumn,
|
|
IconChartLine,
|
|
IconChartPie,
|
|
IconChecklist,
|
|
IconChristmasTree,
|
|
IconCircleDot,
|
|
IconClipboardList,
|
|
IconClock,
|
|
IconCloud,
|
|
IconCloudDownload,
|
|
IconCloudUpload,
|
|
IconCode,
|
|
IconCoffee,
|
|
IconCompass,
|
|
IconCpu,
|
|
IconCreditCard,
|
|
IconCrosshair,
|
|
IconCrown,
|
|
IconCurrencyDollar,
|
|
IconCurrencyEuro,
|
|
IconDatabase,
|
|
IconDeviceComputerCamera,
|
|
IconDeviceDesktop,
|
|
IconDeviceGamepad2,
|
|
IconDeviceLaptop,
|
|
IconDeviceMobile,
|
|
IconDeviceTablet,
|
|
IconDeviceTv,
|
|
IconDice5,
|
|
IconDna,
|
|
IconEdit,
|
|
IconExternalLink,
|
|
IconEye,
|
|
IconFileSpreadsheet,
|
|
IconFileText,
|
|
IconFingerprint,
|
|
IconFlag,
|
|
IconFlame,
|
|
IconFlask2,
|
|
IconFlower,
|
|
IconFolder,
|
|
IconGift,
|
|
IconGlass,
|
|
IconHammer,
|
|
IconHeadphones,
|
|
IconHeart,
|
|
IconHeartbeat,
|
|
IconHome,
|
|
IconInbox,
|
|
IconKey,
|
|
IconLayoutGrid,
|
|
IconLayoutKanban,
|
|
IconLeaf,
|
|
IconLink,
|
|
IconLock,
|
|
IconLockOpen,
|
|
IconMagnet,
|
|
IconMail,
|
|
IconMan,
|
|
IconMap,
|
|
IconMapPin,
|
|
IconMessage,
|
|
IconMessageCircle,
|
|
IconMessages,
|
|
IconMicrophone,
|
|
IconMicrophone2,
|
|
IconMicroscope,
|
|
IconMoon,
|
|
IconMountain,
|
|
IconMovie,
|
|
IconMusic,
|
|
IconNavigation,
|
|
IconNotebook,
|
|
IconPackage,
|
|
IconPalette,
|
|
IconPencil,
|
|
IconPhone,
|
|
IconPhoto,
|
|
IconPigMoney,
|
|
IconPill,
|
|
IconPizza,
|
|
IconPlane,
|
|
IconPlayerPlay,
|
|
IconPuzzle,
|
|
IconQrcode,
|
|
IconRadio,
|
|
IconReceipt,
|
|
IconRipple,
|
|
IconRobot,
|
|
IconRocket,
|
|
IconRss,
|
|
IconRuler,
|
|
IconScan,
|
|
IconScissors,
|
|
IconServer,
|
|
IconServer2,
|
|
IconShield,
|
|
IconShieldCheck,
|
|
IconShip,
|
|
IconSnowflake,
|
|
IconSoup,
|
|
IconSparkles,
|
|
IconSquareCheck,
|
|
IconStack,
|
|
IconStar,
|
|
IconStethoscope,
|
|
IconStopwatch,
|
|
IconSun,
|
|
IconSunWind,
|
|
IconTag,
|
|
IconTarget,
|
|
IconTerminal,
|
|
IconThermometer,
|
|
IconThumbUp,
|
|
IconTool,
|
|
IconTrain,
|
|
IconTrendingUp,
|
|
IconTrophy,
|
|
IconUserCircle,
|
|
IconUserPlus,
|
|
IconUsers,
|
|
IconVaccine,
|
|
IconVideo,
|
|
IconWallet,
|
|
IconWand,
|
|
IconWifi,
|
|
IconWorld,
|
|
} from '@tabler/icons-react';
|
|
|
|
export const TABLER_ICON_PREFIX = 'tabler:';
|
|
|
|
/** Tabler names shown first in the icon picker. */
|
|
export const POPULAR_ICON_NAMES: readonly string[] = ["world","rss","radio","microphone-2","message-circle","message","messages","phone","video","device-computer-camera","headphones","microphone","file-text","file-spreadsheet","notebook","book","clipboard-list","checklist","square-check","layout-kanban","brand-trello","pencil","edit","code","terminal","braces","bug","database","server","cpu","server-2","device-desktop","device-laptop","device-mobile","device-tablet","wifi","cloud","cloud-download","cloud-upload","users","user-plus","user-circle","address-book","man","heart","thumb-up","star","award","trophy","crown","photo","camera","movie","music","player-play","device-tv","brand-youtube","palette","brush","map","map-pin","navigation","compass","home","building","building-bank","building-store","building-warehouse","currency-dollar","currency-euro","credit-card","wallet","receipt","pig-money","trending-up","chart-bar","chart-column","chart-line","chart-pie","shield","shield-check","lock","lock-open","key","fingerprint","eye","flask-2","atom","dna","microscope","stethoscope","heartbeat","pill","vaccine","thermometer","sun","moon","sun-wind","snowflake","bolt","flame","christmas-tree","flower","leaf","mountain","ripple","tool","hammer","scissors","ruler","magnet","package","gift","box","archive","car","bike","bus","train","plane","ship","rocket","coffee","glass","beer","pizza","apple","cake","soup","device-gamepad-2","dice-5","puzzle","sparkles","wand","robot","brain","bulb","bookmark","flag","bell","clock","stopwatch","link","external-link","qrcode","scan","layout-grid","stack","aperture","circle-dot","target","crosshair"];
|
|
|
|
const CURATED: Record<string, TablerIcon> = {
|
|
'address-book': IconAddressBook,
|
|
'aperture': IconAperture,
|
|
'apple': IconApple,
|
|
'archive': IconArchive,
|
|
'atom': IconAtom,
|
|
'award': IconAward,
|
|
'beer': IconBeer,
|
|
'bell': IconBell,
|
|
'bike': IconBike,
|
|
'bolt': IconBolt,
|
|
'book': IconBook,
|
|
'bookmark': IconBookmark,
|
|
'box': IconBox,
|
|
'braces': IconBraces,
|
|
'brain': IconBrain,
|
|
'brand-trello': IconBrandTrello,
|
|
'brand-youtube': IconBrandYoutube,
|
|
'brush': IconBrush,
|
|
'bug': IconBug,
|
|
'building': IconBuilding,
|
|
'building-bank': IconBuildingBank,
|
|
'building-store': IconBuildingStore,
|
|
'building-warehouse': IconBuildingWarehouse,
|
|
'bulb': IconBulb,
|
|
'bus': IconBus,
|
|
'cake': IconCake,
|
|
'calendar': IconCalendar,
|
|
'camera': IconCamera,
|
|
'car': IconCar,
|
|
'chart-bar': IconChartBar,
|
|
'chart-column': IconChartColumn,
|
|
'chart-line': IconChartLine,
|
|
'chart-pie': IconChartPie,
|
|
'checklist': IconChecklist,
|
|
'christmas-tree': IconChristmasTree,
|
|
'circle-dot': IconCircleDot,
|
|
'clipboard-list': IconClipboardList,
|
|
'clock': IconClock,
|
|
'cloud': IconCloud,
|
|
'cloud-download': IconCloudDownload,
|
|
'cloud-upload': IconCloudUpload,
|
|
'code': IconCode,
|
|
'coffee': IconCoffee,
|
|
'compass': IconCompass,
|
|
'cpu': IconCpu,
|
|
'credit-card': IconCreditCard,
|
|
'crosshair': IconCrosshair,
|
|
'crown': IconCrown,
|
|
'currency-dollar': IconCurrencyDollar,
|
|
'currency-euro': IconCurrencyEuro,
|
|
'database': IconDatabase,
|
|
'device-computer-camera': IconDeviceComputerCamera,
|
|
'device-desktop': IconDeviceDesktop,
|
|
'device-gamepad-2': IconDeviceGamepad2,
|
|
'device-laptop': IconDeviceLaptop,
|
|
'device-mobile': IconDeviceMobile,
|
|
'device-tablet': IconDeviceTablet,
|
|
'device-tv': IconDeviceTv,
|
|
'dice-5': IconDice5,
|
|
'dna': IconDna,
|
|
'edit': IconEdit,
|
|
'external-link': IconExternalLink,
|
|
'eye': IconEye,
|
|
'file-spreadsheet': IconFileSpreadsheet,
|
|
'file-text': IconFileText,
|
|
'fingerprint': IconFingerprint,
|
|
'flag': IconFlag,
|
|
'flame': IconFlame,
|
|
'flask-2': IconFlask2,
|
|
'flower': IconFlower,
|
|
'folder': IconFolder,
|
|
'gift': IconGift,
|
|
'glass': IconGlass,
|
|
'hammer': IconHammer,
|
|
'headphones': IconHeadphones,
|
|
'heart': IconHeart,
|
|
'heartbeat': IconHeartbeat,
|
|
'home': IconHome,
|
|
'inbox': IconInbox,
|
|
'key': IconKey,
|
|
'layout-grid': IconLayoutGrid,
|
|
'layout-kanban': IconLayoutKanban,
|
|
'leaf': IconLeaf,
|
|
'link': IconLink,
|
|
'lock': IconLock,
|
|
'lock-open': IconLockOpen,
|
|
'magnet': IconMagnet,
|
|
'mail': IconMail,
|
|
'man': IconMan,
|
|
'map': IconMap,
|
|
'map-pin': IconMapPin,
|
|
'message': IconMessage,
|
|
'message-circle': IconMessageCircle,
|
|
'messages': IconMessages,
|
|
'microphone': IconMicrophone,
|
|
'microphone-2': IconMicrophone2,
|
|
'microscope': IconMicroscope,
|
|
'moon': IconMoon,
|
|
'mountain': IconMountain,
|
|
'movie': IconMovie,
|
|
'music': IconMusic,
|
|
'navigation': IconNavigation,
|
|
'notebook': IconNotebook,
|
|
'package': IconPackage,
|
|
'palette': IconPalette,
|
|
'pencil': IconPencil,
|
|
'phone': IconPhone,
|
|
'photo': IconPhoto,
|
|
'pig-money': IconPigMoney,
|
|
'pill': IconPill,
|
|
'pizza': IconPizza,
|
|
'plane': IconPlane,
|
|
'player-play': IconPlayerPlay,
|
|
'puzzle': IconPuzzle,
|
|
'qrcode': IconQrcode,
|
|
'radio': IconRadio,
|
|
'receipt': IconReceipt,
|
|
'ripple': IconRipple,
|
|
'robot': IconRobot,
|
|
'rocket': IconRocket,
|
|
'rss': IconRss,
|
|
'ruler': IconRuler,
|
|
'scan': IconScan,
|
|
'scissors': IconScissors,
|
|
'server': IconServer,
|
|
'server-2': IconServer2,
|
|
'shield': IconShield,
|
|
'shield-check': IconShieldCheck,
|
|
'ship': IconShip,
|
|
'snowflake': IconSnowflake,
|
|
'soup': IconSoup,
|
|
'sparkles': IconSparkles,
|
|
'square-check': IconSquareCheck,
|
|
'stack': IconStack,
|
|
'star': IconStar,
|
|
'stethoscope': IconStethoscope,
|
|
'stopwatch': IconStopwatch,
|
|
'sun': IconSun,
|
|
'sun-wind': IconSunWind,
|
|
'tag': IconTag,
|
|
'target': IconTarget,
|
|
'terminal': IconTerminal,
|
|
'thermometer': IconThermometer,
|
|
'thumb-up': IconThumbUp,
|
|
'tool': IconTool,
|
|
'train': IconTrain,
|
|
'trending-up': IconTrendingUp,
|
|
'trophy': IconTrophy,
|
|
'user-circle': IconUserCircle,
|
|
'user-plus': IconUserPlus,
|
|
'users': IconUsers,
|
|
'vaccine': IconVaccine,
|
|
'video': IconVideo,
|
|
'wallet': IconWallet,
|
|
'wand': IconWand,
|
|
'wifi': IconWifi,
|
|
'world': IconWorld,
|
|
};
|
|
|
|
/** Lucide names (pre-Tabler settings, plugin tab definitions) -> Tabler names. */
|
|
export const LEGACY_LUCIDE: Readonly<Record<string, string>> ={"Activity":"activity","AlertCircle":"alert-circle","AlertTriangle":"alert-triangle","AlignCenter":"align-center","AlignLeft":"align-left","AlignRight":"align-right","Aperture":"aperture","Apple":"apple","Archive":"archive","ArrowDown":"arrow-down","ArrowDownAZ":"sort-a-z","ArrowLeft":"arrow-left","ArrowLeftRight":"arrow-left-right","ArrowRight":"arrow-right","ArrowUp":"arrow-up","ArrowUpCircle":"circle-arrow-up","ArrowUpRight":"arrow-up-right","Atom":"atom","Award":"award","Ban":"ban","BarChart":"chart-bar","BarChart3":"chart-column","Baseline":"baseline","Beaker":"flask-2","Beer":"beer","Bell":"bell","BellOff":"bell-off","Bike":"bike","Bold":"bold","Book":"book-2","Bookmark":"bookmark","BookmarkPlus":"bookmark-plus","BookOpen":"book","BookPlus":"book-2","BookUser":"address-book","Bot":"robot","Box":"box","Braces":"braces","Brain":"brain","BrainCircuit":"brain","Briefcase":"briefcase","Brush":"brush","Bug":"bug","Building":"building","Building2":"building","Bus":"bus","Cake":"cake","Calendar":"calendar","CalendarArrowUp":"calendar-up","CalendarCheck":"calendar-check","CalendarClock":"calendar-clock","CalendarDays":"calendar-month","CalendarX":"calendar-x","Camera":"camera","Car":"car","Check":"check","CheckCheck":"checks","CheckCircle":"circle-check","CheckCircle2":"circle-check","CheckSquare":"square-check","ChevronDown":"chevron-down","ChevronLeft":"chevron-left","ChevronRight":"chevron-right","ChevronsLeft":"chevrons-left","ChevronsRight":"chevrons-right","ChevronUp":"chevron-up","Circle":"circle","CircleDot":"circle-dot","Clapperboard":"movie","Clipboard":"clipboard","ClipboardCopy":"clipboard-copy","ClipboardList":"clipboard-list","Clock":"clock","Clock3":"clock-hour-3","Cloud":"cloud","CloudDownload":"cloud-download","CloudSun":"sun-wind","CloudUpload":"cloud-upload","Code":"code","Code2":"code","Coffee":"coffee","Columns3":"columns-3","Compass":"compass","Contact":"address-book","CookingPot":"soup","Copy":"copy","CopyPlus":"copy-plus","CornerDownLeft":"corner-down-left","Cpu":"cpu","CreditCard":"credit-card","Crosshair":"crosshair","Crown":"crown","Database":"database","Dice5":"dice-5","Dna":"dna","DollarSign":"currency-dollar","Download":"download","Edit":"edit","EditIcon":"edit","Ellipsis":"dots","Eraser":"eraser","Euro":"currency-euro","ExternalLink":"external-link","Eye":"eye","EyeOff":"eye-off","File":"file","FileArchive":"file-zip","FileAudio":"file-music","FileCode":"file-code","FileImage":"photo","FilePlus":"file-plus","FileSpreadsheet":"file-spreadsheet","FileText":"file-text","FileVideo":"video","Film":"movie","Filter":"filter","Fingerprint":"fingerprint","Flag":"flag","Flame":"flame","FlaskConical":"flask","Flower":"flower","Folder":"folder","FolderInput":"folder-down","FolderOpen":"folder-open","FolderPlus":"folder-plus","FolderSync":"folders","FolderUp":"folder-up","FolderX":"folder-x","Forward":"arrow-forward-up","Gamepad2":"device-gamepad-2","Gift":"gift","Globe":"world","GripVertical":"grip-vertical","Hammer":"hammer","HardDrive":"server-2","Heading1":"h-1","Heading2":"h-2","Headphones":"headphones","Heart":"heart","HeartPulse":"heartbeat","HelpCircle":"help-circle","Highlighter":"highlight","Home":"home","Image":"photo","ImageIcon":"photo","Inbox":"inbox","Info":"info-circle","Italic":"italic","Kanban":"layout-kanban","Key":"key","Keyboard":"keyboard","KeyRound":"key","Landmark":"building-bank","Languages":"language","Laptop":"device-laptop","Layers":"stack","LayoutDashboard":"layout-dashboard","LayoutGrid":"layout-grid","LayoutList":"layout-list","Leaf":"leaf","Lightbulb":"bulb","LineChart":"chart-line","Link":"link","List":"list","ListOrdered":"list-numbers","ListTodo":"checklist","Loader2":"loader-2","Lock":"lock","LockKeyhole":"lock","LockOpen":"lock-open","LogIn":"login","LogOut":"logout","Magnet":"magnet","Mail":"mail","MailCheck":"mail-check","MailOpen":"mail-opened","Mails":"inbox","MailX":"mail-x","Map":"map","MapPin":"map-pin","Maximize2":"maximize","Menu":"menu-2","MessageCircle":"message-circle","MessageSquare":"message","MessagesSquare":"messages","Mic":"microphone","Microscope":"microscope","Minimize2":"minimize","Minus":"minus","Monitor":"device-desktop","Moon":"moon","MoreHorizontal":"dots","MoreVertical":"dots-vertical","Mountain":"mountain","Music":"music","Navigation":"navigation","Notebook":"notebook","NotebookPen":"notebook","Package":"package","PackageCheck":"package","Paintbrush":"brush","Palette":"palette","Palmtree":"beach","PalmtreeIcon":"beach","PanelLeftClose":"layout-sidebar-left-collapse","PanelRight":"layout-sidebar-right","PanelRightClose":"layout-sidebar-right-collapse","PanelRightOpen":"layout-sidebar-right-expand","Paperclip":"paperclip","Pencil":"pencil","PenLine":"pencil","PenSquare":"edit","PenTool":"vector","PersonStanding":"man","Phone":"phone","PieChart":"chart-pie","PiggyBank":"pig-money","Pill":"pill","Pin":"pinned","PinOff":"pinned-off","Pizza":"pizza","Plane":"plane","Play":"player-play","PlayCircle":"player-play","Plus":"plus","Podcast":"microphone-2","Power":"power","PowerOff":"power","Presentation":"presentation","Printer":"printer","Puzzle":"puzzle","QrCode":"qrcode","Quote":"quote","Radio":"radio","Receipt":"receipt","Redo":"arrow-forward","RefreshCw":"refresh","RemoveFormatting":"clear-formatting","Repeat":"repeat","Reply":"arrow-back-up","ReplyAll":"arrow-back-up-double","Rocket":"rocket","RotateCcw":"rotate","RotateCw":"rotate-clockwise","Rows3":"layout-rows","Rss":"rss","Ruler":"ruler","Save":"device-floppy","Scale":"scale","Scan":"scan","Scissors":"scissors","ScrollText":"file-text","Search":"search","SearchX":"search-off","Send":"send","Server":"server","Settings":"settings","Share2":"share-2","Shield":"shield","ShieldAlert":"shield-exclamation","ShieldCheck":"shield-check","Ship":"ship","Shuffle":"arrows-shuffle","Smartphone":"device-mobile","Snowflake":"snowflake","Sparkles":"sparkles","Square":"square","SquareKanban":"layout-kanban","SquarePen":"edit","Star":"star","Stethoscope":"stethoscope","StickyNote":"note","Store":"building-store","Strikethrough":"strikethrough","Sun":"sun","SwatchBook":"color-swatch","Syringe":"vaccine","Table":"table","Tablet":"device-tablet","Tag":"tag","Tags":"tags","Target":"target","Terminal":"terminal","Thermometer":"thermometer","ThumbsUp":"thumb-up","Timer":"stopwatch","Train":"train","Trash":"trash","Trash2":"trash","TreePine":"christmas-tree","Trello":"brand-trello","TrendingUp":"trending-up","TriangleAlert":"alert-triangle","Trophy":"trophy","Tv":"device-tv","Type":"typography","Underline":"underline","Undo":"arrow-back","Unlock":"lock-open","Upload":"upload","User":"user","UserCircle":"user-circle","UserMinus":"user-minus","UserPen":"user-edit","UserPlus":"user-plus","Users":"users","UsersRound":"users","Video":"video","Volume2":"volume-2","Wallet":"wallet","Wand2":"wand","Warehouse":"building-warehouse","Waves":"ripple","Webcam":"device-computer-camera","Wifi":"wifi","Wine":"glass","Wrench":"tool","X":"x","XCircle":"circle-x","Youtube":"brand-youtube","Zap":"bolt","ZoomIn":"zoom-in","ZoomOut":"zoom-out"};
|
|
|
|
const TABLER_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
const LUCIDE_NAME_RE = /^[A-Za-z][A-Za-z0-9]{0,63}$/;
|
|
|
|
/** Stored form of a Tabler icon name. */
|
|
export function toStoredIconName(tablerName: string): string {
|
|
return TABLER_ICON_PREFIX + tablerName;
|
|
}
|
|
|
|
/**
|
|
* Resolve a stored icon name to a Tabler name: `tabler:<name>` as is, a
|
|
* known Lucide name through the table, any other Lucide name by converting
|
|
* its PascalCase to kebab-case (most Lucide and Tabler names agree).
|
|
*/
|
|
export function toTablerName(stored: string | null | undefined): string | null {
|
|
if (!stored) return null;
|
|
if (stored.startsWith(TABLER_ICON_PREFIX)) {
|
|
const name = stored.slice(TABLER_ICON_PREFIX.length);
|
|
return TABLER_NAME_RE.test(name) ? name : null;
|
|
}
|
|
if (LEGACY_LUCIDE[stored]) return LEGACY_LUCIDE[stored];
|
|
// A bare Tabler name, as an admin might type it.
|
|
if (TABLER_NAME_RE.test(stored)) return stored;
|
|
if (!LUCIDE_NAME_RE.test(stored)) return null;
|
|
return stored.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/([A-Za-z])(\d)/g, '$1-$2').toLowerCase();
|
|
}
|
|
|
|
const componentKey = (name: string) =>
|
|
'Icon' + name.split('-').map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('');
|
|
|
|
let allIcons: Record<string, TablerIcon> | null = null;
|
|
let allIconsPromise: Promise<Record<string, TablerIcon>> | null = null;
|
|
|
|
function loadAllIcons(): Promise<Record<string, TablerIcon>> {
|
|
allIconsPromise ??= import('@tabler/icons-react').then((m) => {
|
|
allIcons = m.icons as unknown as Record<string, TablerIcon>;
|
|
return allIcons;
|
|
});
|
|
return allIconsPromise;
|
|
}
|
|
|
|
/** Every outline icon name in Tabler, for the picker's "show all" list. */
|
|
export async function loadAllIconNames(): Promise<string[]> {
|
|
const m = await import('@tabler/icons-react');
|
|
return m.iconsList.default.filter((n) => !n.endsWith('-filled'));
|
|
}
|
|
|
|
const lazyIcons = new Map<string, TablerIcon>();
|
|
|
|
function lazyIcon(name: string): TablerIcon {
|
|
const LazyIcon = forwardRef<SVGSVGElement, Parameters<TablerIcon>[0]>(function LazyIcon(props, ref) {
|
|
const [Resolved, setResolved] = useState<TablerIcon | null>(
|
|
() => (allIcons ? allIcons[componentKey(name)] ?? IconWorld : null),
|
|
);
|
|
useEffect(() => {
|
|
if (Resolved) return;
|
|
let live = true;
|
|
loadAllIcons().then((icons) => {
|
|
if (live) setResolved(() => icons[componentKey(name)] ?? IconWorld);
|
|
});
|
|
return () => {
|
|
live = false;
|
|
};
|
|
}, [Resolved]);
|
|
if (!Resolved) {
|
|
const size = props.size ?? 24;
|
|
return <svg ref={ref} width={size} height={size} className={props.className} aria-hidden="true" />;
|
|
}
|
|
return <Resolved ref={ref} {...props} />;
|
|
});
|
|
return LazyIcon as unknown as TablerIcon;
|
|
}
|
|
|
|
/**
|
|
* The component for a stored icon name, or undefined when there is no usable
|
|
* name. Unknown names render the world icon once the full set has loaded.
|
|
*/
|
|
export function iconForName(stored: string | null | undefined): TablerIcon | undefined {
|
|
const name = toTablerName(stored);
|
|
if (!name) return undefined;
|
|
const curated = CURATED[name];
|
|
if (curated) return curated;
|
|
let icon = lazyIcons.get(name);
|
|
if (!icon) {
|
|
icon = lazyIcon(name);
|
|
lazyIcons.set(name, icon);
|
|
}
|
|
return icon;
|
|
}
|