From 75299244cfd9b5999063ce114aab7c46a86c765d Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 15 Oct 2026 14:13:02 +0200 Subject: [PATCH] refactor: draw the webmail's Tabler icons instead of Lucide --- App.tsx | 2 +- babel.config.js | 1 - metro.config.js | 15 - package-lock.json | 13 +- package.json | 1 - scripts/babel-plugin-lucide-imports.js | 91 ----- scripts/generate-icons.mjs | 241 ++++++++++++ src/components/Dialog.tsx | 2 +- src/components/EmailBodyView.tsx | 2 +- src/components/IdentitySheet.tsx | 2 +- src/components/MoveSheet.tsx | 6 +- src/components/OfflineBanner.tsx | 2 +- src/components/OfflineCacheBanner.tsx | 2 +- src/components/PasteSignInLinkModal.tsx | 2 +- src/components/PushOnboardingPrompt.tsx | 2 +- src/components/QrScanModal.tsx | 2 +- src/components/SidebarDrawer.tsx | 10 +- src/components/SwipeableRow.tsx | 8 +- src/components/TagSheet.tsx | 2 +- src/components/TemplateSheet.tsx | 2 +- src/components/ToastHost.tsx | 2 +- src/components/UpdateBanner.tsx | 2 +- src/components/calendar/CalendarEditSheet.tsx | 2 +- src/components/calendar/CalendarFab.tsx | 2 +- .../calendar/CalendarShareSheet.tsx | 2 +- .../calendar/CalendarSidebarDrawer.tsx | 6 +- src/components/calendar/EventCard.tsx | 2 +- src/components/calendar/EventDetailSheet.tsx | 2 +- src/components/calendar/EventModal.tsx | 2 +- src/components/calendar/ICalImportSheet.tsx | 2 +- .../calendar/ICalSubscriptionSheet.tsx | 2 +- src/components/calendar/ParticipantInput.tsx | 2 +- src/components/calendar/RecurrenceEditor.tsx | 2 +- .../calendar/RecurrenceScopeDialog.tsx | 2 +- src/components/calendar/TasksSheet.tsx | 2 +- .../contacts/AddressBookPickerSheet.tsx | 2 +- src/components/contacts/ContactActivity.tsx | 2 +- .../contacts/ContactImportSheet.tsx | 2 +- src/components/contacts/ContactListRow.tsx | 2 +- .../contacts/ContactPickerSheet.tsx | 2 +- .../contacts/ContactsSidebarDrawer.tsx | 2 +- src/components/contacts/FieldBlock.tsx | 2 +- src/components/contacts/TagAssignSheet.tsx | 2 +- src/components/email/ActionSheet.tsx | 2 +- src/components/email/AddressActionSheet.tsx | 2 +- src/components/email/AttachmentChips.tsx | 2 +- .../email/AttachmentPreviewModal.tsx | 2 +- .../email/CalendarInvitationBanner.tsx | 2 +- src/components/email/ListAttachmentChips.tsx | 2 +- src/components/email/MessageHeader.tsx | 2 +- src/components/email/QuickReplyBox.tsx | 2 +- src/components/email/ReadReceiptBanner.tsx | 2 +- src/components/email/ThreadMessageCard.tsx | 2 +- src/components/email/UnsubscribeBanner.tsx | 2 +- src/components/files/ShareSheet.tsx | 2 +- src/components/filters/FilterRuleModal.tsx | 2 +- src/components/filters/SieveEditorSheet.tsx | 2 +- src/components/icons/__tests__/icons.test.ts | 74 ++++ src/components/icons/icon.tsx | 41 ++ src/components/icons/index.ts | 357 ++++++++++++++++++ src/components/settings/AboutDataSettings.tsx | 2 +- .../settings/AccountSecuritySettings.tsx | 2 +- src/components/settings/AccountSettings.tsx | 2 +- src/components/settings/ComposingSettings.tsx | 2 +- src/components/settings/ContactsSettings.tsx | 2 +- .../settings/ContentSendersSettings.tsx | 2 +- src/components/settings/DownloadsSettings.tsx | 2 +- src/components/settings/FilesSettings.tsx | 2 +- src/components/settings/FilterSettings.tsx | 2 +- src/components/settings/FolderSettings.tsx | 2 +- src/components/settings/IdentitySettings.tsx | 2 +- src/components/settings/KeywordSettings.tsx | 2 +- src/components/settings/LanguageSettings.tsx | 2 +- src/components/settings/LayoutSettings.tsx | 2 +- .../settings/NotificationSettings.tsx | 2 +- src/components/settings/PluginsSettings.tsx | 2 +- src/components/settings/ReadingSettings.tsx | 2 +- .../settings/SidebarAppsSettings.tsx | 2 +- src/components/settings/SmimeSettings.tsx | 2 +- src/components/settings/TemplateSettings.tsx | 2 +- src/components/settings/ThemesSettings.tsx | 2 +- src/components/settings/VacationSettings.tsx | 2 +- .../device-sync/DeviceSyncAccountRow.tsx | 2 +- .../DeviceSyncCollectionsSheet.tsx | 2 +- src/components/settings/settings-section.tsx | 2 +- src/lib/__tests__/lucide-imports.test.ts | 105 ------ src/screens/CalendarScreen.tsx | 2 +- src/screens/ComposeScreen.tsx | 2 +- src/screens/ContactDetailScreen.tsx | 2 +- src/screens/ContactFormScreen.tsx | 2 +- src/screens/ContactsScreen.tsx | 2 +- src/screens/ContactsTab.tsx | 2 +- src/screens/EmailListScreen.tsx | 2 +- src/screens/EmailSourceScreen.tsx | 2 +- src/screens/EmailThreadScreen.tsx | 2 +- src/screens/FilesScreen.tsx | 2 +- src/screens/GroupDetailScreen.tsx | 2 +- src/screens/MailTab.tsx | 2 +- src/screens/ScheduledScreen.tsx | 2 +- src/screens/SettingsScreen.tsx | 6 +- src/screens/UnifiedInboxScreen.tsx | 2 +- src/screens/login/ChooseStep.tsx | 2 +- src/screens/login/ConfirmStep.tsx | 2 +- src/screens/login/EmailStep.tsx | 2 +- src/screens/login/LoginNotice.tsx | 2 +- src/screens/login/LoginShell.tsx | 2 +- src/screens/login/OptionTile.tsx | 2 +- src/screens/login/PasswordStep.tsx | 2 +- src/screens/login/ServerStep.tsx | 2 +- 109 files changed, 826 insertions(+), 337 deletions(-) delete mode 100644 scripts/babel-plugin-lucide-imports.js create mode 100644 scripts/generate-icons.mjs create mode 100644 src/components/icons/__tests__/icons.test.ts create mode 100644 src/components/icons/icon.tsx create mode 100644 src/components/icons/index.ts delete mode 100644 src/lib/__tests__/lucide-imports.test.ts diff --git a/App.tsx b/App.tsx index df9efb2a..2cb212d9 100644 --- a/App.tsx +++ b/App.tsx @@ -8,7 +8,7 @@ import { } from '@react-navigation/native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { createNativeStackNavigator, type NativeStackScreenProps } from '@react-navigation/native-stack'; -import { Mail, Calendar, BookUser, HardDrive, Settings } from 'lucide-react-native'; +import { Mail, Calendar, BookUser, HardDrive, Settings } from './src/components/icons'; import { startLiveUpdates, type LiveUpdatesHandle } from './src/api/push-stream'; import { jmapClient } from './src/api/jmap-client'; diff --git a/babel.config.js b/babel.config.js index 755e6f71..6ab2f75a 100644 --- a/babel.config.js +++ b/babel.config.js @@ -10,6 +10,5 @@ module.exports = function babelConfig(api) { api.cache(true); return { presets: [presetExpo], - plugins: [require.resolve('./scripts/babel-plugin-lucide-imports')], }; }; diff --git a/metro.config.js b/metro.config.js index 222aecac..32938e89 100644 --- a/metro.config.js +++ b/metro.config.js @@ -2,19 +2,4 @@ const { getDefaultConfig } = require('expo/metro-config'); const config = getDefaultConfig(__dirname); -// babel.config.js rewrites lucide-react-native imports to per-icon modules -// under dist/ (scripts/babel-plugin-lucide-imports.js). The package's -// `exports` map only lists its barrel, so resolve those paths as plain files -// instead of letting Metro warn about every one of them. -const LUCIDE_DEEP_IMPORT = 'lucide-react-native/dist/'; -const upstreamResolveRequest = config.resolver.resolveRequest; - -config.resolver.resolveRequest = (context, moduleName, platform) => { - const resolve = upstreamResolveRequest ?? context.resolveRequest; - if (moduleName.startsWith(LUCIDE_DEEP_IMPORT)) { - return resolve({ ...context, unstable_enablePackageExports: false }, moduleName, platform); - } - return resolve(context, moduleName, platform); -}; - module.exports = config; diff --git a/package-lock.json b/package-lock.json index 4ffd7635..775b0295 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "bulwark-mobile", "version": "0.1.76", + "hasInstallScript": true, "license": "AGPL-3.0-only", "dependencies": { "@react-native-async-storage/async-storage": "2.2.0", @@ -33,7 +34,6 @@ "expo-status-bar": "~3.0.9", "expo-web-browser": "~15.0.8", "jszip": "^3.10.1", - "lucide-react-native": "^1.7.0", "postal-mime": "^3.0.0", "react": "19.1.0", "react-native": "0.81.5", @@ -7790,17 +7790,6 @@ "yallist": "^3.0.2" } }, - "node_modules/lucide-react-native": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/lucide-react-native/-/lucide-react-native-1.7.0.tgz", - "integrity": "sha512-wGJY5nosSawh028jg8r1ZKqnGPDIVfIL9xvKOs4wPYFQHeJMHsADYm/lmuFYXMXXatSkHhpsCjeqIRgeFGzf8g==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-native": "*", - "react-native-svg": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0" - } - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", diff --git a/package.json b/package.json index 47658d87..7651f984 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "expo-status-bar": "~3.0.9", "expo-web-browser": "~15.0.8", "jszip": "^3.10.1", - "lucide-react-native": "^1.7.0", "postal-mime": "^3.0.0", "react": "19.1.0", "react-native": "0.81.5", diff --git a/scripts/babel-plugin-lucide-imports.js b/scripts/babel-plugin-lucide-imports.js deleted file mode 100644 index 44484f4e..00000000 --- a/scripts/babel-plugin-lucide-imports.js +++ /dev/null @@ -1,91 +0,0 @@ -// Babel plugin: rewrite named imports from the `lucide-react-native` barrel to -// the per-icon modules they re-export, so the bundle only contains (and the -// app only evaluates) the icons it uses. The barrel re-exports all ~1,700 -// icons, and Metro has no tree shaking, so one `import { Mail }` used to pull -// in and run every icon at startup. -// -// import { Mail, Trash2 as Bin } from 'lucide-react-native'; -// becomes -// import Mail from 'lucide-react-native/dist/esm/icons/mail.js'; -// import Bin from 'lucide-react-native/dist/esm/icons/trash-2.js'; -// -// The name -> file table is read from the package's own ESM barrel, so icon -// aliases (`MailIcon`, `LucideMail`, renamed icons) resolve exactly as they do -// through the barrel. metro.config.js resolves these deep paths, which the -// package's `exports` map does not list. -const fs = require('node:fs'); -const path = require('node:path'); - -const PACKAGE = 'lucide-react-native'; -const ESM_DIR = 'dist/esm'; - -/** Map every name the barrel exports to `{ source, imported }`. */ -function loadExportMap() { - // `main` is dist/cjs/lucide-react-native.js; the ESM tree sits next to it. - const pkgDir = path.resolve(path.dirname(require.resolve(PACKAGE)), '..', '..'); - const barrel = fs.readFileSync(path.join(pkgDir, ESM_DIR, `${PACKAGE}.js`), 'utf8'); - const map = new Map(); - for (const m of barrel.matchAll(/export\s*\{([^}]*)\}\s*from\s*'\.\/([^']+)'/g)) { - const source = `${PACKAGE}/${ESM_DIR}/${m[2]}`; - for (const spec of m[1].split(',')) { - const [imported, exported = imported] = spec.trim().split(/\s+as\s+/); - if (imported) map.set(exported, { source, imported }); - } - } - if (map.size === 0) { - throw new Error(`${PACKAGE}: could not read the export list from its ESM barrel`); - } - return map; -} - -let exportMap = null; -function getExportMap() { - if (!exportMap) exportMap = loadExportMap(); - return exportMap; -} - -// A value-less binding (only used as a type) is dropped by the TypeScript -// transform anyway; treat it the same way instead of failing the build. -function isOnlyUsedAsType(importPath, localName) { - const binding = importPath.scope.getBinding(localName); - if (!binding) return true; - return binding.referencePaths.every((ref) => Boolean(ref.findParent((p) => p.isTSType()))); -} - -function lucideImportsPlugin({ types: t }) { - return { - name: 'lucide-direct-imports', - visitor: { - ImportDeclaration(importPath) { - const { node } = importPath; - if (node.source.value !== PACKAGE || node.importKind === 'type') return; - // Side-effect, default and namespace imports keep using the barrel. - if (node.specifiers.length === 0) return; - if (!node.specifiers.every((s) => t.isImportSpecifier(s))) return; - - const map = getExportMap(); - const replacements = []; - for (const spec of node.specifiers) { - if (spec.importKind === 'type') continue; - const name = t.isIdentifier(spec.imported) ? spec.imported.name : spec.imported.value; - const target = map.get(name); - if (!target) { - if (isOnlyUsedAsType(importPath, spec.local.name)) continue; - throw importPath.buildCodeFrameError(`${PACKAGE} has no export named "${name}"`); - } - const local = t.identifier(spec.local.name); - const specifier = target.imported === 'default' - ? t.importDefaultSpecifier(local) - : t.importSpecifier(local, t.identifier(target.imported)); - replacements.push(t.importDeclaration([specifier], t.stringLiteral(target.source))); - } - if (replacements.length === 0) importPath.remove(); - else importPath.replaceWithMultiple(replacements); - }, - }, - }; -} - -module.exports = lucideImportsPlugin; -module.exports.loadExportMap = loadExportMap; -module.exports.PACKAGE = PACKAGE; diff --git a/scripts/generate-icons.mjs b/scripts/generate-icons.mjs new file mode 100644 index 00000000..8be3b870 --- /dev/null +++ b/scripts/generate-icons.mjs @@ -0,0 +1,241 @@ +// Writes src/components/icons/index.ts from Tabler's outline path data. +// +// The app draws the same icon set as the webmail (Tabler Icons, MIT) under the +// names it used when it was on Lucide, so screens import `{ Mail, Trash2 }` +// from components/icons. Only the glyphs listed in MAP ship in the bundle. +// +// node scripts/generate-icons.mjs [path/to/tabler-nodes-outline.json] +// +// The default path is the webmail checkout this repo usually sits in +// (repos/react-native inside jmap-webmail), whose node_modules has +// @tabler/icons. Add a line to MAP to make a new icon importable. + +import { readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); +const SOURCE = process.argv[2] + ?? join(ROOT, '..', '..', 'node_modules', '@tabler', 'icons', 'tabler-nodes-outline.json'); + +// App name -> Tabler outline name. Where the webmail (components/icons) maps +// the same name, this uses its choice. +const MAP = { + AlarmClock: 'alarm', + AlertCircle: 'alert-circle', + AlertTriangle: 'alert-triangle', + AlignCenter: 'align-center', + AlignLeft: 'align-left', + AlignRight: 'align-right', + Archive: 'archive', + ArrowDown: 'arrow-down', + ArrowDownWideNarrow: 'sort-descending', + ArrowLeft: 'arrow-left', + ArrowRight: 'arrow-right', + ArrowUp: 'arrow-up', + ArrowUpDown: 'arrows-sort', + ArrowUpNarrowWide: 'sort-ascending', + Ban: 'ban', + Bell: 'bell', + BellRing: 'bell-ringing', + Bold: 'bold', + Book: 'book-2', + BookOpen: 'book', + BookUser: 'address-book', + Bug: 'bug', + Building: 'building', + Cake: 'cake', + Calendar: 'calendar', + CalendarDays: 'calendar-month', + CalendarPlus: 'calendar-plus', + Camera: 'camera', + Check: 'check', + CheckCheck: 'checks', + CheckCircle: 'circle-check', + CheckCircle2: 'circle-check', + CheckSquare: 'square-check', + ChevronDown: 'chevron-down', + ChevronLeft: 'chevron-left', + ChevronRight: 'chevron-right', + ChevronUp: 'chevron-up', + CircleCheck: 'circle-check', + ClipboardPaste: 'clipboard-text', + Clock: 'clock', + CloudDownload: 'cloud-download', + CloudOff: 'cloud-off', + Code: 'code', + Columns3: 'columns-3', + Columns4: 'layout-columns', + Copy: 'copy', + Database: 'database', + Download: 'download', + Edit2: 'pencil', + Eraser: 'eraser', + ExternalLink: 'external-link', + Eye: 'eye', + EyeOff: 'eye-off', + File: 'file', + FileArchive: 'file-zip', + FileAudio: 'file-music', + FileCode: 'file-code', + FileCode2: 'file-code-2', + FileImage: 'photo', + FileInput: 'file-import', + FileSpreadsheet: 'file-spreadsheet', + FileText: 'file-text', + FileVideo: 'video', + Filter: 'filter', + Flag: 'flag', + FlaskConical: 'flask', + Folder: 'folder', + FolderArchive: 'archive', + FolderInput: 'folder-down', + FolderOpen: 'folder-open', + FolderPlus: 'folder-plus', + FolderSync: 'folders', + Forward: 'arrow-forward-up', + Globe: 'world', + Grid3x3: 'grid-dots', + GripVertical: 'grip-vertical', + HardDrive: 'server-2', + Heading1: 'h-1', + Heading2: 'h-2', + Heart: 'heart', + HelpCircle: 'help-circle', + Highlighter: 'highlight', + Image: 'photo', + Import: 'file-import', + Inbox: 'inbox', + Info: 'info-circle', + Italic: 'italic', + Key: 'key', + KeyRound: 'key', + Languages: 'language', + Layers: 'stack', + LayoutGrid: 'layout-grid', + LayoutTemplate: 'template', + Link2: 'link', + Link2Off: 'unlink', + List: 'list', + ListChecks: 'list-check', + ListOrdered: 'list-numbers', + Lock: 'lock', + LogOut: 'logout', + Mail: 'mail', + MailCheck: 'mail-check', + MailMinus: 'mail-minus', + MailOpen: 'mail-opened', + Mails: 'inbox', + MapPin: 'map-pin', + Maximize2: 'maximize', + Menu: 'menu-2', + MessageSquare: 'message', + Minus: 'minus', + Monitor: 'device-desktop', + Moon: 'moon', + MoreHorizontal: 'dots', + MoreVertical: 'dots-vertical', + MoveVertical: 'arrows-vertical', + Palette: 'palette', + Palmtree: 'beach', + PanelLeftClose: 'layout-sidebar-left-collapse', + PanelRight: 'layout-sidebar-right', + Paperclip: 'paperclip', + PenLine: 'pencil', + PenSquare: 'edit', + Pencil: 'pencil', + Phone: 'phone', + Pin: 'pinned', + PinOff: 'pinned-off', + Plus: 'plus', + Puzzle: 'puzzle', + QrCode: 'qrcode', + Quote: 'quote', + Redo2: 'arrow-forward', + RefreshCw: 'refresh', + RemoveFormatting: 'clear-formatting', + Repeat: 'repeat', + Reply: 'arrow-back-up', + ReplyAll: 'arrow-back-up-double', + RotateCcw: 'rotate', + Rows2: 'layout-rows', + Rss: 'rss', + Save: 'device-floppy', + ScanSearch: 'zoom-scan', + Scale: 'scale', + Search: 'search', + Send: 'send', + Server: 'server', + Settings: 'settings', + Share2: 'share-2', + Shield: 'shield', + ShieldAlert: 'shield-exclamation', + ShieldCheck: 'shield-check', + ShieldQuestion: 'shield-question', + Shuffle: 'arrows-shuffle', + Smartphone: 'device-mobile', + Square: 'square', + SquareCheck: 'square-check', + SquarePen: 'edit', + Star: 'star', + StickyNote: 'note', + Strikethrough: 'strikethrough', + Sun: 'sun', + Table: 'table', + Tag: 'tag', + Tags: 'tags', + Terminal: 'terminal', + Trash: 'trash', + Trash2: 'trash', + Type: 'typography', + Underline: 'underline', + Undo2: 'arrow-back', + Upload: 'upload', + User: 'user', + UserCircle: 'user-circle', + UserMinus: 'user-minus', + UserPen: 'user-edit', + UserPlus: 'user-plus', + UserRound: 'user', + UserRoundPlus: 'user-plus', + Users: 'users', + Video: 'video', + X: 'x', + XCircle: 'circle-x', +}; + +function main() { + const nodes = JSON.parse(readFileSync(SOURCE, 'utf8')); + const glyphs = new Map(); + const missing = []; + for (const [appName, tabler] of Object.entries(MAP)) { + const icon = nodes[tabler]; + if (!icon) { missing.push(`${appName} -> ${tabler}`); continue; } + const paths = icon.map(([tag, attrs]) => { + if (tag !== 'path') throw new Error(`${tabler}: unexpected <${tag}>`); + return attrs.d; + }); + glyphs.set(tabler, paths); + } + if (missing.length) throw new Error(`Not in Tabler: ${missing.join(', ')}`); + + const lines = [ + '// Generated by scripts/generate-icons.mjs from @tabler/icons (MIT).', + '// Do not edit by hand: add the icon to MAP in the script and run it.', + '', + "import { makeIcon } from './icon';", + '', + "export type { AppIcon, IconProps } from './icon';", + '', + 'const G = {', + ...[...glyphs.keys()].sort().map((k) => ` '${k}': ${JSON.stringify(glyphs.get(k))},`), + '} as const;', + '', + ...Object.entries(MAP).map(([a, t]) => `export const ${a} = makeIcon('${a}', G['${t}']);`), + '', + ]; + writeFileSync(join(ROOT, 'src', 'components', 'icons', 'index.ts'), lines.join('\n')); + console.log(`${Object.keys(MAP).length} names, ${glyphs.size} glyphs`); +} + +main(); diff --git a/src/components/Dialog.tsx b/src/components/Dialog.tsx index a82463e9..e5a177b5 100644 --- a/src/components/Dialog.tsx +++ b/src/components/Dialog.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet, Modal, TextInput, type TextInputProps } from 'react-native'; -import { AlertTriangle } from 'lucide-react-native'; +import { AlertTriangle } from './icons'; import Button from './Button'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; diff --git a/src/components/EmailBodyView.tsx b/src/components/EmailBodyView.tsx index 225cdfd2..091f0088 100644 --- a/src/components/EmailBodyView.tsx +++ b/src/components/EmailBodyView.tsx @@ -3,7 +3,7 @@ import { View, StyleSheet, Linking, Platform, Text, Pressable, useWindowDimensio import { WebView, type WebViewMessageEvent } from 'react-native-webview'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { Image as ImageIcon, ShieldCheck } from 'lucide-react-native'; +import { Image as ImageIcon, ShieldCheck } from './icons'; import type { Email } from '../api/types'; import { extractCidRefs } from '../lib/email-html'; import { hasTruncatedDisplayedBody, pickEmailBody, selectRenderableHtml } from '../lib/email-body'; diff --git a/src/components/IdentitySheet.tsx b/src/components/IdentitySheet.tsx index 76a96b29..a4712c83 100644 --- a/src/components/IdentitySheet.tsx +++ b/src/components/IdentitySheet.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ScrollView, Pressable, Modal, Animated, Easing, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { X, Check, Mail } from 'lucide-react-native'; +import { X, Check, Mail } from './icons'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; import { useSheetDrag } from '../lib/use-sheet-drag'; diff --git a/src/components/MoveSheet.tsx b/src/components/MoveSheet.tsx index 3b9818f0..ac61934a 100644 --- a/src/components/MoveSheet.tsx +++ b/src/components/MoveSheet.tsx @@ -5,8 +5,8 @@ import { import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Inbox, Send, File as FileIcon, Trash2, Ban, Archive, Folder, - X, Check, type LucideIcon, -} from 'lucide-react-native'; + X, Check, type AppIcon, +} from './icons'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; import { buildMailboxTree, flattenVisible, type MailboxNode } from '../lib/mailbox-tree'; @@ -17,7 +17,7 @@ import type { Mailbox } from '../api/types'; import { MODAL_ORIENTATIONS } from '../lib/modal-orientations'; import { useSheetInsets } from '../lib/use-layout'; -function moveTargetIcon(role: string | null | undefined, name: string): LucideIcon { +function moveTargetIcon(role: string | null | undefined, name: string): AppIcon { const lower = name.toLowerCase(); if (role === 'inbox' || lower.includes('inbox')) return Inbox; if (role === 'sent' || lower.includes('sent')) return Send; diff --git a/src/components/OfflineBanner.tsx b/src/components/OfflineBanner.tsx index 6b30da33..46fe7e32 100644 --- a/src/components/OfflineBanner.tsx +++ b/src/components/OfflineBanner.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; -import { CloudOff } from 'lucide-react-native'; +import { CloudOff } from './icons'; import { useNetworkStore } from '../stores/network-store'; import { useOutboxStore } from '../stores/outbox-store'; import { useLocaleStore } from '../stores/locale-store'; diff --git a/src/components/OfflineCacheBanner.tsx b/src/components/OfflineCacheBanner.tsx index 2aac398f..38e4c220 100644 --- a/src/components/OfflineCacheBanner.tsx +++ b/src/components/OfflineCacheBanner.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { CloudDownload, X } from 'lucide-react-native'; +import { CloudDownload, X } from './icons'; import { useOfflineCacheStore } from '../stores/offline-cache-store'; import { useUpdatesStore } from '../stores/updates-store'; import { formatBytes } from '../lib/offline-sync'; diff --git a/src/components/PasteSignInLinkModal.tsx b/src/components/PasteSignInLinkModal.tsx index c8b8fb73..3f27698b 100644 --- a/src/components/PasteSignInLinkModal.tsx +++ b/src/components/PasteSignInLinkModal.tsx @@ -12,7 +12,7 @@ import { } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import * as Clipboard from 'expo-clipboard'; -import { ClipboardPaste, X } from 'lucide-react-native'; +import { ClipboardPaste, X } from './icons'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; import Button from './Button'; diff --git a/src/components/PushOnboardingPrompt.tsx b/src/components/PushOnboardingPrompt.tsx index ad9ef3b2..f6bbd171 100644 --- a/src/components/PushOnboardingPrompt.tsx +++ b/src/components/PushOnboardingPrompt.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable, Animated, Easing } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useFloatingCardFrame } from '../lib/use-layout'; -import { BellRing, X } from 'lucide-react-native'; +import { BellRing, X } from './icons'; import Button from './Button'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; diff --git a/src/components/QrScanModal.tsx b/src/components/QrScanModal.tsx index bd81ca3b..c9a98a00 100644 --- a/src/components/QrScanModal.tsx +++ b/src/components/QrScanModal.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { View, Text, Pressable, StyleSheet, Modal, Platform, Linking } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { CameraView, useCameraPermissions, type BarcodeScanningResult } from 'expo-camera'; -import { X } from 'lucide-react-native'; +import { X } from './icons'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { useColors, useResolvedTheme } from '../theme/colors'; import Button from './Button'; diff --git a/src/components/SidebarDrawer.tsx b/src/components/SidebarDrawer.tsx index 0dc475c4..94bf05e8 100644 --- a/src/components/SidebarDrawer.tsx +++ b/src/components/SidebarDrawer.tsx @@ -10,8 +10,8 @@ import { Folder, FolderOpen, ChevronDown, ChevronRight, X, Settings, LogOut, Check, Plus, Clock, Layers, Users, Tag, Mails, MailOpen, StickyNote, AlarmClock, Flag, CheckCheck, Eraser, FolderPlus, Pencil, AlertTriangle, UserMinus, - type LucideIcon, -} from 'lucide-react-native'; + type AppIcon, +} from './icons'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; import { useAnimDuration } from '../theme/dynamic'; @@ -65,7 +65,7 @@ function iconFor( name: string | undefined, hasChildren: boolean, isExpanded: boolean, -): LucideIcon { +): AppIcon { switch (role) { case 'inbox': return Inbox; case 'sent': return Send; @@ -224,7 +224,7 @@ function SidebarRow({ interface SheetAction { key: string; label: string; - icon: LucideIcon; + icon: AppIcon; destructive?: boolean; onPress: () => void; } @@ -685,7 +685,7 @@ export default function SidebarDrawer({ visible, onClose, docked = false }: Side }; }, [mailboxes, accountSnapshots, activeAccountId, unifiedCrossAccount, includeGroupInUnified]); - const unifiedIcon = (role: UnifiedRole): LucideIcon => iconFor(role, undefined, false, false); + const unifiedIcon = (role: UnifiedRole): AppIcon => iconFor(role, undefined, false, false); const slideX = React.useRef(new Animated.Value(-Dimensions.get('window').width)).current; const overlayOpacity = React.useRef(new Animated.Value(0)).current; diff --git a/src/components/SwipeableRow.tsx b/src/components/SwipeableRow.tsx index 5f5e8276..0b824de1 100644 --- a/src/components/SwipeableRow.tsx +++ b/src/components/SwipeableRow.tsx @@ -4,8 +4,8 @@ import { } from 'react-native'; import { Archive, Trash2, ShieldAlert, ShieldCheck, MailOpen, Mail, Star, Pin, PinOff, FolderInput, - type LucideIcon, -} from 'lucide-react-native'; + type AppIcon, +} from './icons'; import { typography, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; import { useLocaleStore } from '../stores/locale-store'; @@ -43,7 +43,7 @@ export interface SwipeContext { // Distance the row flies off-screen by before the action callback fires. const EXIT_DISTANCE = 600; -const ACTION_META: Record, { icon: LucideIcon; bg: string; defaultLabel: string }> = { +const ACTION_META: Record, { icon: AppIcon; bg: string; defaultLabel: string }> = { archive: { icon: Archive, bg: '#1d4ed8', defaultLabel: 'Archive' }, delete: { icon: Trash2, bg: '#b91c1c', defaultLabel: 'Delete' }, spam: { icon: ShieldAlert, bg: '#a16207', defaultLabel: 'Spam' }, @@ -68,7 +68,7 @@ function actionLabel(action: SwipeAction, context: SwipeContext, t: Translate): } } -function actionIcon(action: Exclude, context: SwipeContext): LucideIcon { +function actionIcon(action: Exclude, context: SwipeContext): AppIcon { if (action === 'spam' && context.inJunk) return ShieldCheck; if (action === 'read' && !context.unread) return Mail; if (action === 'pin' && context.pinned) return PinOff; diff --git a/src/components/TagSheet.tsx b/src/components/TagSheet.tsx index 8c054f3b..1cd7f105 100644 --- a/src/components/TagSheet.tsx +++ b/src/components/TagSheet.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ScrollView, Pressable, Modal, Animated, Easing, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { X, Check, Tag } from 'lucide-react-native'; +import { X, Check, Tag } from './icons'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; import { useSheetDrag } from '../lib/use-sheet-drag'; diff --git a/src/components/TemplateSheet.tsx b/src/components/TemplateSheet.tsx index 562586f6..ae0980bf 100644 --- a/src/components/TemplateSheet.tsx +++ b/src/components/TemplateSheet.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ScrollView, Pressable, Modal, Animated, Easing, TextInput, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { X, Star, FileText } from 'lucide-react-native'; +import { X, Star, FileText } from './icons'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; import { useSheetDrag } from '../lib/use-sheet-drag'; diff --git a/src/components/ToastHost.tsx b/src/components/ToastHost.tsx index 86043935..e2ece7aa 100644 --- a/src/components/ToastHost.tsx +++ b/src/components/ToastHost.tsx @@ -5,7 +5,7 @@ import { } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useFloatingCardFrame } from '../lib/use-layout'; -import { AlertCircle, AlertTriangle, CheckCircle2, Info } from 'lucide-react-native'; +import { AlertCircle, AlertTriangle, CheckCircle2, Info } from './icons'; import { spacing, radius, type ThemePalette } from '../theme/tokens'; import { useColors, useResolvedTheme } from '../theme/colors'; import { useShouldAnimate } from '../theme/dynamic'; diff --git a/src/components/UpdateBanner.tsx b/src/components/UpdateBanner.tsx index d3c89948..83469c78 100644 --- a/src/components/UpdateBanner.tsx +++ b/src/components/UpdateBanner.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable, Linking } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Download, ShieldAlert, X } from 'lucide-react-native'; +import { Download, ShieldAlert, X } from './icons'; import { useUpdatesStore } from '../stores/updates-store'; import { useLocaleStore } from '../stores/locale-store'; import { spacing, radius, typography, type ThemePalette } from '../theme/tokens'; diff --git a/src/components/calendar/CalendarEditSheet.tsx b/src/components/calendar/CalendarEditSheet.tsx index 9a4e054f..0944073f 100644 --- a/src/components/calendar/CalendarEditSheet.tsx +++ b/src/components/calendar/CalendarEditSheet.tsx @@ -10,7 +10,7 @@ import { TouchableWithoutFeedback, View, } from 'react-native'; -import { Check } from 'lucide-react-native'; +import { Check } from '../icons'; import type { Calendar } from '../../api/types'; import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/calendar/CalendarFab.tsx b/src/components/calendar/CalendarFab.tsx index b9c83f0c..8fe962a2 100644 --- a/src/components/calendar/CalendarFab.tsx +++ b/src/components/calendar/CalendarFab.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Animated, Easing, Pressable, StyleSheet, Text, View } from 'react-native'; -import { CalendarPlus, CircleCheck, Plus } from 'lucide-react-native'; +import { CalendarPlus, CircleCheck, Plus } from '../icons'; import { componentSizes, radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { useAnimDuration } from '../../theme/dynamic'; diff --git a/src/components/calendar/CalendarShareSheet.tsx b/src/components/calendar/CalendarShareSheet.tsx index 17db4fef..9c745ab9 100644 --- a/src/components/calendar/CalendarShareSheet.tsx +++ b/src/components/calendar/CalendarShareSheet.tsx @@ -11,7 +11,7 @@ import { TouchableWithoutFeedback, View, } from 'react-native'; -import { Trash2, UserPlus, Users } from 'lucide-react-native'; +import { Trash2, UserPlus, Users } from '../icons'; import { getPrincipals, ownPrincipalId } from '../../api/files'; import type { Calendar, CalendarRights, Principal } from '../../api/types'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/calendar/CalendarSidebarDrawer.tsx b/src/components/calendar/CalendarSidebarDrawer.tsx index f6181e68..26560670 100644 --- a/src/components/calendar/CalendarSidebarDrawer.tsx +++ b/src/components/calendar/CalendarSidebarDrawer.tsx @@ -13,8 +13,8 @@ import { import { SafeAreaView } from 'react-native-safe-area-context'; import { Check, X, Upload, Rss, Shuffle, Star, Plus, Pencil, Share2, Eraser, Trash2, RefreshCw, - type LucideIcon, -} from 'lucide-react-native'; + type AppIcon, +} from '../icons'; import type { Calendar } from '../../api/types'; import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; @@ -29,7 +29,7 @@ import { MODAL_ORIENTATIONS } from '../../lib/modal-orientations'; export interface DrawerViewOption { key: string; label: string; - Icon: LucideIcon; + Icon: AppIcon; } interface CalendarSidebarDrawerProps { diff --git a/src/components/calendar/EventCard.tsx b/src/components/calendar/EventCard.tsx index 68a4e5e1..fd6f9055 100644 --- a/src/components/calendar/EventCard.tsx +++ b/src/components/calendar/EventCard.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable } from 'react-native'; -import { Clock, MapPin, Users } from 'lucide-react-native'; +import { Clock, MapPin, Users } from '../icons'; import { format } from 'date-fns'; import type { Calendar, CalendarEvent } from '../../api/types'; import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/calendar/EventDetailSheet.tsx b/src/components/calendar/EventDetailSheet.tsx index 7285cbf5..ef5aaf8a 100644 --- a/src/components/calendar/EventDetailSheet.tsx +++ b/src/components/calendar/EventDetailSheet.tsx @@ -35,7 +35,7 @@ import { Users, Video, X, -} from 'lucide-react-native'; +} from '../icons'; import { format, type Locale } from 'date-fns'; import type { Calendar, CalendarEvent } from '../../api/types'; import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/calendar/EventModal.tsx b/src/components/calendar/EventModal.tsx index 7bdd45e5..192cf67f 100644 --- a/src/components/calendar/EventModal.tsx +++ b/src/components/calendar/EventModal.tsx @@ -25,7 +25,7 @@ import { Users, Trash2, AlignLeft, -} from 'lucide-react-native'; +} from '../icons'; import { addHours, addMinutes, format } from 'date-fns'; import type { Alert, diff --git a/src/components/calendar/ICalImportSheet.tsx b/src/components/calendar/ICalImportSheet.tsx index 8bbe4713..e26adbcc 100644 --- a/src/components/calendar/ICalImportSheet.tsx +++ b/src/components/calendar/ICalImportSheet.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ScrollView, Pressable, Modal, Animated, Easing, Alert, ActivityIndicator, } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Upload, X, Check, CalendarPlus, AlertTriangle, Clock } from 'lucide-react-native'; +import { Upload, X, Check, CalendarPlus, AlertTriangle, Clock } from '../icons'; import { format, parseISO } from 'date-fns'; import type { Calendar, CalendarEvent } from '../../api/types'; import { uploadBlob } from '../../api/blob'; diff --git a/src/components/calendar/ICalSubscriptionSheet.tsx b/src/components/calendar/ICalSubscriptionSheet.tsx index 486785fc..80d8a5a8 100644 --- a/src/components/calendar/ICalSubscriptionSheet.tsx +++ b/src/components/calendar/ICalSubscriptionSheet.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ScrollView, Pressable, Modal, Animated, Easing, TextInput, ActivityIndicator, Alert, } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; -import { X, Plus, RefreshCw, Trash2, Rss, AlertTriangle } from 'lucide-react-native'; +import { X, Plus, RefreshCw, Trash2, Rss, AlertTriangle } from '../icons'; import { formatDistanceToNow } from 'date-fns'; import Button from '../Button'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/calendar/ParticipantInput.tsx b/src/components/calendar/ParticipantInput.tsx index 6c940bf2..34162d9b 100644 --- a/src/components/calendar/ParticipantInput.tsx +++ b/src/components/calendar/ParticipantInput.tsx @@ -7,7 +7,7 @@ import { TextInput, ScrollView, } from 'react-native'; -import { X } from 'lucide-react-native'; +import { X } from '../icons'; import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { useContactsStore } from '../../stores/contacts-store'; diff --git a/src/components/calendar/RecurrenceEditor.tsx b/src/components/calendar/RecurrenceEditor.tsx index 4a6f20a4..e7d28cb8 100644 --- a/src/components/calendar/RecurrenceEditor.tsx +++ b/src/components/calendar/RecurrenceEditor.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { View, Text, TextInput, Pressable, StyleSheet, Platform } from 'react-native'; import DateTimePicker from '@react-native-community/datetimepicker'; -import { ChevronDown } from 'lucide-react-native'; +import { ChevronDown } from '../icons'; import { format, parseISO } from 'date-fns'; import type { RecurrenceRule } from '../../api/types'; import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/calendar/RecurrenceScopeDialog.tsx b/src/components/calendar/RecurrenceScopeDialog.tsx index dfa76f18..1755cbff 100644 --- a/src/components/calendar/RecurrenceScopeDialog.tsx +++ b/src/components/calendar/RecurrenceScopeDialog.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Modal, View, Text, Pressable, StyleSheet } from 'react-native'; -import { Repeat, Trash2 } from 'lucide-react-native'; +import { Repeat, Trash2 } from '../icons'; import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { useLocaleStore } from '../../stores/locale-store'; diff --git a/src/components/calendar/TasksSheet.tsx b/src/components/calendar/TasksSheet.tsx index 9be36f03..8cb32cb0 100644 --- a/src/components/calendar/TasksSheet.tsx +++ b/src/components/calendar/TasksSheet.tsx @@ -24,7 +24,7 @@ import { Plus, Trash2, X, -} from 'lucide-react-native'; +} from '../icons'; import { format, isBefore, startOfDay } from 'date-fns'; import type { Calendar, CalendarEvent } from '../../api/types'; import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/contacts/AddressBookPickerSheet.tsx b/src/components/contacts/AddressBookPickerSheet.tsx index 7a976497..d8958d63 100644 --- a/src/components/contacts/AddressBookPickerSheet.tsx +++ b/src/components/contacts/AddressBookPickerSheet.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ScrollView, Pressable, Modal, Animated, Easing, TextInput, Alert, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { BookUser, X, Check, Plus } from 'lucide-react-native'; +import { BookUser, X, Check, Plus } from '../icons'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { useSheetDrag } from '../../lib/use-sheet-drag'; diff --git a/src/components/contacts/ContactActivity.tsx b/src/components/contacts/ContactActivity.tsx index 7ee73188..803dfdf5 100644 --- a/src/components/contacts/ContactActivity.tsx +++ b/src/components/contacts/ContactActivity.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { ActivityIndicator, Pressable, StyleSheet, Text, View } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { Mail as MailIcon, CalendarDays } from 'lucide-react-native'; +import { Mail as MailIcon, CalendarDays } from '../icons'; import { format, isAfter, parseISO, type Locale } from 'date-fns'; import type { ContactCard, Email, CalendarEvent } from '../../api/types'; import type { RootStackParamList } from '../../navigation/types'; diff --git a/src/components/contacts/ContactImportSheet.tsx b/src/components/contacts/ContactImportSheet.tsx index a42cf2fc..4a460d52 100644 --- a/src/components/contacts/ContactImportSheet.tsx +++ b/src/components/contacts/ContactImportSheet.tsx @@ -4,7 +4,7 @@ import { } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import * as FileSystem from 'expo-file-system/legacy'; -import { Upload, X, Check, FileText, AlertTriangle, BookUser } from 'lucide-react-native'; +import { Upload, X, Check, FileText, AlertTriangle, BookUser } from '../icons'; import type { ContactCard } from '../../api/types'; import { parseVCard, detectDuplicates } from '../../lib/vcard'; import { getContactDisplayName, getContactPrimaryEmail } from '../../lib/contact-utils'; diff --git a/src/components/contacts/ContactListRow.tsx b/src/components/contacts/ContactListRow.tsx index 72e878d9..9953961d 100644 --- a/src/components/contacts/ContactListRow.tsx +++ b/src/components/contacts/ContactListRow.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable, Image } from 'react-native'; -import { Check, Users } from 'lucide-react-native'; +import { Check, Users } from '../icons'; import type { ContactCard } from '../../api/types'; import { getContactDisplayName, diff --git a/src/components/contacts/ContactPickerSheet.tsx b/src/components/contacts/ContactPickerSheet.tsx index 6087fc4e..742b4e4a 100644 --- a/src/components/contacts/ContactPickerSheet.tsx +++ b/src/components/contacts/ContactPickerSheet.tsx @@ -4,7 +4,7 @@ import { Animated, Dimensions, Easing, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { Search, X, Check } from 'lucide-react-native'; +import { Search, X, Check } from '../icons'; import type { ContactCard } from '../../api/types'; import { useContactsStore, diff --git a/src/components/contacts/ContactsSidebarDrawer.tsx b/src/components/contacts/ContactsSidebarDrawer.tsx index f3a2cb5b..ccc066af 100644 --- a/src/components/contacts/ContactsSidebarDrawer.tsx +++ b/src/components/contacts/ContactsSidebarDrawer.tsx @@ -7,7 +7,7 @@ import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { X, Users, Tag, BookUser, Inbox, ChevronDown, ChevronRight, Plus, Share2, Check, -} from 'lucide-react-native'; +} from '../icons'; import type { RootStackParamList } from '../../navigation/types'; import type { AddressBook } from '../../api/types'; import type { ContactCategory } from '../../stores/contacts-store'; diff --git a/src/components/contacts/FieldBlock.tsx b/src/components/contacts/FieldBlock.tsx index 723af526..410405d5 100644 --- a/src/components/contacts/FieldBlock.tsx +++ b/src/components/contacts/FieldBlock.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable } from 'react-native'; -import { Plus, X } from 'lucide-react-native'; +import { Plus, X } from '../icons'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { useLocaleStore } from '../../stores/locale-store'; diff --git a/src/components/contacts/TagAssignSheet.tsx b/src/components/contacts/TagAssignSheet.tsx index f98c2f2d..229146cb 100644 --- a/src/components/contacts/TagAssignSheet.tsx +++ b/src/components/contacts/TagAssignSheet.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ScrollView, Pressable, Modal, Animated, Easing, TextInput, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Tag, X, Plus } from 'lucide-react-native'; +import { Tag, X, Plus } from '../icons'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { useSheetDrag } from '../../lib/use-sheet-drag'; diff --git a/src/components/email/ActionSheet.tsx b/src/components/email/ActionSheet.tsx index d920632e..f99cae91 100644 --- a/src/components/email/ActionSheet.tsx +++ b/src/components/email/ActionSheet.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable, Modal, Animated, Easing } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { X } from 'lucide-react-native'; +import { X } from '../icons'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { useSheetDrag } from '../../lib/use-sheet-drag'; diff --git a/src/components/email/AddressActionSheet.tsx b/src/components/email/AddressActionSheet.tsx index 8d6fea56..5f9dd415 100644 --- a/src/components/email/AddressActionSheet.tsx +++ b/src/components/email/AddressActionSheet.tsx @@ -3,7 +3,7 @@ import { Alert } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import * as Clipboard from 'expo-clipboard'; -import { Copy, PenSquare, UserRound, UserRoundPlus } from 'lucide-react-native'; +import { Copy, PenSquare, UserRound, UserRoundPlus } from '../icons'; import type { EmailAddress } from '../../api/types'; import type { RootStackParamList } from '../../navigation/types'; import { useContactsStore } from '../../stores/contacts-store'; diff --git a/src/components/email/AttachmentChips.tsx b/src/components/email/AttachmentChips.tsx index b1cb6680..6d1609ea 100644 --- a/src/components/email/AttachmentChips.tsx +++ b/src/components/email/AttachmentChips.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable, ActivityIndicator, Alert } from 'react-native'; -import { Paperclip, Download, Eye, ExternalLink, Save, Share2, FolderArchive } from 'lucide-react-native'; +import { Paperclip, Download, Eye, ExternalLink, Save, Share2, FolderArchive } from '../icons'; import type { File } from 'expo-file-system'; import type { Attachment, Email } from '../../api/types'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/email/AttachmentPreviewModal.tsx b/src/components/email/AttachmentPreviewModal.tsx index 257b103d..2e3aa9a7 100644 --- a/src/components/email/AttachmentPreviewModal.tsx +++ b/src/components/email/AttachmentPreviewModal.tsx @@ -4,7 +4,7 @@ import { } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { WebView } from 'react-native-webview'; -import { X, ExternalLink, Share2, Download } from 'lucide-react-native'; +import { X, ExternalLink, Share2, Download } from '../icons'; import type { Email } from '../../api/types'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/email/CalendarInvitationBanner.tsx b/src/components/email/CalendarInvitationBanner.tsx index 3534354c..2a90a744 100644 --- a/src/components/email/CalendarInvitationBanner.tsx +++ b/src/components/email/CalendarInvitationBanner.tsx @@ -4,7 +4,7 @@ import { openExternalUrl } from '../../lib/open-url'; import { CalendarPlus, Check, HelpCircle, X, MapPin, Video, Clock, CalendarDays, AlertTriangle, ShieldCheck, ShieldAlert, ChevronDown, -} from 'lucide-react-native'; +} from '../icons'; import { format, parseISO } from 'date-fns'; import type { Calendar, Email, CalendarEvent } from '../../api/types'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/email/ListAttachmentChips.tsx b/src/components/email/ListAttachmentChips.tsx index 9e28a425..5d44f261 100644 --- a/src/components/email/ListAttachmentChips.tsx +++ b/src/components/email/ListAttachmentChips.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Alert, Pressable, StyleSheet, Text, View } from 'react-native'; -import { File as FileIcon, FileArchive, FileImage, FileSpreadsheet, FileText } from 'lucide-react-native'; +import { File as FileIcon, FileArchive, FileImage, FileSpreadsheet, FileText } from '../icons'; import type { Attachment, Email, Mailbox } from '../../api/types'; import { getEmailAttachments } from '../../api/email'; import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/email/MessageHeader.tsx b/src/components/email/MessageHeader.tsx index b8e4ce3a..3d8bca3a 100644 --- a/src/components/email/MessageHeader.tsx +++ b/src/components/email/MessageHeader.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable } from 'react-native'; import { Star, ChevronDown, ChevronUp, Reply, Forward, ShieldCheck, ShieldAlert, ShieldQuestion, Lock, -} from 'lucide-react-native'; +} from '../icons'; import type { Email, EmailAddress, Identity } from '../../api/types'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/email/QuickReplyBox.tsx b/src/components/email/QuickReplyBox.tsx index fd147cb7..2dd0593d 100644 --- a/src/components/email/QuickReplyBox.tsx +++ b/src/components/email/QuickReplyBox.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet, TextInput, Pressable, ActivityIndicator, Alert, Keyboard } from 'react-native'; -import { Send, Maximize2 } from 'lucide-react-native'; +import { Send, Maximize2 } from '../icons'; import type { Email } from '../../api/types'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/email/ReadReceiptBanner.tsx b/src/components/email/ReadReceiptBanner.tsx index 6451fb75..9a0d5ce7 100644 --- a/src/components/email/ReadReceiptBanner.tsx +++ b/src/components/email/ReadReceiptBanner.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable, ActivityIndicator, Alert } from 'react-native'; -import { MailCheck } from 'lucide-react-native'; +import { MailCheck } from '../icons'; import type { Email } from '../../api/types'; import { spacing, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/email/ThreadMessageCard.tsx b/src/components/email/ThreadMessageCard.tsx index 01b4bab0..b7718f36 100644 --- a/src/components/email/ThreadMessageCard.tsx +++ b/src/components/email/ThreadMessageCard.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable } from 'react-native'; -import { Paperclip, Reply, ReplyAll, Forward, Star } from 'lucide-react-native'; +import { Paperclip, Reply, ReplyAll, Forward, Star } from '../icons'; import type { Email } from '../../api/types'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/email/UnsubscribeBanner.tsx b/src/components/email/UnsubscribeBanner.tsx index 473c23eb..4796b541 100644 --- a/src/components/email/UnsubscribeBanner.tsx +++ b/src/components/email/UnsubscribeBanner.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { View, Text, StyleSheet, Pressable, ActivityIndicator, Alert } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import * as WebBrowser from 'expo-web-browser'; -import { MailMinus, CheckCircle2, AlertCircle, X } from 'lucide-react-native'; +import { MailMinus, CheckCircle2, AlertCircle, X } from '../icons'; import type { Email } from '../../api/types'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/files/ShareSheet.tsx b/src/components/files/ShareSheet.tsx index 8ef24586..c11d01e1 100644 --- a/src/components/files/ShareSheet.tsx +++ b/src/components/files/ShareSheet.tsx @@ -11,7 +11,7 @@ import { TouchableWithoutFeedback, View, } from 'react-native'; -import { Folder, FileText, Trash2, UserPlus, Users } from 'lucide-react-native'; +import { Folder, FileText, Trash2, UserPlus, Users } from '../icons'; import { getPrincipals, isFolder, ownPrincipalId, setFileNodeShare } from '../../api/files'; import type { FileNode, FileNodeRights, Principal } from '../../api/types'; diff --git a/src/components/filters/FilterRuleModal.tsx b/src/components/filters/FilterRuleModal.tsx index 524fad68..ffe96e30 100644 --- a/src/components/filters/FilterRuleModal.tsx +++ b/src/components/filters/FilterRuleModal.tsx @@ -4,7 +4,7 @@ import { KeyboardAvoidingView, Platform, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { X, Plus, Trash2 } from 'lucide-react-native'; +import { X, Plus, Trash2 } from '../icons'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { Select, ToggleSwitch } from '../settings/settings-section'; diff --git a/src/components/filters/SieveEditorSheet.tsx b/src/components/filters/SieveEditorSheet.tsx index 0793ec14..116610a3 100644 --- a/src/components/filters/SieveEditorSheet.tsx +++ b/src/components/filters/SieveEditorSheet.tsx @@ -4,7 +4,7 @@ import { KeyboardAvoidingView, Platform, ActivityIndicator, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { X, AlertTriangle, CheckCircle } from 'lucide-react-native'; +import { X, AlertTriangle, CheckCircle } from '../icons'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import Button from '../Button'; diff --git a/src/components/icons/__tests__/icons.test.ts b/src/components/icons/__tests__/icons.test.ts new file mode 100644 index 00000000..39cbcd9e --- /dev/null +++ b/src/components/icons/__tests__/icons.test.ts @@ -0,0 +1,74 @@ +// The app's icons are Tabler glyphs under the names the screens import. +// These tests make sure each name draws the glyph the webmail uses for it, +// and that every icon a screen imports exists. +import { describe, it, expect, vi } from 'vitest'; +import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; +import { join, relative } from 'node:path'; + +vi.mock('react-native-svg', () => ({ default: 'Svg', Path: 'Path' })); + +const ROOT = join(__dirname, '..', '..', '..', '..'); +const WEBMAIL_ICONS = join(ROOT, '..', '..', 'components', 'icons', 'index.ts'); +const TABLER = join(ROOT, '..', '..', 'node_modules', '@tabler', 'icons', 'tabler-nodes-outline.json'); + +function sourceFiles(dir: string, out: string[] = []): string[] { + for (const entry of readdirSync(dir)) { + const p = join(dir, entry); + if (statSync(p).isDirectory()) { + // The widgets draw their own SVG strings (src/widgets/icons.ts). + if (entry !== '__tests__' && entry !== 'node_modules' && entry !== 'widgets') sourceFiles(p, out); + } else if (/\.tsx?$/.test(entry)) { + out.push(p); + } + } + return out; +} + +/** Path data the generated module hands to an icon, read off its element. */ +function glyphOf(icon: unknown): string[] { + const memo = icon as { type: (p: object) => { props: { children: { props: { d: string } }[] } } }; + return memo.type({}).props.children.map((child) => child.props.d); +} + +describe('icons', () => { + it('exports every icon a screen imports', async () => { + const icons: Record = await import('..'); + const missing: string[] = []; + const files = [...sourceFiles(join(ROOT, 'src')), join(ROOT, 'App.tsx')]; + const re = /import\s+(?:type\s+)?\{([^}]*)\}\s+from\s+'[./]*(?:components\/)?icons'/g; + let checked = 0; + for (const file of files) { + for (const m of readFileSync(file, 'utf8').matchAll(re)) { + for (const raw of m[1].split(',')) { + const spec = raw.trim().replace(/^type\s+/, ''); + if (!spec) continue; + const name = spec.split(/\s+as\s+/)[0]; + if (name === 'AppIcon' || name === 'IconProps') continue; + checked++; + if (typeof icons[name] !== 'object') missing.push(`${relative(ROOT, file)}: ${name}`); + } + } + } + expect(missing).toEqual([]); + expect(checked).toBeGreaterThan(100); + }); + + it.skipIf(!existsSync(WEBMAIL_ICONS) || !existsSync(TABLER))('draws the glyph the webmail uses for the same name', async () => { + const icons: Record = await import('..'); + const tabler: Record = JSON.parse(readFileSync(TABLER, 'utf8')); + const webmail = readFileSync(WEBMAIL_ICONS, 'utf8'); + const kebab = (pascal: string) => pascal.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/([A-Za-z])(\d)/g, '$1-$2').toLowerCase(); + const differ: string[] = []; + let compared = 0; + for (const m of webmail.matchAll(/Icon(\w+) as (\w+),/g)) { + const [, tablerPascal, name] = m; + if (!(name in icons)) continue; + const expected = tabler[kebab(tablerPascal)]?.map(([, a]) => a.d); + if (!expected) continue; + compared++; + if (JSON.stringify(glyphOf(icons[name])) !== JSON.stringify(expected)) differ.push(`${name} (webmail: ${kebab(tablerPascal)})`); + } + expect(differ).toEqual([]); + expect(compared).toBeGreaterThan(100); + }); +}); diff --git a/src/components/icons/icon.tsx b/src/components/icons/icon.tsx new file mode 100644 index 00000000..c9c6d877 --- /dev/null +++ b/src/components/icons/icon.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import type { StyleProp, ViewStyle } from 'react-native'; +import Svg, { Path } from 'react-native-svg'; + +export interface IconProps { + size?: number; + color?: string; + /** Tabler draws at 2 on a 24 grid, like the webmail. */ + strokeWidth?: number; + /** Fills the outline, for a starred star or a coloured tag. */ + fill?: string; + style?: StyleProp; + testID?: string; +} + +export type AppIcon = React.ComponentType; + +/** An icon component for one Tabler outline glyph (its path data). */ +export function makeIcon(name: string, paths: readonly string[]): AppIcon { + if (!paths?.length) throw new Error(`Icon ${name} has no glyph`); + function Icon({ size = 24, color = '#000', strokeWidth = 2, fill = 'none', style, testID }: IconProps) { + return ( + + {paths.map((d, i) => )} + + ); + } + Icon.displayName = name; + return React.memo(Icon); +} diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts new file mode 100644 index 00000000..fba15194 --- /dev/null +++ b/src/components/icons/index.ts @@ -0,0 +1,357 @@ +// Generated by scripts/generate-icons.mjs from @tabler/icons (MIT). +// Do not edit by hand: add the icon to MAP in the script and run it. + +import { makeIcon } from './icon'; + +export type { AppIcon, IconProps } from './icon'; + +const G = { + 'address-book': ["M20 6v12a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2","M10 16h6","M11 11a2 2 0 1 0 4 0a2 2 0 1 0 -4 0","M4 8h3","M4 12h3","M4 16h3"], + 'alarm': ["M5 13a7 7 0 1 0 14 0a7 7 0 1 0 -14 0","M12 10l0 3l2 0","M7 4l-2.75 2","M17 4l2.75 2"], + 'alert-circle': ["M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0","M12 8v4","M12 16h.01"], + 'alert-triangle': ["M12 9v4","M10.363 3.591l-8.106 13.534a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636 -2.87l-8.106 -13.536a1.914 1.914 0 0 0 -3.274 0","M12 16h.01"], + 'align-center': ["M4 6l16 0","M8 12l8 0","M6 18l12 0"], + 'align-left': ["M4 6l16 0","M4 12l10 0","M4 18l14 0"], + 'align-right': ["M4 6l16 0","M10 12l10 0","M6 18l14 0"], + 'archive': ["M3 6a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2","M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10","M10 12l4 0"], + 'arrow-back': ["M9 11l-4 4l4 4m-4 -4h11a4 4 0 0 0 0 -8h-1"], + 'arrow-back-up': ["M9 14l-4 -4l4 -4","M5 10h11a4 4 0 1 1 0 8h-1"], + 'arrow-back-up-double': ["M13 14l-4 -4l4 -4","M8 14l-4 -4l4 -4","M9 10h7a4 4 0 1 1 0 8h-1"], + 'arrow-down': ["M12 5l0 14","M18 13l-6 6","M6 13l6 6"], + 'arrow-forward': ["M15 11l4 4l-4 4m4 -4h-11a4 4 0 0 1 0 -8h1"], + 'arrow-forward-up': ["M15 14l4 -4l-4 -4","M19 10h-11a4 4 0 1 0 0 8h1"], + 'arrow-left': ["M5 12l14 0","M5 12l6 6","M5 12l6 -6"], + 'arrow-right': ["M5 12l14 0","M13 18l6 -6","M13 6l6 6"], + 'arrow-up': ["M12 5l0 14","M18 11l-6 -6","M6 11l6 -6"], + 'arrows-shuffle': ["M18 4l3 3l-3 3","M18 20l3 -3l-3 -3","M3 7h3a5 5 0 0 1 5 5a5 5 0 0 0 5 5h5","M21 7h-5a4.978 4.978 0 0 0 -3 1m-4 8a4.984 4.984 0 0 1 -3 1h-3"], + 'arrows-sort': ["M3 9l4 -4l4 4m-4 -4v14","M21 15l-4 4l-4 -4m4 4v-14"], + 'arrows-vertical': ["M8 7l4 -4l4 4","M8 17l4 4l4 -4","M12 3l0 18"], + 'ban': ["M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0","M5.7 5.7l12.6 12.6"], + 'beach': ["M17.553 16.75a7.5 7.5 0 0 0 -10.606 0","M18 3.804a6 6 0 0 0 -8.196 2.196l10.392 6a6 6 0 0 0 -2.196 -8.196","M16.732 10c1.658 -2.87 2.225 -5.644 1.268 -6.196c-.957 -.552 -3.075 1.326 -4.732 4.196","M15 9l-3 5.196","M3 19.25a2.4 2.4 0 0 1 1 -.25a2.4 2.4 0 0 1 2 1a2.4 2.4 0 0 0 2 1a2.4 2.4 0 0 0 2 -1a2.4 2.4 0 0 1 2 -1a2.4 2.4 0 0 1 2 1a2.4 2.4 0 0 0 2 1a2.4 2.4 0 0 0 2 -1a2.4 2.4 0 0 1 2 -1a2.4 2.4 0 0 1 1 .25"], + 'bell': ["M10 5a2 2 0 1 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6","M9 17v1a3 3 0 0 0 6 0v-1"], + 'bell-ringing': ["M10 5a2 2 0 0 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6","M9 17v1a3 3 0 0 0 6 0v-1","M21 6.727a11.05 11.05 0 0 0 -2.794 -3.727","M3 6.727a11.05 11.05 0 0 1 2.792 -3.727"], + 'bold': ["M7 5h6a3.5 3.5 0 0 1 0 7h-6l0 -7","M13 12h1a3.5 3.5 0 0 1 0 7h-7v-7"], + 'book': ["M3 19a9 9 0 0 1 9 0a9 9 0 0 1 9 0","M3 6a9 9 0 0 1 9 0a9 9 0 0 1 9 0","M3 6l0 13","M12 6l0 13","M21 6l0 13"], + 'book-2': ["M19 4v16h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12","M19 16h-12a2 2 0 0 0 -2 2","M9 8h6"], + 'bug': ["M9 9v-1a3 3 0 0 1 6 0v1","M8 9h8a6 6 0 0 1 1 3v3a5 5 0 0 1 -10 0v-3a6 6 0 0 1 1 -3","M3 13l4 0","M17 13l4 0","M12 20l0 -6","M4 19l3.35 -2","M20 19l-3.35 -2","M4 7l3.75 2.4","M20 7l-3.75 2.4"], + 'building': ["M3 21l18 0","M9 8l1 0","M9 12l1 0","M9 16l1 0","M14 8l1 0","M14 12l1 0","M14 16l1 0","M5 21v-16a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v16"], + 'cake': ["M3 20h18v-8a3 3 0 0 0 -3 -3h-12a3 3 0 0 0 -3 3v8","M3 14.803c.312 .135 .654 .204 1 .197a2.4 2.4 0 0 0 2 -1a2.4 2.4 0 0 1 2 -1a2.4 2.4 0 0 1 2 1a2.4 2.4 0 0 0 2 1a2.4 2.4 0 0 0 2 -1a2.4 2.4 0 0 1 2 -1a2.4 2.4 0 0 1 2 1a2.4 2.4 0 0 0 2 1c.35 .007 .692 -.062 1 -.197","M12 4l1.465 1.638a2 2 0 1 1 -3.015 .099l1.55 -1.737"], + 'calendar': ["M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12","M16 3v4","M8 3v4","M4 11h16","M11 15h1","M12 15v3"], + 'calendar-month': ["M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12","M16 3v4","M8 3v4","M4 11h16","M8 14v4","M12 14v4","M16 14v4"], + 'calendar-plus': ["M12.5 21h-6.5a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v5","M16 3v4","M8 3v4","M4 11h16","M16 19h6","M19 16v6"], + 'camera': ["M5 7h1a2 2 0 0 0 2 -2a1 1 0 0 1 1 -1h6a1 1 0 0 1 1 1a2 2 0 0 0 2 2h1a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-9a2 2 0 0 1 2 -2","M9 13a3 3 0 1 0 6 0a3 3 0 0 0 -6 0"], + 'check': ["M5 12l5 5l10 -10"], + 'checks': ["M7 12l5 5l10 -10","M2 12l5 5m5 -5l5 -5"], + 'chevron-down': ["M6 9l6 6l6 -6"], + 'chevron-left': ["M15 6l-6 6l6 6"], + 'chevron-right': ["M9 6l6 6l-6 6"], + 'chevron-up': ["M6 15l6 -6l6 6"], + 'circle-check': ["M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0","M9 12l2 2l4 -4"], + 'circle-x': ["M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0","M10 10l4 4m0 -4l-4 4"], + 'clear-formatting': ["M17 15l4 4m0 -4l-4 4","M7 6v-1h11v1","M7 19l4 0","M13 5l-4 14"], + 'clipboard-text': ["M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2","M9 5a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2","M9 12h6","M9 16h6"], + 'clock': ["M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0","M12 7v5l3 3"], + 'cloud-download': ["M19 18a3.5 3.5 0 0 0 0 -7h-1a5 4.5 0 0 0 -11 -2a4.6 4.4 0 0 0 -2.1 8.4","M12 13l0 9","M9 19l3 3l3 -3"], + 'cloud-off': ["M9.58 5.548c.24 -.11 .492 -.207 .752 -.286c1.88 -.572 3.956 -.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486c0 .957 -.383 1.824 -1.003 2.454m-2.997 1.033h-11.343c-2.572 -.004 -4.657 -2.011 -4.657 -4.487c0 -2.475 2.085 -4.482 4.657 -4.482c.13 -.582 .37 -1.128 .7 -1.62","M3 3l18 18"], + 'code': ["M7 8l-4 4l4 4","M17 8l4 4l-4 4","M14 4l-4 16"], + 'columns-3': ["M3 4a1 1 0 0 1 1 -1h16a1 1 0 0 1 1 1v16a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1v-16","M9 3v18","M15 3v18"], + 'copy': ["M7 9.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667l0 -8.666","M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1"], + 'database': ["M4 6a8 3 0 1 0 16 0a8 3 0 1 0 -16 0","M4 6v6a8 3 0 0 0 16 0v-6","M4 12v6a8 3 0 0 0 16 0v-6"], + 'device-desktop': ["M3 5a1 1 0 0 1 1 -1h16a1 1 0 0 1 1 1v10a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1v-10","M7 20h10","M9 16v4","M15 16v4"], + 'device-floppy': ["M6 4h10l4 4v10a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2","M10 14a2 2 0 1 0 4 0a2 2 0 1 0 -4 0","M14 4l0 4l-6 0l0 -4"], + 'device-mobile': ["M6 5a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2v-14","M11 4h2","M12 17v.01"], + 'dots': ["M4 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M11 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M18 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"], + 'dots-vertical': ["M11 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M11 19a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M11 5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"], + 'download': ["M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2","M7 11l5 5l5 -5","M12 4l0 12"], + 'edit': ["M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1","M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415","M16 5l3 3"], + 'eraser': ["M19 20h-10.5l-4.21 -4.3a1 1 0 0 1 0 -1.41l10 -10a1 1 0 0 1 1.41 0l5 5a1 1 0 0 1 0 1.41l-9.2 9.3","M18 13.3l-6.3 -6.3"], + 'external-link': ["M12 6h-6a2 2 0 0 0 -2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-6","M11 13l9 -9","M15 4h5v5"], + 'eye': ["M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0","M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6"], + 'eye-off': ["M10.585 10.587a2 2 0 0 0 2.829 2.828","M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87","M3 3l18 18"], + 'file': ["M14 3v4a1 1 0 0 0 1 1h4","M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2"], + 'file-code': ["M14 3v4a1 1 0 0 0 1 1h4","M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2","M10 13l-1 2l1 2","M14 13l1 2l-1 2"], + 'file-code-2': ["M10 12h-1v5h1","M14 12h1v5h-1","M14 3v4a1 1 0 0 0 1 1h4","M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2"], + 'file-import': ["M14 3v4a1 1 0 0 0 1 1h4","M5 13v-8a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2h-5.5m-9.5 -2h7m-3 -3l3 3l-3 3"], + 'file-music': ["M14 3v4a1 1 0 0 0 1 1h4","M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2","M10 16a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M12 16l0 -5l2 1"], + 'file-spreadsheet': ["M14 3v4a1 1 0 0 0 1 1h4","M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2","M8 11h8v7h-8l0 -7","M8 15h8","M11 11v7"], + 'file-text': ["M14 3v4a1 1 0 0 0 1 1h4","M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2","M9 9l1 0","M9 13l6 0","M9 17l6 0"], + 'file-zip': ["M6 20.735a2 2 0 0 1 -1 -1.735v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2h-1","M11 17a2 2 0 0 1 2 2v2a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1v-2a2 2 0 0 1 2 -2","M11 5l-1 0","M13 7l-1 0","M11 9l-1 0","M13 11l-1 0","M11 13l-1 0","M13 15l-1 0"], + 'filter': ["M4 4h16v2.172a2 2 0 0 1 -.586 1.414l-4.414 4.414v7l-6 2v-8.5l-4.48 -4.928a2 2 0 0 1 -.52 -1.345v-2.227"], + 'flag': ["M5 5a5 5 0 0 1 7 0a5 5 0 0 0 7 0v9a5 5 0 0 1 -7 0a5 5 0 0 0 -7 0v-9","M5 21v-7"], + 'flask': ["M9 3l6 0","M10 9l4 0","M10 3v6l-4 11a.7 .7 0 0 0 .5 1h11a.7 .7 0 0 0 .5 -1l-4 -11v-6"], + 'folder': ["M5 4h4l3 3h7a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2"], + 'folder-down': ["M12 19h-7a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v3.5","M19 16v6","M22 19l-3 3l-3 -3"], + 'folder-open': ["M5 19l2.757 -7.351a1 1 0 0 1 .936 -.649h12.307a1 1 0 0 1 .986 1.164l-.996 5.211a2 2 0 0 1 -1.964 1.625h-14.026a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v2"], + 'folder-plus': ["M12 19h-7a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2h4l3 3h7a2 2 0 0 1 2 2v3.5","M16 19h6","M19 16v6"], + 'folders': ["M9 3h3l2 2h5a2 2 0 0 1 2 2v7a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-9a2 2 0 0 1 2 -2","M17 16v2a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-9a2 2 0 0 1 2 -2h2"], + 'grid-dots': ["M4 5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M11 5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M18 5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M4 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M11 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M18 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M4 19a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M11 19a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M18 19a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"], + 'grip-vertical': ["M8 5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M8 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M8 19a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M14 5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M14 12a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M14 19a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"], + 'h-1': ["M19 18v-8l-2 2","M4 6v12","M12 6v12","M11 18h2","M3 18h2","M4 12h8","M3 6h2","M11 6h2"], + 'h-2': ["M17 12a2 2 0 1 1 4 0c0 .591 -.417 1.318 -.816 1.858l-3.184 4.143l4 0","M4 6v12","M12 6v12","M11 18h2","M3 18h2","M4 12h8","M3 6h2","M11 6h2"], + 'heart': ["M19.5 12.572l-7.5 7.428l-7.5 -7.428a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572"], + 'help-circle': ["M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0","M12 16v.01","M12 13a2 2 0 0 0 .914 -3.782a1.98 1.98 0 0 0 -2.414 .483"], + 'highlight': ["M3 19h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4","M12.5 5.5l4 4","M4.5 13.5l4 4","M21 15v4h-8l4 -4l4 0"], + 'inbox': ["M4 6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2l0 -12","M4 13h3l3 3h4l3 -3h3"], + 'info-circle': ["M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0","M12 9h.01","M11 12h1v4h1"], + 'italic': ["M11 5l6 0","M7 19l6 0","M14 5l-4 14"], + 'key': ["M16.555 3.843l3.602 3.602a2.877 2.877 0 0 1 0 4.069l-2.643 2.643a2.877 2.877 0 0 1 -4.069 0l-.301 -.301l-6.558 6.558a2 2 0 0 1 -1.239 .578l-.175 .008h-1.172a1 1 0 0 1 -.993 -.883l-.007 -.117v-1.172a2 2 0 0 1 .467 -1.284l.119 -.13l.414 -.414h2v-2h2v-2l2.144 -2.144l-.301 -.301a2.877 2.877 0 0 1 0 -4.069l2.643 -2.643a2.877 2.877 0 0 1 4.069 0","M15 9h.01"], + 'language': ["M9 6.371c0 4.418 -2.239 6.629 -5 6.629","M4 6.371h7","M5 9c0 2.144 2.252 3.908 6 4","M12 20l4 -9l4 9","M19.1 18h-6.2","M6.694 3l.793 .582"], + 'layout-columns': ["M4 6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2l0 -12","M12 4l0 16"], + 'layout-grid': ["M4 5a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4","M14 5a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4","M4 15a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4","M14 15a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4"], + 'layout-rows': ["M4 6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2l0 -12","M4 12l16 0"], + 'layout-sidebar-left-collapse': ["M4 6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2l0 -12","M9 4v16","M15 10l-2 2l2 2"], + 'layout-sidebar-right': ["M4 6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2l0 -12","M15 4l0 16"], + 'link': ["M9 15l6 -6","M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464","M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463"], + 'list': ["M9 6l11 0","M9 12l11 0","M9 18l11 0","M5 6l0 .01","M5 12l0 .01","M5 18l0 .01"], + 'list-check': ["M3.5 5.5l1.5 1.5l2.5 -2.5","M3.5 11.5l1.5 1.5l2.5 -2.5","M3.5 17.5l1.5 1.5l2.5 -2.5","M11 6l9 0","M11 12l9 0","M11 18l9 0"], + 'list-numbers': ["M11 6h9","M11 12h9","M12 18h8","M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4","M6 10v-6l-2 2"], + 'lock': ["M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6","M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0","M8 11v-4a4 4 0 1 1 8 0v4"], + 'logout': ["M14 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2","M9 12h12l-3 -3","M18 15l3 -3"], + 'mail': ["M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10","M3 7l9 6l9 -6"], + 'mail-check': ["M11 19h-6a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v6","M3 7l9 6l9 -6","M15 19l2 2l4 -4"], + 'mail-minus': ["M12 19h-7a2 2 0 0 1 -2 -2v-10a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v8","M16 19h6","M3 7l9 6l9 -6"], + 'mail-opened': ["M3 9l9 6l9 -6l-9 -6l-9 6","M21 9v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10","M3 19l6 -6","M15 13l6 6"], + 'map-pin': ["M9 11a3 3 0 1 0 6 0a3 3 0 0 0 -6 0","M17.657 16.657l-4.243 4.243a2 2 0 0 1 -2.827 0l-4.244 -4.243a8 8 0 1 1 11.314 0"], + 'maximize': ["M4 8v-2a2 2 0 0 1 2 -2h2","M4 16v2a2 2 0 0 0 2 2h2","M16 4h2a2 2 0 0 1 2 2v2","M16 20h2a2 2 0 0 0 2 -2v-2"], + 'menu-2': ["M4 6l16 0","M4 12l16 0","M4 18l16 0"], + 'message': ["M8 9h8","M8 13h6","M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"], + 'minus': ["M5 12l14 0"], + 'moon': ["M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454l0 .008"], + 'note': ["M13 20l7 -7","M13 20v-6a1 1 0 0 1 1 -1h6v-7a2 2 0 0 0 -2 -2h-12a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7"], + 'palette': ["M12 21a9 9 0 0 1 0 -18c4.97 0 9 3.582 9 8c0 1.06 -.474 2.078 -1.318 2.828c-.844 .75 -1.989 1.172 -3.182 1.172h-2.5a2 2 0 0 0 -1 3.75a1.3 1.3 0 0 1 -1 2.25","M7.5 10.5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M11.5 7.5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M15.5 10.5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"], + 'paperclip': ["M15 7l-6.5 6.5a1.5 1.5 0 0 0 3 3l6.5 -6.5a3 3 0 0 0 -6 -6l-6.5 6.5a4.5 4.5 0 0 0 9 9l6.5 -6.5"], + 'pencil': ["M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4","M13.5 6.5l4 4"], + 'phone': ["M5 4h4l2 5l-2.5 1.5a11 11 0 0 0 5 5l1.5 -2.5l5 2v4a2 2 0 0 1 -2 2a16 16 0 0 1 -15 -15a2 2 0 0 1 2 -2"], + 'photo': ["M15 8h.01","M3 6a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-12","M3 16l5 -5c.928 -.893 2.072 -.893 3 0l5 5","M14 14l1 -1c.928 -.893 2.072 -.893 3 0l3 3"], + 'pinned': ["M9 4v6l-2 4v2h10v-2l-2 -4v-6","M12 16l0 5","M8 4l8 0"], + 'pinned-off': ["M3 3l18 18","M15 4.5l-3.249 3.249m-2.57 1.433l-2.181 .818l-1.5 1.5l7 7l1.5 -1.5l.82 -2.186m1.43 -2.563l3.25 -3.251","M9 15l-4.5 4.5","M14.5 4l5.5 5.5"], + 'plus': ["M12 5l0 14","M5 12l14 0"], + 'puzzle': ["M4 7h3a1 1 0 0 0 1 -1v-1a2 2 0 0 1 4 0v1a1 1 0 0 0 1 1h3a1 1 0 0 1 1 1v3a1 1 0 0 0 1 1h1a2 2 0 0 1 0 4h-1a1 1 0 0 0 -1 1v3a1 1 0 0 1 -1 1h-3a1 1 0 0 1 -1 -1v-1a2 2 0 0 0 -4 0v1a1 1 0 0 1 -1 1h-3a1 1 0 0 1 -1 -1v-3a1 1 0 0 1 1 -1h1a2 2 0 0 0 0 -4h-1a1 1 0 0 1 -1 -1v-3a1 1 0 0 1 1 -1"], + 'qrcode': ["M4 5a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4","M7 17l0 .01","M14 5a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4","M7 7l0 .01","M4 15a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4","M17 7l0 .01","M14 14l3 0","M20 14l0 .01","M14 14l0 3","M14 20l3 0","M17 17l3 0","M20 17l0 3"], + 'quote': ["M10 11h-4a1 1 0 0 1 -1 -1v-3a1 1 0 0 1 1 -1h3a1 1 0 0 1 1 1v6c0 2.667 -1.333 4.333 -4 5","M19 11h-4a1 1 0 0 1 -1 -1v-3a1 1 0 0 1 1 -1h3a1 1 0 0 1 1 1v6c0 2.667 -1.333 4.333 -4 5"], + 'refresh': ["M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4","M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"], + 'repeat': ["M4 12v-3a3 3 0 0 1 3 -3h13m-3 -3l3 3l-3 3","M20 12v3a3 3 0 0 1 -3 3h-13m3 3l-3 -3l3 -3"], + 'rotate': ["M19.95 11a8 8 0 1 0 -.5 4m.5 5v-5h-5"], + 'rss': ["M4 19a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M4 4a16 16 0 0 1 16 16","M4 11a9 9 0 0 1 9 9"], + 'scale': ["M7 20l10 0","M6 6l6 -1l6 1","M12 3l0 17","M9 12l-3 -6l-3 6a3 3 0 0 0 6 0","M21 12l-3 -6l-3 6a3 3 0 0 0 6 0"], + 'search': ["M3 10a7 7 0 1 0 14 0a7 7 0 1 0 -14 0","M21 21l-6 -6"], + 'send': ["M10 14l11 -11","M21 3l-6.5 18a.55 .55 0 0 1 -1 0l-3.5 -7l-7 -3.5a.55 .55 0 0 1 0 -1l18 -6.5"], + 'server': ["M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-2","M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2","M7 8l0 .01","M7 16l0 .01"], + 'server-2': ["M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-2","M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2","M7 8l0 .01","M7 16l0 .01","M11 8h6","M11 16h6"], + 'settings': ["M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065","M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0"], + 'share-2': ["M8 9h-1a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-8a2 2 0 0 0 -2 -2h-1","M12 14v-11","M9 6l3 -3l3 3"], + 'shield': ["M12 3a12 12 0 0 0 8.5 3a12 12 0 0 1 -8.5 15a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3"], + 'shield-check': ["M11.46 20.846a12 12 0 0 1 -7.96 -14.846a12 12 0 0 0 8.5 -3a12 12 0 0 0 8.5 3a12 12 0 0 1 -.09 7.06","M15 19l2 2l4 -4"], + 'shield-exclamation': ["M15.04 19.745c-.942 .551 -1.964 .976 -3.04 1.255a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3a12 12 0 0 0 8.5 3a12 12 0 0 1 .195 6.015","M19 16v3","M19 22v.01"], + 'shield-question': ["M15.065 19.732c-.95 .557 -1.98 .986 -3.065 1.268a12 12 0 0 1 -8.5 -15a12 12 0 0 0 8.5 -3a12 12 0 0 0 8.5 3c.51 1.738 .617 3.55 .333 5.303","M19 22v.01","M19 19a2.003 2.003 0 0 0 .914 -3.782a1.98 1.98 0 0 0 -2.414 .483"], + 'sort-ascending': ["M4 6l7 0","M4 12l7 0","M4 18l9 0","M15 9l3 -3l3 3","M18 6l0 12"], + 'sort-descending': ["M4 6l9 0","M4 12l7 0","M4 18l7 0","M15 15l3 3l3 -3","M18 6l0 12"], + 'square': ["M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14"], + 'square-check': ["M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14","M9 12l2 2l4 -4"], + 'stack': ["M12 6l-8 4l8 4l8 -4l-8 -4","M4 14l8 4l8 -4"], + 'star': ["M12 17.75l-6.172 3.245l1.179 -6.873l-5 -4.867l6.9 -1l3.086 -6.253l3.086 6.253l6.9 1l-5 4.867l1.179 6.873l-6.158 -3.245"], + 'strikethrough': ["M5 12l14 0","M16 6.5a4 2 0 0 0 -4 -1.5h-1a3.5 3.5 0 0 0 0 7h2a3.5 3.5 0 0 1 0 7h-1.5a4 2 0 0 1 -4 -1.5"], + 'sun': ["M8 12a4 4 0 1 0 8 0a4 4 0 1 0 -8 0","M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7"], + 'table': ["M3 5a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-14","M3 10h18","M10 3v18"], + 'tag': ["M6.5 7.5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0","M3 6v5.172a2 2 0 0 0 .586 1.414l7.71 7.71a2.41 2.41 0 0 0 3.408 0l5.592 -5.592a2.41 2.41 0 0 0 0 -3.408l-7.71 -7.71a2 2 0 0 0 -1.414 -.586h-5.172a3 3 0 0 0 -3 3"], + 'tags': ["M3 8v4.172a2 2 0 0 0 .586 1.414l5.71 5.71a2.41 2.41 0 0 0 3.408 0l3.592 -3.592a2.41 2.41 0 0 0 0 -3.408l-5.71 -5.71a2 2 0 0 0 -1.414 -.586h-4.172a2 2 0 0 0 -2 2","M18 19l1.592 -1.592a4.82 4.82 0 0 0 0 -6.816l-4.592 -4.592","M7 10h-.01"], + 'template': ["M4 5a1 1 0 0 1 1 -1h14a1 1 0 0 1 1 1v2a1 1 0 0 1 -1 1h-14a1 1 0 0 1 -1 -1l0 -2","M4 13a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v6a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -6","M14 12l6 0","M14 16l6 0","M14 20l6 0"], + 'terminal': ["M5 7l5 5l-5 5","M12 19l7 0"], + 'trash': ["M4 7l16 0","M10 11l0 6","M14 11l0 6","M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12","M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"], + 'typography': ["M4 20l3 0","M14 20l7 0","M6.9 15l6.9 0","M10.2 6.3l5.8 13.7","M5 20l6 -16l2 0l7 16"], + 'underline': ["M7 5v5a5 5 0 0 0 10 0v-5","M5 19h14"], + 'unlink': ["M17 22v-2","M9 15l6 -6","M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464","M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463","M20 17h2","M2 7h2","M7 2v2"], + 'upload': ["M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2","M7 9l5 -5l5 5","M12 4l0 12"], + 'user': ["M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0","M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"], + 'user-circle': ["M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0","M9 10a3 3 0 1 0 6 0a3 3 0 1 0 -6 0","M6.168 18.849a4 4 0 0 1 3.832 -2.849h4a4 4 0 0 1 3.834 2.855"], + 'user-edit': ["M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0","M6 21v-2a4 4 0 0 1 4 -4h3.5","M18.42 15.61a2.1 2.1 0 0 1 2.97 2.97l-3.39 3.42h-3v-3l3.42 -3.39"], + 'user-minus': ["M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0","M6 21v-2a4 4 0 0 1 4 -4h4c.348 0 .686 .045 1.009 .128","M16 19h6"], + 'user-plus': ["M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0","M16 19h6","M19 16v6","M6 21v-2a4 4 0 0 1 4 -4h4"], + 'users': ["M5 7a4 4 0 1 0 8 0a4 4 0 1 0 -8 0","M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2","M16 3.13a4 4 0 0 1 0 7.75","M21 21v-2a4 4 0 0 0 -3 -3.85"], + 'video': ["M15 10l4.553 -2.276a1 1 0 0 1 1.447 .894v6.764a1 1 0 0 1 -1.447 .894l-4.553 -2.276v-4","M3 8a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2l0 -8"], + 'world': ["M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0","M3.6 9h16.8","M3.6 15h16.8","M11.5 3a17 17 0 0 0 0 18","M12.5 3a17 17 0 0 1 0 18"], + 'x': ["M18 6l-12 12","M6 6l12 12"], + 'zoom-scan': ["M8 11a3 3 0 1 0 6 0a3 3 0 0 0 -6 0","M16 16l-2.5 -2.5","M3 7v-2a2 2 0 0 1 2 -2h2","M3 17v2a2 2 0 0 0 2 2h2","M17 3h2a2 2 0 0 1 2 2v2","M17 21h2a2 2 0 0 0 2 -2v-2"], +} as const; + +export const AlarmClock = makeIcon('AlarmClock', G['alarm']); +export const AlertCircle = makeIcon('AlertCircle', G['alert-circle']); +export const AlertTriangle = makeIcon('AlertTriangle', G['alert-triangle']); +export const AlignCenter = makeIcon('AlignCenter', G['align-center']); +export const AlignLeft = makeIcon('AlignLeft', G['align-left']); +export const AlignRight = makeIcon('AlignRight', G['align-right']); +export const Archive = makeIcon('Archive', G['archive']); +export const ArrowDown = makeIcon('ArrowDown', G['arrow-down']); +export const ArrowDownWideNarrow = makeIcon('ArrowDownWideNarrow', G['sort-descending']); +export const ArrowLeft = makeIcon('ArrowLeft', G['arrow-left']); +export const ArrowRight = makeIcon('ArrowRight', G['arrow-right']); +export const ArrowUp = makeIcon('ArrowUp', G['arrow-up']); +export const ArrowUpDown = makeIcon('ArrowUpDown', G['arrows-sort']); +export const ArrowUpNarrowWide = makeIcon('ArrowUpNarrowWide', G['sort-ascending']); +export const Ban = makeIcon('Ban', G['ban']); +export const Bell = makeIcon('Bell', G['bell']); +export const BellRing = makeIcon('BellRing', G['bell-ringing']); +export const Bold = makeIcon('Bold', G['bold']); +export const Book = makeIcon('Book', G['book-2']); +export const BookOpen = makeIcon('BookOpen', G['book']); +export const BookUser = makeIcon('BookUser', G['address-book']); +export const Bug = makeIcon('Bug', G['bug']); +export const Building = makeIcon('Building', G['building']); +export const Cake = makeIcon('Cake', G['cake']); +export const Calendar = makeIcon('Calendar', G['calendar']); +export const CalendarDays = makeIcon('CalendarDays', G['calendar-month']); +export const CalendarPlus = makeIcon('CalendarPlus', G['calendar-plus']); +export const Camera = makeIcon('Camera', G['camera']); +export const Check = makeIcon('Check', G['check']); +export const CheckCheck = makeIcon('CheckCheck', G['checks']); +export const CheckCircle = makeIcon('CheckCircle', G['circle-check']); +export const CheckCircle2 = makeIcon('CheckCircle2', G['circle-check']); +export const CheckSquare = makeIcon('CheckSquare', G['square-check']); +export const ChevronDown = makeIcon('ChevronDown', G['chevron-down']); +export const ChevronLeft = makeIcon('ChevronLeft', G['chevron-left']); +export const ChevronRight = makeIcon('ChevronRight', G['chevron-right']); +export const ChevronUp = makeIcon('ChevronUp', G['chevron-up']); +export const CircleCheck = makeIcon('CircleCheck', G['circle-check']); +export const ClipboardPaste = makeIcon('ClipboardPaste', G['clipboard-text']); +export const Clock = makeIcon('Clock', G['clock']); +export const CloudDownload = makeIcon('CloudDownload', G['cloud-download']); +export const CloudOff = makeIcon('CloudOff', G['cloud-off']); +export const Code = makeIcon('Code', G['code']); +export const Columns3 = makeIcon('Columns3', G['columns-3']); +export const Columns4 = makeIcon('Columns4', G['layout-columns']); +export const Copy = makeIcon('Copy', G['copy']); +export const Database = makeIcon('Database', G['database']); +export const Download = makeIcon('Download', G['download']); +export const Edit2 = makeIcon('Edit2', G['pencil']); +export const Eraser = makeIcon('Eraser', G['eraser']); +export const ExternalLink = makeIcon('ExternalLink', G['external-link']); +export const Eye = makeIcon('Eye', G['eye']); +export const EyeOff = makeIcon('EyeOff', G['eye-off']); +export const File = makeIcon('File', G['file']); +export const FileArchive = makeIcon('FileArchive', G['file-zip']); +export const FileAudio = makeIcon('FileAudio', G['file-music']); +export const FileCode = makeIcon('FileCode', G['file-code']); +export const FileCode2 = makeIcon('FileCode2', G['file-code-2']); +export const FileImage = makeIcon('FileImage', G['photo']); +export const FileInput = makeIcon('FileInput', G['file-import']); +export const FileSpreadsheet = makeIcon('FileSpreadsheet', G['file-spreadsheet']); +export const FileText = makeIcon('FileText', G['file-text']); +export const FileVideo = makeIcon('FileVideo', G['video']); +export const Filter = makeIcon('Filter', G['filter']); +export const Flag = makeIcon('Flag', G['flag']); +export const FlaskConical = makeIcon('FlaskConical', G['flask']); +export const Folder = makeIcon('Folder', G['folder']); +export const FolderArchive = makeIcon('FolderArchive', G['archive']); +export const FolderInput = makeIcon('FolderInput', G['folder-down']); +export const FolderOpen = makeIcon('FolderOpen', G['folder-open']); +export const FolderPlus = makeIcon('FolderPlus', G['folder-plus']); +export const FolderSync = makeIcon('FolderSync', G['folders']); +export const Forward = makeIcon('Forward', G['arrow-forward-up']); +export const Globe = makeIcon('Globe', G['world']); +export const Grid3x3 = makeIcon('Grid3x3', G['grid-dots']); +export const GripVertical = makeIcon('GripVertical', G['grip-vertical']); +export const HardDrive = makeIcon('HardDrive', G['server-2']); +export const Heading1 = makeIcon('Heading1', G['h-1']); +export const Heading2 = makeIcon('Heading2', G['h-2']); +export const Heart = makeIcon('Heart', G['heart']); +export const HelpCircle = makeIcon('HelpCircle', G['help-circle']); +export const Highlighter = makeIcon('Highlighter', G['highlight']); +export const Image = makeIcon('Image', G['photo']); +export const Import = makeIcon('Import', G['file-import']); +export const Inbox = makeIcon('Inbox', G['inbox']); +export const Info = makeIcon('Info', G['info-circle']); +export const Italic = makeIcon('Italic', G['italic']); +export const Key = makeIcon('Key', G['key']); +export const KeyRound = makeIcon('KeyRound', G['key']); +export const Languages = makeIcon('Languages', G['language']); +export const Layers = makeIcon('Layers', G['stack']); +export const LayoutGrid = makeIcon('LayoutGrid', G['layout-grid']); +export const LayoutTemplate = makeIcon('LayoutTemplate', G['template']); +export const Link2 = makeIcon('Link2', G['link']); +export const Link2Off = makeIcon('Link2Off', G['unlink']); +export const List = makeIcon('List', G['list']); +export const ListChecks = makeIcon('ListChecks', G['list-check']); +export const ListOrdered = makeIcon('ListOrdered', G['list-numbers']); +export const Lock = makeIcon('Lock', G['lock']); +export const LogOut = makeIcon('LogOut', G['logout']); +export const Mail = makeIcon('Mail', G['mail']); +export const MailCheck = makeIcon('MailCheck', G['mail-check']); +export const MailMinus = makeIcon('MailMinus', G['mail-minus']); +export const MailOpen = makeIcon('MailOpen', G['mail-opened']); +export const Mails = makeIcon('Mails', G['inbox']); +export const MapPin = makeIcon('MapPin', G['map-pin']); +export const Maximize2 = makeIcon('Maximize2', G['maximize']); +export const Menu = makeIcon('Menu', G['menu-2']); +export const MessageSquare = makeIcon('MessageSquare', G['message']); +export const Minus = makeIcon('Minus', G['minus']); +export const Monitor = makeIcon('Monitor', G['device-desktop']); +export const Moon = makeIcon('Moon', G['moon']); +export const MoreHorizontal = makeIcon('MoreHorizontal', G['dots']); +export const MoreVertical = makeIcon('MoreVertical', G['dots-vertical']); +export const MoveVertical = makeIcon('MoveVertical', G['arrows-vertical']); +export const Palette = makeIcon('Palette', G['palette']); +export const Palmtree = makeIcon('Palmtree', G['beach']); +export const PanelLeftClose = makeIcon('PanelLeftClose', G['layout-sidebar-left-collapse']); +export const PanelRight = makeIcon('PanelRight', G['layout-sidebar-right']); +export const Paperclip = makeIcon('Paperclip', G['paperclip']); +export const PenLine = makeIcon('PenLine', G['pencil']); +export const PenSquare = makeIcon('PenSquare', G['edit']); +export const Pencil = makeIcon('Pencil', G['pencil']); +export const Phone = makeIcon('Phone', G['phone']); +export const Pin = makeIcon('Pin', G['pinned']); +export const PinOff = makeIcon('PinOff', G['pinned-off']); +export const Plus = makeIcon('Plus', G['plus']); +export const Puzzle = makeIcon('Puzzle', G['puzzle']); +export const QrCode = makeIcon('QrCode', G['qrcode']); +export const Quote = makeIcon('Quote', G['quote']); +export const Redo2 = makeIcon('Redo2', G['arrow-forward']); +export const RefreshCw = makeIcon('RefreshCw', G['refresh']); +export const RemoveFormatting = makeIcon('RemoveFormatting', G['clear-formatting']); +export const Repeat = makeIcon('Repeat', G['repeat']); +export const Reply = makeIcon('Reply', G['arrow-back-up']); +export const ReplyAll = makeIcon('ReplyAll', G['arrow-back-up-double']); +export const RotateCcw = makeIcon('RotateCcw', G['rotate']); +export const Rows2 = makeIcon('Rows2', G['layout-rows']); +export const Rss = makeIcon('Rss', G['rss']); +export const Save = makeIcon('Save', G['device-floppy']); +export const ScanSearch = makeIcon('ScanSearch', G['zoom-scan']); +export const Scale = makeIcon('Scale', G['scale']); +export const Search = makeIcon('Search', G['search']); +export const Send = makeIcon('Send', G['send']); +export const Server = makeIcon('Server', G['server']); +export const Settings = makeIcon('Settings', G['settings']); +export const Share2 = makeIcon('Share2', G['share-2']); +export const Shield = makeIcon('Shield', G['shield']); +export const ShieldAlert = makeIcon('ShieldAlert', G['shield-exclamation']); +export const ShieldCheck = makeIcon('ShieldCheck', G['shield-check']); +export const ShieldQuestion = makeIcon('ShieldQuestion', G['shield-question']); +export const Shuffle = makeIcon('Shuffle', G['arrows-shuffle']); +export const Smartphone = makeIcon('Smartphone', G['device-mobile']); +export const Square = makeIcon('Square', G['square']); +export const SquareCheck = makeIcon('SquareCheck', G['square-check']); +export const SquarePen = makeIcon('SquarePen', G['edit']); +export const Star = makeIcon('Star', G['star']); +export const StickyNote = makeIcon('StickyNote', G['note']); +export const Strikethrough = makeIcon('Strikethrough', G['strikethrough']); +export const Sun = makeIcon('Sun', G['sun']); +export const Table = makeIcon('Table', G['table']); +export const Tag = makeIcon('Tag', G['tag']); +export const Tags = makeIcon('Tags', G['tags']); +export const Terminal = makeIcon('Terminal', G['terminal']); +export const Trash = makeIcon('Trash', G['trash']); +export const Trash2 = makeIcon('Trash2', G['trash']); +export const Type = makeIcon('Type', G['typography']); +export const Underline = makeIcon('Underline', G['underline']); +export const Undo2 = makeIcon('Undo2', G['arrow-back']); +export const Upload = makeIcon('Upload', G['upload']); +export const User = makeIcon('User', G['user']); +export const UserCircle = makeIcon('UserCircle', G['user-circle']); +export const UserMinus = makeIcon('UserMinus', G['user-minus']); +export const UserPen = makeIcon('UserPen', G['user-edit']); +export const UserPlus = makeIcon('UserPlus', G['user-plus']); +export const UserRound = makeIcon('UserRound', G['user']); +export const UserRoundPlus = makeIcon('UserRoundPlus', G['user-plus']); +export const Users = makeIcon('Users', G['users']); +export const Video = makeIcon('Video', G['video']); +export const X = makeIcon('X', G['x']); +export const XCircle = makeIcon('XCircle', G['circle-x']); diff --git a/src/components/settings/AboutDataSettings.tsx b/src/components/settings/AboutDataSettings.tsx index 24f35691..d1a340dd 100644 --- a/src/components/settings/AboutDataSettings.tsx +++ b/src/components/settings/AboutDataSettings.tsx @@ -4,7 +4,7 @@ import Constants from 'expo-constants'; import * as DocumentPicker from 'expo-document-picker'; import { File, Paths } from 'expo-file-system'; import * as Sharing from 'expo-sharing'; -import { CloudDownload, ExternalLink } from 'lucide-react-native'; +import { CloudDownload, ExternalLink } from '../icons'; import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section'; import Button from '../Button'; import Dialog from '../Dialog'; diff --git a/src/components/settings/AccountSecuritySettings.tsx b/src/components/settings/AccountSecuritySettings.tsx index 28a4b7b5..45a7fcef 100644 --- a/src/components/settings/AccountSecuritySettings.tsx +++ b/src/components/settings/AccountSecuritySettings.tsx @@ -4,7 +4,7 @@ import DateTimePicker, { type DateTimePickerEvent } from '@react-native-communit import { Key, Smartphone, Lock, Eye, EyeOff, ShieldCheck, Monitor, Trash2, Plus, Shield, Terminal, Check, ExternalLink, Calendar, X, -} from 'lucide-react-native'; +} from '../icons'; import { SettingsSection, SettingItem, ToggleSwitch } from './settings-section'; import Input from '../Input'; import Button from '../Button'; diff --git a/src/components/settings/AccountSettings.tsx b/src/components/settings/AccountSettings.tsx index 398e12e4..eac866a0 100644 --- a/src/components/settings/AccountSettings.tsx +++ b/src/components/settings/AccountSettings.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { View, Text, StyleSheet, Pressable, Alert } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { ArrowDown, ArrowUp, Check, ChevronRight, Plus, Star, Trash2, Users } from 'lucide-react-native'; +import { ArrowDown, ArrowUp, Check, ChevronRight, Plus, Star, Trash2, Users } from '../icons'; import { SettingsSection, SettingItem } from './settings-section'; import Button from '../Button'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/settings/ComposingSettings.tsx b/src/components/settings/ComposingSettings.tsx index 8407ae29..a1581101 100644 --- a/src/components/settings/ComposingSettings.tsx +++ b/src/components/settings/ComposingSettings.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { View, Text, StyleSheet, Pressable, TextInput } from 'react-native'; -import { X, Plus } from 'lucide-react-native'; +import { X, Plus } from '../icons'; import { SettingsSection, SettingItem, ToggleSwitch } from './settings-section'; import Button from '../Button'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/settings/ContactsSettings.tsx b/src/components/settings/ContactsSettings.tsx index 04b468c4..423727a2 100644 --- a/src/components/settings/ContactsSettings.tsx +++ b/src/components/settings/ContactsSettings.tsx @@ -4,7 +4,7 @@ import * as FileSystem from 'expo-file-system/legacy'; import * as Sharing from 'expo-sharing'; import { Download, Upload, Plus, Pencil, Trash2, Check, X, BookUser, Star, -} from 'lucide-react-native'; +} from '../icons'; import { SettingsSection, SettingItem, ToggleSwitch } from './settings-section'; import Button from '../Button'; import Dialog from '../Dialog'; diff --git a/src/components/settings/ContentSendersSettings.tsx b/src/components/settings/ContentSendersSettings.tsx index 06f48482..e89d0745 100644 --- a/src/components/settings/ContentSendersSettings.tsx +++ b/src/components/settings/ContentSendersSettings.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { View, Text, StyleSheet, Pressable, TextInput, FlatList, Modal, Alert, } from 'react-native'; -import { ChevronRight, X, Plus, Trash2, BookUser } from 'lucide-react-native'; +import { ChevronRight, X, Plus, Trash2, BookUser } from '../icons'; import { SettingsSection, SettingItem, RadioGroup, ToggleSwitch } from './settings-section'; import Button from '../Button'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/settings/DownloadsSettings.tsx b/src/components/settings/DownloadsSettings.tsx index da5a78ef..604b1744 100644 --- a/src/components/settings/DownloadsSettings.tsx +++ b/src/components/settings/DownloadsSettings.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { View, Text, StyleSheet, Pressable } from 'react-native'; -import { RotateCcw } from 'lucide-react-native'; +import { RotateCcw } from '../icons'; import { SettingsSection, SettingItem, RadioGroup, ToggleSwitch } from './settings-section'; import Input from '../Input'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/settings/FilesSettings.tsx b/src/components/settings/FilesSettings.tsx index d00a68c0..96007bd6 100644 --- a/src/components/settings/FilesSettings.tsx +++ b/src/components/settings/FilesSettings.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import { View, Text, StyleSheet } from 'react-native'; import { Folder, FileText, FileCode, FileAudio, File, Image as ImageIcon, -} from 'lucide-react-native'; +} from '../icons'; import { SettingsSection, SettingItem, RadioGroup, ToggleSwitch } from './settings-section'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/settings/FilterSettings.tsx b/src/components/settings/FilterSettings.tsx index df5c6aa3..384bc2b7 100644 --- a/src/components/settings/FilterSettings.tsx +++ b/src/components/settings/FilterSettings.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, Pressable, ActivityIndicator, Alert } from 'rea import { Plus, X, Code, AlertTriangle, Filter, RotateCcw, Lock, ChevronUp, ChevronDown, Palmtree, ChevronRight, -} from 'lucide-react-native'; +} from '../icons'; import { SettingsSection, ToggleSwitch } from './settings-section'; import Button from '../Button'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/settings/FolderSettings.tsx b/src/components/settings/FolderSettings.tsx index 6f01f6b6..2c722b9b 100644 --- a/src/components/settings/FolderSettings.tsx +++ b/src/components/settings/FolderSettings.tsx @@ -13,7 +13,7 @@ import { import { Folder, Inbox, Send, FileText, Trash, ShieldAlert, Archive, Flag, Star, Mails, StickyNote, Clock, AlarmClock, Users, Plus, Pencil, Trash2, X, -} from 'lucide-react-native'; +} from '../icons'; import { SettingsSection, Select } from './settings-section'; import Button from '../Button'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/settings/IdentitySettings.tsx b/src/components/settings/IdentitySettings.tsx index 193e8d92..f9ee1bd6 100644 --- a/src/components/settings/IdentitySettings.tsx +++ b/src/components/settings/IdentitySettings.tsx @@ -10,7 +10,7 @@ import { TextInput, View, } from 'react-native'; -import { Plus, Star, Trash2, X } from 'lucide-react-native'; +import { Plus, Star, Trash2, X } from '../icons'; import { SettingsSection } from './settings-section'; import Button from '../Button'; import { typography, spacing, radius, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/settings/KeywordSettings.tsx b/src/components/settings/KeywordSettings.tsx index 8becb4f5..32bda22e 100644 --- a/src/components/settings/KeywordSettings.tsx +++ b/src/components/settings/KeywordSettings.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { View, Text, StyleSheet, Pressable, TextInput, Alert, ActivityIndicator } from 'react-native'; -import { Plus, Pencil, Trash2, Check, X, RotateCcw, ScanSearch } from 'lucide-react-native'; +import { Plus, Pencil, Trash2, Check, X, RotateCcw, ScanSearch } from '../icons'; import { SettingsSection } from './settings-section'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/settings/LanguageSettings.tsx b/src/components/settings/LanguageSettings.tsx index 35e9ae98..320da97d 100644 --- a/src/components/settings/LanguageSettings.tsx +++ b/src/components/settings/LanguageSettings.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { View, Text, StyleSheet, Pressable } from 'react-native'; -import { Check } from 'lucide-react-native'; +import { Check } from '../icons'; import { SettingsSection, SettingItem, RadioGroup } from './settings-section'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/settings/LayoutSettings.tsx b/src/components/settings/LayoutSettings.tsx index 7d63302c..0fb14612 100644 --- a/src/components/settings/LayoutSettings.tsx +++ b/src/components/settings/LayoutSettings.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { View, Text, StyleSheet } from 'react-native'; -import { ArrowRight, ArrowLeft, MoveVertical, ArrowDownWideNarrow } from 'lucide-react-native'; +import { ArrowRight, ArrowLeft, MoveVertical, ArrowDownWideNarrow } from '../icons'; import { SettingsSection, SettingItem, ToggleSwitch, RadioGroup, Select } from './settings-section'; import { spacing, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/settings/NotificationSettings.tsx b/src/components/settings/NotificationSettings.tsx index 5ed92924..93c26dd0 100644 --- a/src/components/settings/NotificationSettings.tsx +++ b/src/components/settings/NotificationSettings.tsx @@ -7,7 +7,7 @@ import { TextInput, View, } from 'react-native'; -import { CheckCircle2, RefreshCw, XCircle } from 'lucide-react-native'; +import { CheckCircle2, RefreshCw, XCircle } from '../icons'; import Button from '../Button'; import Dialog from '../Dialog'; import { SettingsSection, SettingItem, ToggleSwitch } from './settings-section'; diff --git a/src/components/settings/PluginsSettings.tsx b/src/components/settings/PluginsSettings.tsx index 0d7612ee..b2dd8d59 100644 --- a/src/components/settings/PluginsSettings.tsx +++ b/src/components/settings/PluginsSettings.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; -import { Puzzle } from 'lucide-react-native'; +import { Puzzle } from '../icons'; import { SettingsSection } from './settings-section'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/settings/ReadingSettings.tsx b/src/components/settings/ReadingSettings.tsx index ef39e666..3949f275 100644 --- a/src/components/settings/ReadingSettings.tsx +++ b/src/components/settings/ReadingSettings.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { ActivityIndicator, View, Text, StyleSheet, Pressable, TextInput } from 'react-native'; -import { AlertTriangle, FolderSync, X } from 'lucide-react-native'; +import { AlertTriangle, FolderSync, X } from '../icons'; import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/settings/SidebarAppsSettings.tsx b/src/components/settings/SidebarAppsSettings.tsx index 438a5b9b..ed641e55 100644 --- a/src/components/settings/SidebarAppsSettings.tsx +++ b/src/components/settings/SidebarAppsSettings.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { View, Text, StyleSheet, Pressable, TextInput } from 'react-native'; import { Plus, Pencil, Trash2, Globe, ExternalLink, PanelRight, GripVertical, -} from 'lucide-react-native'; +} from '../icons'; import { SettingsSection, SettingItem, ToggleSwitch } from './settings-section'; import Button from '../Button'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/settings/SmimeSettings.tsx b/src/components/settings/SmimeSettings.tsx index 0af9a542..58f2d50d 100644 --- a/src/components/settings/SmimeSettings.tsx +++ b/src/components/settings/SmimeSettings.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; -import { KeyRound } from 'lucide-react-native'; +import { KeyRound } from '../icons'; import { SettingsSection } from './settings-section'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/components/settings/TemplateSettings.tsx b/src/components/settings/TemplateSettings.tsx index 95d521f5..3fc0fd9b 100644 --- a/src/components/settings/TemplateSettings.tsx +++ b/src/components/settings/TemplateSettings.tsx @@ -10,7 +10,7 @@ import { TextInput, View, } from 'react-native'; -import { Download, FileText, Plus, Star, Trash2, Upload, X } from 'lucide-react-native'; +import { Download, FileText, Plus, Star, Trash2, Upload, X } from '../icons'; import { SettingsSection, ToggleSwitch } from './settings-section'; import Button from '../Button'; import { radius, spacing, typography, type ThemePalette } from '../../theme/tokens'; diff --git a/src/components/settings/ThemesSettings.tsx b/src/components/settings/ThemesSettings.tsx index 817029c1..37383f01 100644 --- a/src/components/settings/ThemesSettings.tsx +++ b/src/components/settings/ThemesSettings.tsx @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { View, Text, StyleSheet, Pressable } from 'react-native'; -import { Check } from 'lucide-react-native'; +import { Check } from '../icons'; import { SettingsSection } from './settings-section'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { resolvePalette, useColors, useResolvedTheme } from '../../theme/colors'; diff --git a/src/components/settings/VacationSettings.tsx b/src/components/settings/VacationSettings.tsx index 695426fa..c3385bf9 100644 --- a/src/components/settings/VacationSettings.tsx +++ b/src/components/settings/VacationSettings.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, TextInput, Pressable, ActivityIndicator, Alert import { WebView } from 'react-native-webview'; import { AlertTriangle, Eye, EyeOff, Bold, Italic, Underline, List as ListIcon, -} from 'lucide-react-native'; +} from '../icons'; import { SettingsSection, SettingItem, ToggleSwitch } from './settings-section'; import Button from '../Button'; import RichTextEditor, { type RichTextEditorHandle } from '../RichTextEditor'; diff --git a/src/components/settings/device-sync/DeviceSyncAccountRow.tsx b/src/components/settings/device-sync/DeviceSyncAccountRow.tsx index b7053ec9..8fb12cbb 100644 --- a/src/components/settings/device-sync/DeviceSyncAccountRow.tsx +++ b/src/components/settings/device-sync/DeviceSyncAccountRow.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { Alert, StyleSheet, Text, View, ActivityIndicator } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { AlertTriangle, RefreshCw, Settings as SettingsIcon, XCircle } from 'lucide-react-native'; +import { AlertTriangle, RefreshCw, Settings as SettingsIcon, XCircle } from '../../icons'; import { SettingItem, Select, RadioGroup, ToggleSwitch } from '../settings-section'; import Button from '../../Button'; import Dialog from '../../Dialog'; diff --git a/src/components/settings/device-sync/DeviceSyncCollectionsSheet.tsx b/src/components/settings/device-sync/DeviceSyncCollectionsSheet.tsx index 1dcabb02..fa04d816 100644 --- a/src/components/settings/device-sync/DeviceSyncCollectionsSheet.tsx +++ b/src/components/settings/device-sync/DeviceSyncCollectionsSheet.tsx @@ -8,7 +8,7 @@ import { ActivityIndicator, Animated, Easing, Modal, Pressable, ScrollView, StyleSheet, Text, View, } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { AlertTriangle, Check, X } from 'lucide-react-native'; +import { AlertTriangle, Check, X } from '../../icons'; import Button from '../../Button'; import { spacing, radius, typography, type ThemePalette } from '../../../theme/tokens'; import { useColors } from '../../../theme/colors'; diff --git a/src/components/settings/settings-section.tsx b/src/components/settings/settings-section.tsx index f7a378dd..e208f876 100644 --- a/src/components/settings/settings-section.tsx +++ b/src/components/settings/settings-section.tsx @@ -8,7 +8,7 @@ import { ScrollView, ViewStyle, } from 'react-native'; -import { FlaskConical, Lock, ChevronDown, Check } from 'lucide-react-native'; +import { FlaskConical, Lock, ChevronDown, Check } from '../icons'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import ToggleSwitchComponent from '../ToggleSwitch'; diff --git a/src/lib/__tests__/lucide-imports.test.ts b/src/lib/__tests__/lucide-imports.test.ts deleted file mode 100644 index 9a4c18a6..00000000 --- a/src/lib/__tests__/lucide-imports.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -// The babel plugin in scripts/babel-plugin-lucide-imports.js rewrites -// `import { X } from 'lucide-react-native'` to per-icon modules. These tests -// make sure every icon the app imports maps to a file that exists, and that -// no import form slips back to the full barrel. -import { describe, it, expect } from 'vitest'; -import { createRequire } from 'node:module'; -import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'; -import { join, relative } from 'node:path'; - -const require = createRequire(import.meta.url); -const plugin = require('../../../scripts/babel-plugin-lucide-imports.js'); -const babel = require('@babel/core'); - -const ROOT = join(__dirname, '..', '..', '..'); -const PACKAGE_DIR = join(require.resolve('lucide-react-native'), '..', '..', '..'); - -function sourceFiles(dir: string, out: string[] = []): string[] { - for (const entry of readdirSync(dir)) { - const p = join(dir, entry); - if (statSync(p).isDirectory()) { - if (entry !== '__tests__' && entry !== 'node_modules') sourceFiles(p, out); - } else if (/\.tsx?$/.test(entry)) { - out.push(p); - } - } - return out; -} - -const FILES = [...sourceFiles(join(ROOT, 'src')), join(ROOT, 'App.tsx'), join(ROOT, 'index.ts')]; -const LUCIDE_IMPORT_RE = /import\s+(type\s+)?([^;]*?)\s+from\s+'lucide-react-native'/g; - -function transform(code: string): string { - return babel.transformSync(code, { - babelrc: false, - configFile: false, - filename: 'fixture.tsx', - parserOpts: { plugins: ['typescript', 'jsx'] }, - plugins: [plugin], - }).code as string; -} - -describe('lucide-react-native imports', () => { - const exportMap: Map = plugin.loadExportMap(); - - it('reads the icon table from the package barrel', () => { - expect(exportMap.size).toBeGreaterThan(1000); - expect(exportMap.get('Mail')?.source).toBe('lucide-react-native/dist/esm/icons/mail.js'); - // Aliases resolve to the icon they re-export. - expect(exportMap.get('MailIcon')?.source).toBe('lucide-react-native/dist/esm/icons/mail.js'); - expect(exportMap.get('AlarmCheck')?.source).toBe('lucide-react-native/dist/esm/icons/alarm-clock-check.js'); - }); - - it('maps every icon the app imports to an existing module', () => { - const problems: string[] = []; - let checked = 0; - for (const file of FILES) { - const text = readFileSync(file, 'utf8'); - for (const m of text.matchAll(LUCIDE_IMPORT_RE)) { - const where = relative(ROOT, file); - if (m[1]) continue; // `import type { … }` is erased - const clause = m[2].trim(); - if (!clause.startsWith('{') || !clause.endsWith('}')) { - problems.push(`${where}: only named imports avoid the barrel (${clause})`); - continue; - } - for (const raw of clause.slice(1, -1).split(',')) { - const spec = raw.trim(); - if (!spec || spec.startsWith('type ')) continue; - const name = spec.split(/\s+as\s+/)[0]; - const target = exportMap.get(name); - if (!target) { - problems.push(`${where}: ${name} is not a lucide export (import types with \`type\`)`); - continue; - } - const file = join(PACKAGE_DIR, target.source.slice('lucide-react-native/'.length)); - if (!existsSync(file)) problems.push(`${where}: ${name} -> missing ${target.source}`); - checked++; - } - } - } - expect(problems).toEqual([]); - expect(checked).toBeGreaterThan(100); - }); - - it('rewrites named imports to per-icon modules', () => { - const out = transform( - "import { Mail, Trash2 as Bin, type LucideIcon, LucideProvider } from 'lucide-react-native';\n" - + 'const icons: LucideIcon[] = [Mail, Bin];\n' - + 'export { icons, LucideProvider };\n', - ); - expect(out).toContain("import Mail from \"lucide-react-native/dist/esm/icons/mail.js\""); - expect(out).toContain("import Bin from \"lucide-react-native/dist/esm/icons/trash-2.js\""); - expect(out).toContain("import { LucideProvider } from \"lucide-react-native/dist/esm/context.js\""); - expect(out).not.toMatch(/from "lucide-react-native"/); - }); - - it('drops type-only names and fails on unknown values', () => { - expect(transform( - "import { LucideProps } from 'lucide-react-native';\nexport function f(p: LucideProps) { return p; }\n", - )).not.toContain('lucide-react-native'); - expect(() => transform( - "import { NotAnIcon } from 'lucide-react-native';\nexport const x = NotAnIcon;\n", - )).toThrow(/no export named "NotAnIcon"/); - }); -}); diff --git a/src/screens/CalendarScreen.tsx b/src/screens/CalendarScreen.tsx index 71584a0c..75ffe061 100644 --- a/src/screens/CalendarScreen.tsx +++ b/src/screens/CalendarScreen.tsx @@ -24,7 +24,7 @@ import { Menu, Rows2, Square, -} from 'lucide-react-native'; +} from '../components/icons'; import { format, addDays, diff --git a/src/screens/ComposeScreen.tsx b/src/screens/ComposeScreen.tsx index 49329ec3..6c048141 100644 --- a/src/screens/ComposeScreen.tsx +++ b/src/screens/ComposeScreen.tsx @@ -12,7 +12,7 @@ import { Heading1, Heading2, AlignLeft, AlignCenter, AlignRight, RemoveFormatting, Undo2, Redo2, FileText, Clock, Check, Palette, Table, LayoutTemplate, MailCheck, Users, Tag, Type, Highlighter, -} from 'lucide-react-native'; +} from '../components/icons'; import DateTimePicker, { type DateTimePickerEvent } from '@react-native-community/datetimepicker'; import * as ImagePicker from 'expo-image-picker'; import * as DocumentPicker from 'expo-document-picker'; diff --git a/src/screens/ContactDetailScreen.tsx b/src/screens/ContactDetailScreen.tsx index ebe82aa7..1ba0f287 100644 --- a/src/screens/ContactDetailScreen.tsx +++ b/src/screens/ContactDetailScreen.tsx @@ -14,7 +14,7 @@ import { Building, Cake, Heart, Globe, Tag, Users, FileText, BookUser, Copy, MoreHorizontal, Calendar as CalendarIcon, UserCircle, Languages, Clock, KeyRound, FolderInput, Plus, -} from 'lucide-react-native'; +} from '../components/icons'; import type { RootStackParamList } from '../navigation/types'; import { openExternalUrl } from '../lib/open-url'; import type { ContactCard } from '../api/types'; diff --git a/src/screens/ContactFormScreen.tsx b/src/screens/ContactFormScreen.tsx index c7619b53..78502216 100644 --- a/src/screens/ContactFormScreen.tsx +++ b/src/screens/ContactFormScreen.tsx @@ -12,7 +12,7 @@ import * as ImageManipulator from 'expo-image-manipulator'; import { ArrowLeft, Mail, Phone, Building, MapPin, Cake, Tag, FileText, User, Check, Calendar, Camera, X as XIcon, Globe, Heart, UserCircle, Plus, ChevronDown, ChevronRight, Book, Users, -} from 'lucide-react-native'; +} from '../components/icons'; import type { RootStackParamList } from '../navigation/types'; import type { ContactCard, ContactEmail, ContactPhone, ContactAddress, ContactOrganization, diff --git a/src/screens/ContactsScreen.tsx b/src/screens/ContactsScreen.tsx index 0694590e..d25ecda3 100644 --- a/src/screens/ContactsScreen.tsx +++ b/src/screens/ContactsScreen.tsx @@ -21,7 +21,7 @@ import { listBackStep } from '../lib/list-back'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { Search, Plus, UserCircle, X, Menu, Trash2, Tag, FolderInput, Upload, CheckSquare, Share2, -} from 'lucide-react-native'; +} from '../components/icons'; import type { RootStackParamList } from '../navigation/types'; import type { ContactCard } from '../api/types'; import { diff --git a/src/screens/ContactsTab.tsx b/src/screens/ContactsTab.tsx index 1b1a6956..93257e1f 100644 --- a/src/screens/ContactsTab.tsx +++ b/src/screens/ContactsTab.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { StyleSheet, Text, View, useWindowDimensions } from 'react-native'; import { useNavigation, type NavigationProp, type ParamListBase } from '@react-navigation/native'; -import { BookUser } from 'lucide-react-native'; +import { BookUser } from '../components/icons'; import ContactsScreen from './ContactsScreen'; import ContactDetailScreen from './ContactDetailScreen'; diff --git a/src/screens/EmailListScreen.tsx b/src/screens/EmailListScreen.tsx index 11a1ea85..c7d3a10b 100644 --- a/src/screens/EmailListScreen.tsx +++ b/src/screens/EmailListScreen.tsx @@ -8,7 +8,7 @@ import { Star, Paperclip, Mail as MailIcon, MailOpen, Trash2, RotateCcw, CalendarDays, Archive, FolderInput, Tag, Import, ArrowDownWideNarrow, ArrowUpNarrowWide, Pin, Reply, Forward, ShieldAlert, ShieldCheck, Folder, -} from 'lucide-react-native'; +} from '../components/icons'; import { useNavigation } from '@react-navigation/native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../theme/tokens'; diff --git a/src/screens/EmailSourceScreen.tsx b/src/screens/EmailSourceScreen.tsx index 2afcddc1..2b629b59 100644 --- a/src/screens/EmailSourceScreen.tsx +++ b/src/screens/EmailSourceScreen.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ScrollView, ActivityIndicator, Pressable, Alert, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { ArrowLeft, Share2, Copy, Check } from 'lucide-react-native'; +import { ArrowLeft, Share2, Copy, Check } from '../components/icons'; import * as Clipboard from 'expo-clipboard'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../navigation/types'; diff --git a/src/screens/EmailThreadScreen.tsx b/src/screens/EmailThreadScreen.tsx index 1e563b65..d1879a44 100644 --- a/src/screens/EmailThreadScreen.tsx +++ b/src/screens/EmailThreadScreen.tsx @@ -12,7 +12,7 @@ import { ChevronLeft, ChevronRight, Archive, Mail, MailOpen, FolderInput, ShieldAlert, ShieldCheck, X, Check, Code, Download, Tag, Sun, Moon, FileInput, UserRoundPlus, -} from 'lucide-react-native'; +} from '../components/icons'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../theme/tokens'; import { useColors, useResolvedTheme } from '../theme/colors'; import { MoveSheet } from '../components/MoveSheet'; diff --git a/src/screens/FilesScreen.tsx b/src/screens/FilesScreen.tsx index d5fdcdbb..ae2136cd 100644 --- a/src/screens/FilesScreen.tsx +++ b/src/screens/FilesScreen.tsx @@ -21,7 +21,7 @@ import { FileAudio, FileArchive, FileSpreadsheet, FileCode2, LayoutGrid, List as ListIcon, MoreVertical, Pencil, Share2, Trash2, Upload, Users, X, Download, Search, ArrowUpDown, ArrowUp, ArrowDown, FolderInput, Copy, Check, Eye, ExternalLink, -} from 'lucide-react-native'; +} from '../components/icons'; import { File, Paths } from 'expo-file-system'; import * as Sharing from 'expo-sharing'; import JSZip from 'jszip'; diff --git a/src/screens/GroupDetailScreen.tsx b/src/screens/GroupDetailScreen.tsx index 6cce19d9..9911c90b 100644 --- a/src/screens/GroupDetailScreen.tsx +++ b/src/screens/GroupDetailScreen.tsx @@ -8,7 +8,7 @@ import { useNavigation, useRoute, type RouteProp } from '@react-navigation/nativ import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { ArrowLeft, Edit2, Trash2, Plus, Users, Mail, UserMinus, -} from 'lucide-react-native'; +} from '../components/icons'; import type { RootStackParamList } from '../navigation/types'; import type { ContactCard } from '../api/types'; import { diff --git a/src/screens/MailTab.tsx b/src/screens/MailTab.tsx index af348f78..e07e7b3c 100644 --- a/src/screens/MailTab.tsx +++ b/src/screens/MailTab.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { StyleSheet, Text, View, useWindowDimensions } from 'react-native'; import type { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { Mail } from 'lucide-react-native'; +import { Mail } from '../components/icons'; import EmailListScreen from './EmailListScreen'; import EmailThreadScreen from './EmailThreadScreen'; diff --git a/src/screens/ScheduledScreen.tsx b/src/screens/ScheduledScreen.tsx index dbb77ae3..4522848e 100644 --- a/src/screens/ScheduledScreen.tsx +++ b/src/screens/ScheduledScreen.tsx @@ -3,7 +3,7 @@ import { View, Text, StyleSheet, FlatList, ActivityIndicator, Pressable, Alert, Modal, Platform, } from 'react-native'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; -import { ArrowLeft, Clock, MoreHorizontal } from 'lucide-react-native'; +import { ArrowLeft, Clock, MoreHorizontal } from '../components/icons'; import DateTimePicker, { type DateTimePickerEvent } from '@react-native-community/datetimepicker'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../navigation/types'; diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx index b6bf0822..00253935 100644 --- a/src/screens/SettingsScreen.tsx +++ b/src/screens/SettingsScreen.tsx @@ -9,8 +9,8 @@ import { Filter, FileText, FolderOpen, Tags, HardDrive, BookUser, KeyRound, PanelLeftClose, Bell, Puzzle, RefreshCw, LayoutGrid, BookOpen, PenLine, EyeOff, Languages, Info, Download, - type LucideIcon, -} from 'lucide-react-native'; + type AppIcon, +} from '../components/icons'; import { spacing, radius, typography, componentSizes, type ThemePalette } from '../theme/tokens'; import { useColors } from '../theme/colors'; import { ReadingSettings } from '../components/settings/ReadingSettings'; @@ -62,7 +62,7 @@ type TabGroup = 'general' | 'appearance' | 'mail' | 'privacy' | 'apps' | 'advanc interface TabDef { id: Tab; label: string; - icon: LucideIcon; + icon: AppIcon; group: TabGroup; experimental?: boolean; implemented: boolean; diff --git a/src/screens/UnifiedInboxScreen.tsx b/src/screens/UnifiedInboxScreen.tsx index 5e68ace5..5040f28b 100644 --- a/src/screens/UnifiedInboxScreen.tsx +++ b/src/screens/UnifiedInboxScreen.tsx @@ -8,7 +8,7 @@ import { useFocusEffect } from '@react-navigation/native'; import { ArrowLeft, Star, Paperclip, AlertTriangle, Search, X, Square, SquareCheck, Mail as MailIcon, MailOpen, Archive, Trash2, ShieldAlert, ShieldCheck, Folder, -} from 'lucide-react-native'; +} from '../components/icons'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import type { RootStackParamList } from '../navigation/types'; import SenderAvatar from '../components/SenderAvatar'; diff --git a/src/screens/login/ChooseStep.tsx b/src/screens/login/ChooseStep.tsx index 3df641a3..46d5905d 100644 --- a/src/screens/login/ChooseStep.tsx +++ b/src/screens/login/ChooseStep.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, Pressable, StyleSheet, Image } from 'react-native'; -import { ClipboardPaste, QrCode, Mail, Plus, Server } from 'lucide-react-native'; +import { ClipboardPaste, QrCode, Mail, Plus, Server } from '../../components/icons'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors, useResolvedTheme } from '../../theme/colors'; import type { AccountEntry } from '../../stores/account-store'; diff --git a/src/screens/login/ConfirmStep.tsx b/src/screens/login/ConfirmStep.tsx index 4cdb1936..70faf59f 100644 --- a/src/screens/login/ConfirmStep.tsx +++ b/src/screens/login/ConfirmStep.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, Pressable, StyleSheet } from 'react-native'; -import { Globe } from 'lucide-react-native'; +import { Globe } from '../../components/icons'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { Button } from '../../components'; diff --git a/src/screens/login/EmailStep.tsx b/src/screens/login/EmailStep.tsx index 1b694dfe..41560ca7 100644 --- a/src/screens/login/EmailStep.tsx +++ b/src/screens/login/EmailStep.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, Pressable, StyleSheet } from 'react-native'; -import { Mail } from 'lucide-react-native'; +import { Mail } from '../../components/icons'; import { spacing, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { Button, Input } from '../../components'; diff --git a/src/screens/login/LoginNotice.tsx b/src/screens/login/LoginNotice.tsx index 0d972203..05dc51f1 100644 --- a/src/screens/login/LoginNotice.tsx +++ b/src/screens/login/LoginNotice.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, StyleSheet } from 'react-native'; -import { AlertTriangle, Lock } from 'lucide-react-native'; +import { AlertTriangle, Lock } from '../../components/icons'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/screens/login/LoginShell.tsx b/src/screens/login/LoginShell.tsx index cd15bff6..35eaad7b 100644 --- a/src/screens/login/LoginShell.tsx +++ b/src/screens/login/LoginShell.tsx @@ -10,7 +10,7 @@ import { } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import Constants from 'expo-constants'; -import { ArrowLeft, X } from 'lucide-react-native'; +import { ArrowLeft, X } from '../../components/icons'; import { spacing, typography, componentSizes, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { useLocaleStore } from '../../stores/locale-store'; diff --git a/src/screens/login/OptionTile.tsx b/src/screens/login/OptionTile.tsx index 0e0632a0..eb511d65 100644 --- a/src/screens/login/OptionTile.tsx +++ b/src/screens/login/OptionTile.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, Pressable, StyleSheet } from 'react-native'; -import { ChevronRight } from 'lucide-react-native'; +import { ChevronRight } from '../../components/icons'; import { spacing, radius, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; diff --git a/src/screens/login/PasswordStep.tsx b/src/screens/login/PasswordStep.tsx index e3dedc64..24eda069 100644 --- a/src/screens/login/PasswordStep.tsx +++ b/src/screens/login/PasswordStep.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, Pressable, StyleSheet, type TextInput } from 'react-native'; -import { Eye, EyeOff } from 'lucide-react-native'; +import { Eye, EyeOff } from '../../components/icons'; import { spacing, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { Button, Input } from '../../components'; diff --git a/src/screens/login/ServerStep.tsx b/src/screens/login/ServerStep.tsx index aadae708..f349e63a 100644 --- a/src/screens/login/ServerStep.tsx +++ b/src/screens/login/ServerStep.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { View, Text, Pressable, StyleSheet } from 'react-native'; -import { ClipboardPaste, Globe, QrCode } from 'lucide-react-native'; +import { ClipboardPaste, Globe, QrCode } from '../../components/icons'; import { spacing, typography, type ThemePalette } from '../../theme/tokens'; import { useColors } from '../../theme/colors'; import { Button, Input } from '../../components';