1import type { Icon } from "@tabler/icons-react-native/types";
2/*
3 * Keep these as per-icon exports. Importing the package root eagerly registers
4 * the entire Tabler icon set in Metro.
5 */
6import IconAdjustmentsHorizontal from "@tabler/icons-react-native/IconAdjustmentsHorizontal";
7import IconAlignLeft from "@tabler/icons-react-native/IconAlignLeft";
8import IconAlertCircle from "@tabler/icons-react-native/IconAlertCircle";
9import IconAlertTriangle from "@tabler/icons-react-native/IconAlertTriangle";
10import IconApps from "@tabler/icons-react-native/IconApps";
11import IconArchive from "@tabler/icons-react-native/IconArchive";
12import IconArrowBackUp from "@tabler/icons-react-native/IconArrowBackUp";
13import IconArrowDown from "@tabler/icons-react-native/IconArrowDown";
14import IconArrowForwardUp from "@tabler/icons-react-native/IconArrowForwardUp";
15import IconArrowLeft from "@tabler/icons-react-native/IconArrowLeft";
16import IconArrowRight from "@tabler/icons-react-native/IconArrowRight";
17import IconArrowDownCircle from "@tabler/icons-react-native/IconArrowDownCircle";
18import IconArrowRightCircle from "@tabler/icons-react-native/IconArrowRightCircle";
19import IconArrowUp from "@tabler/icons-react-native/IconArrowUp";
20import IconArrowUpCircle from "@tabler/icons-react-native/IconArrowUpCircle";
21import IconArrowUpRight from "@tabler/icons-react-native/IconArrowUpRight";
22import IconArrowUpRightCircle from "@tabler/icons-react-native/IconArrowUpRightCircle";
23import IconArrowsLeftRight from "@tabler/icons-react-native/IconArrowsLeftRight";
24import IconArrowsDiagonal2 from "@tabler/icons-react-native/IconArrowsDiagonal2";
25import IconArrowsMinimize from "@tabler/icons-react-native/IconArrowsMinimize";
26import IconBellRinging from "@tabler/icons-react-native/IconBellRinging";
27import IconBolt from "@tabler/icons-react-native/IconBolt";
28import IconBox from "@tabler/icons-react-native/IconBox";
29import IconBrain from "@tabler/icons-react-native/IconBrain";
30import IconCamera from "@tabler/icons-react-native/IconCamera";
31import IconCar from "@tabler/icons-react-native/IconCar";
32import IconChartBar from "@tabler/icons-react-native/IconChartBar";
33import IconCheck from "@tabler/icons-react-native/IconCheck";
34import IconCloud from "@tabler/icons-react-native/IconCloud";
35import IconChevronDown from "@tabler/icons-react-native/IconChevronDown";
36import IconChevronLeft from "@tabler/icons-react-native/IconChevronLeft";
37import IconChevronRight from "@tabler/icons-react-native/IconChevronRight";
38import IconChevronUp from "@tabler/icons-react-native/IconChevronUp";
39import IconCircle from "@tabler/icons-react-native/IconCircle";
40import IconCircleCheck from "@tabler/icons-react-native/IconCircleCheck";
41import IconCircleDashed from "@tabler/icons-react-native/IconCircleDashed";
42import IconCircleDot from "@tabler/icons-react-native/IconCircleDot";
43import IconCircleXFilled from "@tabler/icons-react-native/IconCircleXFilled";
44import IconTicket from "@tabler/icons-react-native/IconTicket";
45import IconClock from "@tabler/icons-react-native/IconClock";
46import IconCode from "@tabler/icons-react-native/IconCode";
47import IconCopy from "@tabler/icons-react-native/IconCopy";
48import IconDatabase from "@tabler/icons-react-native/IconDatabase";
49import IconDeviceDesktop from "@tabler/icons-react-native/IconDeviceDesktop";
50import IconDeviceLaptop from "@tabler/icons-react-native/IconDeviceLaptop";
51import IconDeviceMobile from "@tabler/icons-react-native/IconDeviceMobile";
52import IconDots from "@tabler/icons-react-native/IconDots";
53import IconPlusMinus from "@tabler/icons-react-native/IconPlusMinus";
54import IconDotsVertical from "@tabler/icons-react-native/IconDotsVertical";
55import IconDotsCircleHorizontal from "@tabler/icons-react-native/IconDotsCircleHorizontal";
56import IconEdit from "@tabler/icons-react-native/IconEdit";
57import IconExternalLink from "@tabler/icons-react-native/IconExternalLink";
58import IconEyeOff from "@tabler/icons-react-native/IconEyeOff";
59import IconEye from "@tabler/icons-react-native/IconEye";
60import IconFileText from "@tabler/icons-react-native/IconFileText";
61import IconFileDiff from "@tabler/icons-react-native/IconFileDiff";
62import IconFilter from "@tabler/icons-react-native/IconFilter";
63import IconFilterFilled from "@tabler/icons-react-native/IconFilterFilled";
64import IconFolder from "@tabler/icons-react-native/IconFolder";
65import IconFolderOpen from "@tabler/icons-react-native/IconFolderOpen";
66import IconHeadphones from "@tabler/icons-react-native/IconHeadphones";
67import IconFolderPlus from "@tabler/icons-react-native/IconFolderPlus";
68import IconGitPullRequestClosed from "@tabler/icons-react-native/IconGitPullRequestClosed";
69import IconGitPullRequestDraft from "@tabler/icons-react-native/IconGitPullRequestDraft";
70import IconGitCommit from "@tabler/icons-react-native/IconGitCommit";
71import IconGitBranch from "@tabler/icons-react-native/IconGitBranch";
72import IconGitMerge from "@tabler/icons-react-native/IconGitMerge";
73import IconGitPullRequest from "@tabler/icons-react-native/IconGitPullRequest";
74import IconHammer from "@tabler/icons-react-native/IconHammer";
75import IconHome from "@tabler/icons-react-native/IconHome";
76import IconInfoCircle from "@tabler/icons-react-native/IconInfoCircle";
77import IconKeyboard from "@tabler/icons-react-native/IconKeyboard";
78import IconKeyboardHide from "@tabler/icons-react-native/IconKeyboardHide";
79import IconLock from "@tabler/icons-react-native/IconLock";
80import IconLayoutColumns from "@tabler/icons-react-native/IconLayoutColumns";
81import IconLayoutSidebar from "@tabler/icons-react-native/IconLayoutSidebar";
82import IconLayoutSidebarRight from "@tabler/icons-react-native/IconLayoutSidebarRight";
83import IconLetterSpacing from "@tabler/icons-react-native/IconLetterSpacing";
84import IconMicrophone from "@tabler/icons-react-native/IconMicrophone";
85import IconLink from "@tabler/icons-react-native/IconLink";
86import IconListNumbers from "@tabler/icons-react-native/IconListNumbers";
87import IconMenu2 from "@tabler/icons-react-native/IconMenu2";
88import IconMessageCircleQuestion from "@tabler/icons-react-native/IconMessageCircleQuestion";
89import IconMessage from "@tabler/icons-react-native/IconMessage";
90import IconMessagePlus from "@tabler/icons-react-native/IconMessagePlus";
91import IconMinus from "@tabler/icons-react-native/IconMinus";
92import IconMoon from "@tabler/icons-react-native/IconMoon";
93import IconNetwork from "@tabler/icons-react-native/IconNetwork";
94import IconPalette from "@tabler/icons-react-native/IconPalette";
95import IconPencil from "@tabler/icons-react-native/IconPencil";
96import IconPlug from "@tabler/icons-react-native/IconPlug";
97import IconPhoto from "@tabler/icons-react-native/IconPhoto";
98import IconPin from "@tabler/icons-react-native/IconPin";
99import IconPinnedOff from "@tabler/icons-react-native/IconPinnedOff";
100import IconPlayerPlay from "@tabler/icons-react-native/IconPlayerPlay";
101import IconPlayerStopFilled from "@tabler/icons-react-native/IconPlayerStopFilled";
102import IconPlus from "@tabler/icons-react-native/IconPlus";
103import IconQrcode from "@tabler/icons-react-native/IconQrcode";
104import IconRefresh from "@tabler/icons-react-native/IconRefresh";
105import IconSearch from "@tabler/icons-react-native/IconSearch";
106import IconServer from "@tabler/icons-react-native/IconServer";
107import IconSettings from "@tabler/icons-react-native/IconSettings";
108import IconSparkles from "@tabler/icons-react-native/IconSparkles";
109import IconStack2 from "@tabler/icons-react-native/IconStack2";
110import IconStar from "@tabler/icons-react-native/IconStar";
111import IconStarFilled from "@tabler/icons-react-native/IconStarFilled";
112import IconStethoscope from "@tabler/icons-react-native/IconStethoscope";
113import IconSun from "@tabler/icons-react-native/IconSun";
114import IconShieldQuestion from "@tabler/icons-react-native/IconShieldQuestion";
115import IconTarget from "@tabler/icons-react-native/IconTarget";
116import IconTerminal2 from "@tabler/icons-react-native/IconTerminal2";
117import IconTextDecrease from "@tabler/icons-react-native/IconTextDecrease";
118import IconTextIncrease from "@tabler/icons-react-native/IconTextIncrease";
119import IconTool from "@tabler/icons-react-native/IconTool";
120import IconTrash from "@tabler/icons-react-native/IconTrash";
121import IconTypography from "@tabler/icons-react-native/IconTypography";
122import IconUpload from "@tabler/icons-react-native/IconUpload";
123import IconUserCircle from "@tabler/icons-react-native/IconUserCircle";
124import IconUserCheck from "@tabler/icons-react-native/IconUserCheck";
125import IconUserX from "@tabler/icons-react-native/IconUserX";
126import IconUserQuestion from "@tabler/icons-react-native/IconUserQuestion";
127import IconUserPlus from "@tabler/icons-react-native/IconUserPlus";
128import IconUsers from "@tabler/icons-react-native/IconUsers";
129import IconWifi from "@tabler/icons-react-native/IconWifi";
130import IconWifiOff from "@tabler/icons-react-native/IconWifiOff";
131import IconWorld from "@tabler/icons-react-native/IconWorld";
132import IconX from "@tabler/icons-react-native/IconX";
133import type { AndroidSymbol, SFSymbol, SymbolViewProps } from "expo-symbols";
134import { withUniwind } from "uniwind";
135
136const ANDROID_ICON_BY_SF_SYMBOL = {
137 plusminus: IconPlusMinus,
138 "git.pull-request.closed": IconGitPullRequestClosed,
139 "git.pull-request.draft": IconGitPullRequestDraft,
140 "git.commit": IconGitCommit,
141 "arrow.branch": IconGitBranch,
142 "arrow.left": IconArrowLeft,
143 "arrow.right": IconArrowRight,
144 "arrow.left.arrow.right": IconArrowsLeftRight,
145 "arrow.clockwise": IconRefresh,
146 "arrow.down": IconArrowDown,
147 "arrow.down.circle": IconArrowDownCircle,
148 "arrow.right.circle": IconArrowRightCircle,
149 "arrow.triangle.branch": IconGitBranch,
150 "arrow.triangle.merge": IconGitMerge,
151 "arrow.triangle.pull": IconGitPullRequest,
152 "square.3.layers.3d": IconStack2,
153 "arrow.turn.left.up": IconArrowBackUp,
154 "arrow.up": IconArrowUp,
155 "arrow.up.circle": IconArrowUpCircle,
156 "arrow.up.left.and.arrow.down.right": IconArrowsDiagonal2,
157 "arrow.down.right.and.arrow.up.left": IconArrowsMinimize,
158 "arrow.up.right": IconArrowUpRight,
159 "arrow.up.right.circle": IconArrowUpRightCircle,
160 "arrow.uturn.backward": IconArrowBackUp,
161 "arrow.uturn.forward": IconArrowForwardUp,
162 archivebox: IconArchive,
163 "archivebox.fill": IconArchive,
164 "bell.badge": IconBellRinging,
165 "bolt.circle": IconBolt,
166 "bolt.horizontal.circle": IconBolt,
167 brain: IconBrain,
168 "cable.connector": IconPlug,
169 camera: IconCamera,
170 car: IconCar,
171 "chart.bar.xaxis": IconChartBar,
172 checkmark: IconCheck,
173 "checkmark.circle": IconCircleCheck,
174 "checkmark.circle.fill": IconCircleCheck,
175 circle: IconCircle,
176 "circle.dashed": IconCircleDashed,
177 "circle.inset.filled": IconCircleDot,
178 "circle.dashed.inset.filled": IconCircleDot,
179 clock: IconClock,
180 timer: IconClock,
181 ticket: IconTicket,
182 cloud: IconCloud,
183 cube: IconBox,
184 "chevron.down": IconChevronDown,
185 "chevron.left": IconChevronLeft,
186 "chevron.left.forwardslash.chevron.right": IconCode,
187 "chevron.right": IconChevronRight,
188 "chevron.up": IconChevronUp,
189 desktopcomputer: IconDeviceDesktop,
190 doc: IconFileText,
191 "doc.on.doc": IconCopy,
192 "doc.text": IconFileText,
193 "file.diff": IconFileDiff,
194 ellipsis: IconDots,
195 moon: IconMoon,
196 "ellipsis.circle": IconDotsCircleHorizontal,
197 "exclamationmark.triangle": IconAlertTriangle,
198 "exclamationmark.circle": IconAlertCircle,
199 eye: IconEye,
200 "eye.slash": IconEyeOff,
201 folder: IconFolder,
202 "folder.badge.plus": IconFolderPlus,
203 "folder.fill": IconFolder,
204 gearshape: IconSettings,
205 globe: IconWorld,
206 hammer: IconHammer,
207 headphones: IconHeadphones,
208 house: IconHome,
209 "info.circle": IconInfoCircle,
210 internaldrive: IconDatabase,
211 iphone: IconDeviceMobile,
212 keyboard: IconKeyboard,
213 laptopcomputer: IconDeviceLaptop,
214 link: IconLink,
215 "list.number": IconListNumbers,
216 "line.3.horizontal": IconMenu2,
217 "line.3.horizontal.decrease": IconFilter,
218 "line.3.horizontal.decrease.circle": IconFilter,
219 "line.3.horizontal.decrease.circle.fill": IconFilterFilled,
220 // Tabler has no Apple desktops; the closest silhouettes stand in on Android.
221 macmini: IconServer,
222 macstudio: IconDeviceDesktop,
223 magnifyingglass: IconSearch,
224 mic: IconMicrophone,
225 paintbrush: IconPalette,
226 pencil: IconPencil,
227 "person.crop.circle": IconUserCircle,
228 "person.2": IconUsers,
229 "person.fill.checkmark": IconUserCheck,
230 "person.fill.xmark": IconUserX,
231 "person.badge.clock": IconUserQuestion,
232 "person.badge.plus": IconUserPlus,
233 photo: IconPhoto,
234 pin: IconPin,
235 "pin.slash": IconPinnedOff,
236 play: IconPlayerPlay,
237 plus: IconPlus,
238 minus: IconMinus,
239 "qrcode.viewfinder": IconQrcode,
240 "point.3.connected.trianglepath.dotted": IconNetwork,
241 "point.topleft.down.curvedto.point.bottomright.up": IconGitMerge,
242 safari: IconExternalLink,
243 "server.rack": IconServer,
244 stethoscope: IconStethoscope,
245 "sidebar.left": IconLayoutSidebar,
246 "sidebar.right": IconLayoutSidebarRight,
247 "slider.horizontal.3": IconAdjustmentsHorizontal,
248 "square.and.pencil": IconEdit,
249 "square.on.square": IconCopy,
250 "square.grid.2x2": IconApps,
251 "square.split.2x1": IconLayoutColumns,
252 star: IconStar,
253 "star.fill": IconStarFilled,
254 "sun.max": IconSun,
255 "stop.fill": IconPlayerStopFilled,
256 target: IconTarget,
257 terminal: IconTerminal2,
258 "text.alignleft": IconAlignLeft,
259 "text.bubble": IconMessage,
260 "questionmark.bubble": IconMessageCircleQuestion,
261 "exclamationmark.shield": IconShieldQuestion,
262 "bubble.left.and.bubble.right": IconMessage,
263 "bubble.left": IconMessage,
264 sparkles: IconSparkles,
265 "plus.bubble": IconMessagePlus,
266 "text.word.spacing": IconLetterSpacing,
267 "textformat.size": IconTypography,
268 "textformat.size.larger": IconTextIncrease,
269 "textformat.size.smaller": IconTextDecrease,
270 "tray.and.arrow.up": IconUpload,
271 trash: IconTrash,
272 wifi: IconWifi,
273 "wifi.slash": IconWifiOff,
274 xmark: IconX,
275 "xmark.circle.fill": IconCircleXFilled,
276} satisfies Partial<
277 Record<
278 SFSymbol | "git.pull-request.closed" | "git.pull-request.draft" | "git.commit" | "file.diff",
279 Icon
280 >
281>;
282const SF_ICON_LOOKUP: Partial<
283 Record<
284 SFSymbol | "git.pull-request.closed" | "git.pull-request.draft" | "git.commit" | "file.diff",
285 Icon
286 >
287> = ANDROID_ICON_BY_SF_SYMBOL;
288
289// Callers can pass `{ ios, android }` names where `android` is a Material
290// icon name (the raw expo-symbols contract). Resolve those here too so the
291// android key keeps working through this wrapper — it wins over the SF map
292// when both match (e.g. folder vs folder_open for expanded project groups).
293const ANDROID_ICON_BY_MATERIAL_NAME = {
294 auto_awesome: IconSparkles,
295 bolt: IconBolt,
296 build: IconTool,
297 chat: IconMessage,
298 chat_bubble: IconMessage,
299 check: IconCheck,
300 close: IconX,
301 construction: IconHammer,
302 content_copy: IconCopy,
303 desktop_windows: IconDeviceDesktop,
304 edit: IconEdit,
305 error: IconAlertCircle,
306 folder: IconFolder,
307 folder_open: IconFolderOpen,
308 keyboard: IconKeyboard,
309 keyboard_arrow_down: IconChevronDown,
310 keyboard_arrow_up: IconChevronUp,
311 keyboard_hide: IconKeyboardHide,
312 lock: IconLock,
313 more_vert: IconDotsVertical,
314 merge: IconGitMerge,
315 psychology: IconBrain,
316 public: IconWorld,
317 remove: IconMinus,
318 smartphone: IconDeviceMobile,
319 terminal: IconTerminal2,
320 visibility: IconEye,
321} satisfies Partial<Record<AndroidSymbol, Icon>>;
322
323export type { SFSymbol } from "expo-symbols";
324export type AppSymbolName =
325 | keyof typeof ANDROID_ICON_BY_SF_SYMBOL
326 | {
327 ios: SFSymbol;
328 android: keyof typeof ANDROID_ICON_BY_MATERIAL_NAME;
329 };
330
331export type AppSymbolViewProps = Omit<SymbolViewProps, "name"> & { name: AppSymbolName };
332
333export function isAppSymbolName(name: string): name is Extract<AppSymbolName, string> {
334 return Object.prototype.hasOwnProperty.call(ANDROID_ICON_BY_SF_SYMBOL, name);
335}
336
337function AppSymbolView(props: AppSymbolViewProps) {
338 const materialName = typeof props.name === "string" ? undefined : props.name.android;
339 const sfSymbol = typeof props.name === "string" ? props.name : props.name.ios;
340 const AndroidIcon =
341 (materialName ? ANDROID_ICON_BY_MATERIAL_NAME[materialName] : undefined) ??
342 (sfSymbol ? SF_ICON_LOOKUP[sfSymbol] : undefined);
343
344 if (!AndroidIcon) {
345 return props.fallback ?? null;
346 }
347
348 return (
349 <AndroidIcon
350 accessibilityLabel={props.accessibilityLabel}
351 color={props.tintColor}
352 size={props.size}
353 strokeWidth={2}
354 style={props.style}
355 testID={props.testID}
356 />
357 );
358}
359
360/**
361 * expo-symbols and the Android Tabler fallback both expose tint as a native
362 * prop rather than a React Native style. Keep that third-party boundary here
363 * so callers can use Uniwind's `tintColorClassName` instead of subscribing to
364 * theme variables in every parent component.
365 */
366export const SymbolView = withUniwind(AppSymbolView);