refactor: draw the webmail's Tabler icons instead of Lucide

This commit is contained in:
Linus Rath
2026-10-15 14:13:02 +02:00
parent 6aaa479875
commit 75299244cf
109 changed files with 826 additions and 337 deletions
+1 -1
View File
@@ -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';
-1
View File
@@ -10,6 +10,5 @@ module.exports = function babelConfig(api) {
api.cache(true);
return {
presets: [presetExpo],
plugins: [require.resolve('./scripts/babel-plugin-lucide-imports')],
};
};
-15
View File
@@ -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;
+1 -12
View File
@@ -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",
-1
View File
@@ -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",
-91
View File
@@ -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;
+241
View File
@@ -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();
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+3 -3
View File
@@ -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;
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+5 -5
View File
@@ -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;
+4 -4
View File
@@ -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<Exclude<SwipeAction, 'none'>, { icon: LucideIcon; bg: string; defaultLabel: string }> = {
const ACTION_META: Record<Exclude<SwipeAction, 'none'>, { 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<SwipeAction, 'none'>, context: SwipeContext): LucideIcon {
function actionIcon(action: Exclude<SwipeAction, 'none'>, context: SwipeContext): AppIcon {
if (action === 'spam' && context.inJunk) return ShieldCheck;
if (action === 'read' && !context.unread) return Mail;
if (action === 'pin' && context.pinned) return PinOff;
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
@@ -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';
+1 -1
View File
@@ -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';
@@ -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';
@@ -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 {
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -25,7 +25,7 @@ import {
Users,
Trash2,
AlignLeft,
} from 'lucide-react-native';
} from '../icons';
import { addHours, addMinutes, format } from 'date-fns';
import type {
Alert,
+1 -1
View File
@@ -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';
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
@@ -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';
+1 -1
View File
@@ -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';
@@ -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';
+1 -1
View File
@@ -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';
@@ -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';
+1 -1
View File
@@ -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,
@@ -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,
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
@@ -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';
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
@@ -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<string, unknown> = 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<string, unknown> = await import('..');
const tabler: Record<string, [string, { d: string }][]> = 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);
});
});
+41
View File
@@ -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<ViewStyle>;
testID?: string;
}
export type AppIcon = React.ComponentType<IconProps>;
/** 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 (
<Svg
width={size}
height={size}
viewBox="0 0 24 24"
fill={fill}
stroke={color}
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
style={style}
testID={testID}
>
{paths.map((d, i) => <Path key={i} d={d} />)}
</Svg>
);
}
Icon.displayName = name;
return React.memo(Icon);
}
+357
View File
@@ -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']);
@@ -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';
@@ -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';
+1 -1
View File
@@ -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';
@@ -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';
+1 -1
View File
@@ -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';
@@ -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';
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
@@ -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';
@@ -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';
+1 -1
View File
@@ -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';
-105
View File
@@ -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<string, { source: string; imported: string }> = 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"/);
});
});
+1 -1
View File
@@ -24,7 +24,7 @@ import {
Menu,
Rows2,
Square,
} from 'lucide-react-native';
} from '../components/icons';
import {
format,
addDays,
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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';
+1 -1
View File
@@ -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';
+3 -3
View File
@@ -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;

Some files were not shown because too many files have changed in this diff Show More