diff --git a/src/app/call/new/index.tsx b/src/app/call/new/index.tsx index 47a68bd..be593f9 100644 --- a/src/app/call/new/index.tsx +++ b/src/app/call/new/index.tsx @@ -1,7 +1,6 @@ import { zodResolver } from '@hookform/resolvers/zod'; import * as Location from 'expo-location'; import { type Href, router, Stack } from 'expo-router'; -import { BookOpenIcon, ChevronDownIcon, ChevronUpIcon, FileTextIcon, LinkIcon, PlusIcon, SearchIcon, UserIcon } from 'lucide-react-native'; import { useColorScheme } from 'nativewind'; import React, { useEffect, useState } from 'react'; import { Controller, useForm } from 'react-hook-form'; @@ -32,6 +31,7 @@ import { Card } from '@/components/ui/card'; import { FocusAwareStatusBar } from '@/components/ui/focus-aware-status-bar'; import { FormControl, FormControlError, FormControlLabel, FormControlLabelText } from '@/components/ui/form-control'; import { Input, InputField } from '@/components/ui/input'; +import { BookOpenIcon, ChevronDownIcon, ChevronUpIcon, FileTextIcon, LinkIcon, PlusIcon, SearchIcon, UserIcon } from '@/components/ui/lucide-icons'; import { Select, SelectBackdrop, SelectContent, SelectIcon, SelectInput, SelectItem, SelectPortal, SelectTrigger } from '@/components/ui/select'; import { Text } from '@/components/ui/text'; import { Textarea, TextareaInput } from '@/components/ui/textarea'; diff --git a/src/app/lockscreen.tsx b/src/app/lockscreen.tsx index a59778e..4fa29a0 100644 --- a/src/app/lockscreen.tsx +++ b/src/app/lockscreen.tsx @@ -1,6 +1,5 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { type Href, useRouter } from 'expo-router'; -import { AlertTriangle, EyeIcon, EyeOffIcon, LockKeyhole } from 'lucide-react-native'; import { useColorScheme } from 'nativewind'; import React, { useState } from 'react'; import type { SubmitHandler } from 'react-hook-form'; @@ -15,6 +14,7 @@ import { FocusAwareStatusBar } from '@/components/ui'; import { Button, ButtonSpinner, ButtonText } from '@/components/ui/button'; import { FormControl, FormControlError, FormControlErrorIcon, FormControlErrorText, FormControlLabel, FormControlLabelText } from '@/components/ui/form-control'; import { Input, InputField, InputIcon, InputSlot } from '@/components/ui/input'; +import { AlertTriangle, EyeIcon, EyeOffIcon, LockKeyhole } from '@/components/ui/lucide-icons'; import { Pressable } from '@/components/ui/pressable'; import { Text } from '@/components/ui/text'; import colors from '@/constants/colors'; diff --git a/src/app/maintenance.tsx b/src/app/maintenance.tsx index 4d9ad65..dc08f8d 100644 --- a/src/app/maintenance.tsx +++ b/src/app/maintenance.tsx @@ -1,11 +1,11 @@ import { type Href, useRouter } from 'expo-router'; -import { AlertCircle, Clock, Mail } from 'lucide-react-native'; import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Image, ScrollView } from 'react-native'; import { View } from '@/components/ui'; import { FocusAwareStatusBar } from '@/components/ui'; +import { AlertCircle, Clock, Mail } from '@/components/ui/lucide-icons'; import { Text } from '@/components/ui/text'; import { Env } from '@/lib/env'; import { logger } from '@/lib/logging'; diff --git a/src/components/calls/call-detail-menu.tsx b/src/components/calls/call-detail-menu.tsx index 8608361..d0fc7f5 100644 --- a/src/components/calls/call-detail-menu.tsx +++ b/src/components/calls/call-detail-menu.tsx @@ -1,4 +1,3 @@ -import { CalendarClockIcon, EditIcon, MoreVerticalIcon, Trash2Icon, UserPlusIcon, XIcon } from 'lucide-react-native'; import { useColorScheme } from 'nativewind'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -6,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import { Pressable } from '@/components/ui/'; import { Actionsheet, ActionsheetBackdrop, ActionsheetContent, ActionsheetDragIndicator, ActionsheetDragIndicatorWrapper, ActionsheetItem, ActionsheetItemText } from '@/components/ui/actionsheet'; import { HStack } from '@/components/ui/hstack'; +import { CalendarClockIcon, EditIcon, MoreVerticalIcon, Trash2Icon, UserPlusIcon, XIcon } from '@/components/ui/lucide-icons'; import { useAnalytics } from '@/hooks/use-analytics'; interface CallDetailMenuProps { diff --git a/src/components/calls/call-notes-modal.tsx b/src/components/calls/call-notes-modal.tsx index 0d6733d..85ef607 100644 --- a/src/components/calls/call-notes-modal.tsx +++ b/src/components/calls/call-notes-modal.tsx @@ -1,12 +1,12 @@ import type { BottomSheetBackdropProps } from '@gorhom/bottom-sheet'; import BottomSheet, { BottomSheetBackdrop, BottomSheetView } from '@gorhom/bottom-sheet'; -import { SearchIcon, X } from 'lucide-react-native'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Keyboard, Platform, useWindowDimensions } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'; +import { SearchIcon, X } from '@/components/ui/lucide-icons'; import { useAnalytics } from '@/hooks/use-analytics'; import { useAuthStore } from '@/lib/auth'; import { useCallDetailStore } from '@/stores/calls/detail-store'; diff --git a/src/components/calls/dispatch-selection-modal.tsx b/src/components/calls/dispatch-selection-modal.tsx index 47a2f39..d19610c 100644 --- a/src/components/calls/dispatch-selection-modal.tsx +++ b/src/components/calls/dispatch-selection-modal.tsx @@ -1,4 +1,3 @@ -import { CheckIcon, SearchIcon, UsersIcon, X } from 'lucide-react-native'; import { useColorScheme } from 'nativewind'; import React, { useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -11,6 +10,7 @@ import { Button, ButtonText } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { HStack } from '@/components/ui/hstack'; import { Input, InputField } from '@/components/ui/input'; +import { CheckIcon, SearchIcon, UsersIcon, X } from '@/components/ui/lucide-icons'; import { Text } from '@/components/ui/text'; import { VStack } from '@/components/ui/vstack'; import { type DispatchSelection, useDispatchStore } from '@/stores/dispatch/store'; diff --git a/src/components/common/loading.tsx b/src/components/common/loading.tsx index 0d36c4d..80c7299 100644 --- a/src/components/common/loading.tsx +++ b/src/components/common/loading.tsx @@ -1,8 +1,9 @@ -import { Box, Loader2 } from 'lucide-react-native'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { View } from 'react-native'; +import { Box, Loader2 } from '@/components/ui/lucide-icons'; + import { HStack } from '../ui/hstack'; import { Spinner } from '../ui/spinner'; import { Text } from '../ui/text'; diff --git a/src/components/contacts/contact-card.tsx b/src/components/contacts/contact-card.tsx index 5ed7462..76e911b 100644 --- a/src/components/contacts/contact-card.tsx +++ b/src/components/contacts/contact-card.tsx @@ -1,8 +1,8 @@ -import { BuildingIcon, MailIcon, PhoneIcon, StarIcon, UserIcon } from 'lucide-react-native'; import React from 'react'; import { Pressable, Text, View } from 'react-native'; import { Avatar, AvatarImage } from '@/components/ui/avatar'; +import { BuildingIcon, MailIcon, PhoneIcon, StarIcon, UserIcon } from '@/components/ui/lucide-icons'; import { type ContactResultData, ContactType } from '@/models/v4/contacts/contactResultData'; interface ContactCardProps { diff --git a/src/components/contacts/contact-details-sheet.tsx b/src/components/contacts/contact-details-sheet.tsx index 980ac20..7ce3c0e 100644 --- a/src/components/contacts/contact-details-sheet.tsx +++ b/src/components/contacts/contact-details-sheet.tsx @@ -1,3 +1,9 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ScrollView, useWindowDimensions, View } from 'react-native'; + +import { UdfFieldsRenderer } from '@/components/calls/udf-fields-renderer'; +import { Avatar, AvatarImage } from '@/components/ui/avatar'; import { BuildingIcon, CalendarIcon, @@ -15,13 +21,7 @@ import { TrashIcon, UserIcon, X, -} from 'lucide-react-native'; -import React, { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { ScrollView, useWindowDimensions, View } from 'react-native'; - -import { UdfFieldsRenderer } from '@/components/calls/udf-fields-renderer'; -import { Avatar, AvatarImage } from '@/components/ui/avatar'; +} from '@/components/ui/lucide-icons'; import { useAnalytics } from '@/hooks/use-analytics'; import { ContactType } from '@/models/v4/contacts/contactResultData'; import { useContactsStore } from '@/stores/contacts/store'; diff --git a/src/components/dispatch-console/stats-header.tsx b/src/components/dispatch-console/stats-header.tsx index 303b607..b703003 100644 --- a/src/components/dispatch-console/stats-header.tsx +++ b/src/components/dispatch-console/stats-header.tsx @@ -1,4 +1,3 @@ -import { AlertTriangle, CalendarClock, Clock, CloudLightning, Phone, Truck, User, Users } from 'lucide-react-native'; import { useColorScheme } from 'nativewind'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -7,6 +6,7 @@ import { Pressable, StyleSheet, View } from 'react-native'; import { Box } from '@/components/ui/box'; import { HStack } from '@/components/ui/hstack'; import { Icon } from '@/components/ui/icon'; +import { AlertTriangle, CalendarClock, Clock, CloudLightning, Phone, Truck, User, Users } from '@/components/ui/lucide-icons'; import { Text } from '@/components/ui/text'; import { VStack } from '@/components/ui/vstack'; diff --git a/src/components/incident-command/command-board-view.tsx b/src/components/incident-command/command-board-view.tsx index 928c710..b55ceb6 100644 --- a/src/components/incident-command/command-board-view.tsx +++ b/src/components/incident-command/command-board-view.tsx @@ -1,4 +1,12 @@ import { type Href, useRouter } from 'expo-router'; +import React, { useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Alert, Platform } from 'react-native'; + +import { Box } from '@/components/ui/box'; +import { Button, ButtonIcon, ButtonText } from '@/components/ui/button'; +import { Heading } from '@/components/ui/heading'; +import { HStack } from '@/components/ui/hstack'; import { CheckCircleIcon, ChevronDownIcon, @@ -17,15 +25,7 @@ import { UserCogIcon, UserPlusIcon, XIcon, -} from 'lucide-react-native'; -import React, { useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Alert, Platform } from 'react-native'; - -import { Box } from '@/components/ui/box'; -import { Button, ButtonIcon, ButtonText } from '@/components/ui/button'; -import { Heading } from '@/components/ui/heading'; -import { HStack } from '@/components/ui/hstack'; +} from '@/components/ui/lucide-icons'; import { Text } from '@/components/ui/text'; import { VStack } from '@/components/ui/vstack'; import { useDirectMessage } from '@/hooks/use-direct-message'; diff --git a/src/components/incident-command/incident-command-tab.tsx b/src/components/incident-command/incident-command-tab.tsx index d7874c6..c8b8661 100644 --- a/src/components/incident-command/incident-command-tab.tsx +++ b/src/components/incident-command/incident-command-tab.tsx @@ -1,5 +1,4 @@ import { type Href, useRouter } from 'expo-router'; -import { ExpandIcon, NetworkIcon } from 'lucide-react-native'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -7,6 +6,7 @@ import { Loading } from '@/components/common/loading'; import { Box } from '@/components/ui/box'; import { Button, ButtonIcon, ButtonText } from '@/components/ui/button'; import { HStack } from '@/components/ui/hstack'; +import { ExpandIcon, NetworkIcon } from '@/components/ui/lucide-icons'; import { Text } from '@/components/ui/text'; import { VStack } from '@/components/ui/vstack'; import { useChatStore } from '@/stores/chat/store'; diff --git a/src/components/notes/note-details-sheet.tsx b/src/components/notes/note-details-sheet.tsx index d8b079c..98a885f 100644 --- a/src/components/notes/note-details-sheet.tsx +++ b/src/components/notes/note-details-sheet.tsx @@ -1,10 +1,10 @@ -import { Calendar, Tag, X } from 'lucide-react-native'; import { useColorScheme } from 'nativewind'; import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { StyleSheet } from 'react-native'; import WebView from 'react-native-webview'; +import { Calendar, Tag, X } from '@/components/ui/lucide-icons'; import { useAnalytics } from '@/hooks/use-analytics'; import { formatDateForDisplay, parseDateISOString } from '@/lib/utils'; import { useNotesStore } from '@/stores/notes/store'; diff --git a/src/components/notifications/NotificationButton.tsx b/src/components/notifications/NotificationButton.tsx index 3429366..0f49f03 100644 --- a/src/components/notifications/NotificationButton.tsx +++ b/src/components/notifications/NotificationButton.tsx @@ -1,8 +1,8 @@ import { useCounts } from '@novu/react-native'; -import { BellIcon } from 'lucide-react-native'; import React from 'react'; import { ActivityIndicator, Pressable, View } from '@/components/ui'; +import { BellIcon } from '@/components/ui/lucide-icons'; import { Text } from '@/components/ui/text'; interface NotificationButtonProps { onPress: () => void; diff --git a/src/components/notifications/NotificationDetail.tsx b/src/components/notifications/NotificationDetail.tsx index 5654e94..c3cea73 100644 --- a/src/components/notifications/NotificationDetail.tsx +++ b/src/components/notifications/NotificationDetail.tsx @@ -1,7 +1,9 @@ import { useNotifications } from '@novu/react-native'; -import { ArrowLeft, Calendar, ExternalLink, Trash2 } from 'lucide-react-native'; +import { useColorScheme } from 'nativewind'; import React, { useEffect } from 'react'; -import { Animated, Appearance, Dimensions, Platform, Pressable, SafeAreaView, StatusBar, StyleSheet, Text, View } from 'react-native'; +import { Animated, Dimensions, Platform, Pressable, SafeAreaView, StatusBar, type StyleProp, StyleSheet, Text, View, type ViewStyle } from 'react-native'; + +import { ArrowLeft, Calendar, ExternalLink, Trash2 } from '@/components/ui/lucide-icons'; // Define the interface directly in this file interface NotificationPayload { @@ -28,6 +30,7 @@ const SIDEBAR_WIDTH = Math.min(width * 0.85, 400); const STATUS_BAR_HEIGHT = Platform.OS === 'ios' ? 44 : StatusBar.currentHeight || 0; export const NotificationDetail = ({ notification, onClose, onDelete, onNavigateToReference }: NotificationDetailProps) => { + const styles = useStyles(); const { refetch } = useNotifications(); const slideAnim = React.useRef(new Animated.Value(SIDEBAR_WIDTH)).current; const fadeAnim = React.useRef(new Animated.Value(0)).current; @@ -124,7 +127,7 @@ export const NotificationDetail = ({ notification, onClose, onDelete, onNavigate {notification.type ? ( - + {notification.type} ) : null} @@ -176,7 +179,7 @@ const formatValue = (value: any): string => { }; // Helper function to get tag style based on notification type -const getTypeTagStyle = (type: string): any => { +const getTypeTagStyle = (type: string, styles: ReturnType): StyleProp => { const lowerType = type.toLowerCase(); if (lowerType.includes('alert') || lowerType.includes('emergency')) { @@ -192,172 +195,173 @@ const getTypeTagStyle = (type: string): any => { } }; -// Styles are computed once at module load, so resolve the color scheme a single -// time here instead of calling Appearance.getColorScheme() in every style rule. -const isDark = Appearance.getColorScheme() === 'dark'; - -const styles = StyleSheet.create({ - backdrop: { - backgroundColor: 'rgba(0, 0, 0, 0.5)', - zIndex: 9999, - }, - backdropPressable: { - width: '100%', - height: '100%', - }, - sidebarContainer: { - position: 'absolute', - top: 0, - right: 0, - width: SIDEBAR_WIDTH, - height: '100%', - backgroundColor: isDark ? '#171717' : '#fff', - shadowColor: isDark ? '#262626' : '#e5e5e5', - shadowOffset: { - width: -2, - height: 0, +const createStyles = (isDark: boolean) => + StyleSheet.create({ + backdrop: { + backgroundColor: 'rgba(0, 0, 0, 0.5)', + zIndex: 9999, + }, + backdropPressable: { + width: '100%', + height: '100%', + }, + sidebarContainer: { + position: 'absolute', + top: 0, + right: 0, + width: SIDEBAR_WIDTH, + height: '100%', + backgroundColor: isDark ? '#171717' : '#fff', + shadowColor: isDark ? '#262626' : '#e5e5e5', + shadowOffset: { + width: -2, + height: 0, + }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + elevation: 5, + zIndex: 10000, + }, + safeArea: { + flex: 1, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 16, + paddingTop: Platform.OS === 'android' ? STATUS_BAR_HEIGHT + 16 : 16, + borderBottomWidth: 1, + borderBottomColor: isDark ? '#333333' : '#e5e5e5', + }, + headerTitle: { + fontSize: 18, + fontWeight: 'bold', + flex: 1, + textAlign: 'center', + color: isDark ? '#f3f4f6' : '#111827', + }, + backButton: { + padding: 8, + }, + deleteButton: { + padding: 8, + }, + content: { + padding: 20, + }, + metadataContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginBottom: 16, + }, + dateContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + dateText: { + fontSize: 14, + color: isDark ? '#9ca3af' : '#6b7280', + marginLeft: 6, + }, + timeText: { + fontSize: 14, + color: isDark ? '#9ca3af' : '#6b7280', + }, + typeTag: { + alignSelf: 'flex-start', + paddingHorizontal: 10, + paddingVertical: 4, + borderRadius: 16, + marginBottom: 16, + }, + typeTagText: { + fontSize: 12, + fontWeight: '500', + textTransform: 'uppercase', + }, + typeTagDefault: { + backgroundColor: isDark ? '#374151' : '#e5e7eb', }, - shadowOpacity: 0.25, - shadowRadius: 3.84, - elevation: 5, - zIndex: 10000, - }, - safeArea: { - flex: 1, - }, - header: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - padding: 16, - paddingTop: Platform.OS === 'android' ? STATUS_BAR_HEIGHT + 16 : 16, - borderBottomWidth: 1, - borderBottomColor: isDark ? '#333333' : '#e5e5e5', - }, - headerTitle: { - fontSize: 18, - fontWeight: 'bold', - flex: 1, - textAlign: 'center', - color: isDark ? '#f3f4f6' : '#111827', - }, - backButton: { - padding: 8, - }, - deleteButton: { - padding: 8, - }, - content: { - padding: 20, - }, - metadataContainer: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - marginBottom: 16, - }, - dateContainer: { - flexDirection: 'row', - alignItems: 'center', - }, - dateText: { - fontSize: 14, - color: isDark ? '#9ca3af' : '#6b7280', - marginLeft: 6, - }, - timeText: { - fontSize: 14, - color: isDark ? '#9ca3af' : '#6b7280', - }, - typeTag: { - alignSelf: 'flex-start', - paddingHorizontal: 10, - paddingVertical: 4, - borderRadius: 16, - marginBottom: 16, - }, - typeTagText: { - fontSize: 12, - fontWeight: '500', - textTransform: 'uppercase', - }, - typeTagDefault: { - backgroundColor: isDark ? '#374151' : '#e5e7eb', - }, - typeTagInfo: { - backgroundColor: isDark ? '#1e40af' : '#dbeafe', - }, - typeTagSuccess: { - backgroundColor: isDark ? '#065f46' : '#d1fae5', - }, - typeTagWarning: { - backgroundColor: isDark ? '#92400e' : '#fef3c7', - }, - typeTagAlert: { - backgroundColor: isDark ? '#991b1b' : '#fee2e2', - }, - title: { - fontSize: 20, - fontWeight: 'bold', - marginBottom: 12, - color: isDark ? '#f3f4f6' : '#111827', - }, - bodyContainer: { - backgroundColor: isDark ? '#262626' : '#f9fafb', - padding: 16, - borderRadius: 8, - marginBottom: 20, - }, - body: { - fontSize: 16, - lineHeight: 24, - color: isDark ? '#e5e5e5' : '#374151', - }, - metadataDetailsContainer: { - marginTop: 10, - padding: 16, - backgroundColor: isDark ? '#262626' : '#f9fafb', - borderRadius: 8, - }, - metadataTitle: { - fontSize: 16, - fontWeight: '600', - marginBottom: 10, - color: isDark ? '#f3f4f6' : '#111827', - }, - metadataItem: { - flexDirection: 'row', - marginBottom: 6, - }, - metadataKey: { - fontSize: 14, - fontWeight: '500', - marginRight: 8, - color: isDark ? '#9ca3af' : '#6b7280', - }, - metadataValue: { - fontSize: 14, - flex: 1, - color: isDark ? '#e5e5e5' : '#111827', - }, - buttonText: { - fontSize: 16, - fontWeight: '500', - color: isDark ? '#3b82f6' : '#2563eb', - }, - referenceButton: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - marginTop: 24, - backgroundColor: isDark ? '#1e3a8a' : '#dbeafe', - padding: 12, - borderRadius: 8, - borderWidth: 1, - borderColor: isDark ? '#3b82f6' : '#60a5fa', - }, - referenceButtonIcon: { - marginRight: 8, - color: isDark ? '#3b82f6' : '#2563eb', - }, -}); + typeTagInfo: { + backgroundColor: isDark ? '#1e40af' : '#dbeafe', + }, + typeTagSuccess: { + backgroundColor: isDark ? '#065f46' : '#d1fae5', + }, + typeTagWarning: { + backgroundColor: isDark ? '#92400e' : '#fef3c7', + }, + typeTagAlert: { + backgroundColor: isDark ? '#991b1b' : '#fee2e2', + }, + title: { + fontSize: 20, + fontWeight: 'bold', + marginBottom: 12, + color: isDark ? '#f3f4f6' : '#111827', + }, + bodyContainer: { + backgroundColor: isDark ? '#262626' : '#f9fafb', + padding: 16, + borderRadius: 8, + marginBottom: 20, + }, + body: { + fontSize: 16, + lineHeight: 24, + color: isDark ? '#e5e5e5' : '#374151', + }, + metadataDetailsContainer: { + marginTop: 10, + padding: 16, + backgroundColor: isDark ? '#262626' : '#f9fafb', + borderRadius: 8, + }, + metadataTitle: { + fontSize: 16, + fontWeight: '600', + marginBottom: 10, + color: isDark ? '#f3f4f6' : '#111827', + }, + metadataItem: { + flexDirection: 'row', + marginBottom: 6, + }, + metadataKey: { + fontSize: 14, + fontWeight: '500', + marginRight: 8, + color: isDark ? '#9ca3af' : '#6b7280', + }, + metadataValue: { + fontSize: 14, + flex: 1, + color: isDark ? '#e5e5e5' : '#111827', + }, + buttonText: { + fontSize: 16, + fontWeight: '500', + color: isDark ? '#3b82f6' : '#2563eb', + }, + referenceButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginTop: 24, + backgroundColor: isDark ? '#1e3a8a' : '#dbeafe', + padding: 12, + borderRadius: 8, + borderWidth: 1, + borderColor: isDark ? '#3b82f6' : '#60a5fa', + }, + referenceButtonIcon: { + marginRight: 8, + color: isDark ? '#3b82f6' : '#2563eb', + }, + }); +const useStyles = () => { + const { colorScheme } = useColorScheme(); + return React.useMemo(() => createStyles(colorScheme === 'dark'), [colorScheme]); +}; diff --git a/src/components/notifications/NotificationInbox.tsx b/src/components/notifications/NotificationInbox.tsx index 8dfdb6d..e0e4cd8 100644 --- a/src/components/notifications/NotificationInbox.tsx +++ b/src/components/notifications/NotificationInbox.tsx @@ -1,12 +1,13 @@ import { useNotifications } from '@novu/react-native'; -import { CheckCircle, ChevronRight, Circle, ExternalLink, MoreVertical, Trash2, X } from 'lucide-react-native'; +import { useColorScheme } from 'nativewind'; import React, { useEffect, useRef, useState } from 'react'; -import { ActivityIndicator, Animated, Appearance, Dimensions, Platform, Pressable, RefreshControl, SafeAreaView, StatusBar, StyleSheet, View } from 'react-native'; +import { ActivityIndicator, Animated, Dimensions, Platform, Pressable, RefreshControl, SafeAreaView, StatusBar, StyleSheet, View } from 'react-native'; import { deleteMessage } from '@/api/novu/inbox'; import { NotificationDetail } from '@/components/notifications/NotificationDetail'; import { Button } from '@/components/ui/button'; import { FlatList } from '@/components/ui/flat-list'; +import { CheckCircle, ChevronRight, Circle, ExternalLink, MoreVertical, Trash2, X } from '@/components/ui/lucide-icons'; import { Modal, ModalBackdrop, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@/components/ui/modal'; import { Text } from '@/components/ui/text'; import { useCoreStore } from '@/stores/app/core-store'; @@ -24,6 +25,7 @@ interface NotificationInboxProps { } export const NotificationInbox = ({ isOpen, onClose }: NotificationInboxProps) => { + const styles = useStyles(); const activeUnitId = useCoreStore((state) => state.activeUnitId); const config = useCoreStore((state: any) => state.config); const { notifications, isLoading, fetchMore, hasMore, refetch } = useNotifications(); @@ -339,133 +341,138 @@ export const NotificationInbox = ({ isOpen, onClose }: NotificationInboxProps) = ); }; -const styles = StyleSheet.create({ - backdrop: { - backgroundColor: 'rgba(0, 0, 0, 0.5)', - zIndex: 999, - }, - backdropPressable: { - width: '100%', - height: '100%', - }, - sidebarContainer: { - position: 'absolute', - top: 0, - right: 0, - width: SIDEBAR_WIDTH, - height: '100%', - backgroundColor: Appearance.getColorScheme() === 'dark' ? '#171717' : '#fff', - shadowColor: Appearance.getColorScheme() === 'dark' ? '#262626' : '#e5e5e5', - shadowOffset: { - width: -2, - height: 0, +const createStyles = (isDark: boolean) => + StyleSheet.create({ + backdrop: { + backgroundColor: 'rgba(0, 0, 0, 0.5)', + zIndex: 999, }, - shadowOpacity: 0.25, - shadowRadius: 3.84, - elevation: 5, - zIndex: 1000, - }, - safeArea: { - flex: 1, - }, - header: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - padding: 16, - paddingTop: Platform.OS === 'android' ? STATUS_BAR_HEIGHT + 16 : 16, - borderBottomWidth: 1, - borderBottomColor: '#eee', - }, - headerTitle: { - fontSize: 18, - fontWeight: 'bold', - }, - headerActions: { - flexDirection: 'row', - alignItems: 'center', - }, - actionButton: { - padding: 8, - marginRight: 8, - }, - closeButton: { - padding: 8, - }, - selectionHeader: { - flex: 1, - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - }, - selectionCount: { - fontSize: 16, - fontWeight: '600', - color: Appearance.getColorScheme() === 'dark' ? '#ffffff' : '#000000', - }, - selectionActions: { - flexDirection: 'row', - alignItems: 'center', - }, - notificationItem: { - flexDirection: 'row', - alignItems: 'center', - padding: 16, - borderBottomWidth: 1, - borderBottomColor: Appearance.getColorScheme() === 'dark' ? '#333333' : '#eee', - position: 'relative', - }, - unreadNotificationItem: { - backgroundColor: Appearance.getColorScheme() === 'dark' ? '#262626' : '#f0f7ff', - }, - selectedNotificationItem: { - backgroundColor: Appearance.getColorScheme() === 'dark' ? '#1e3a8a' : '#dbeafe', - }, - unreadIndicator: { - position: 'absolute', - left: 0, - top: 0, - width: 4, - height: '100%', - backgroundColor: Appearance.getColorScheme() === 'dark' ? '#60a5fa' : '#3b82f6', - }, - selectionIndicator: { - marginRight: 12, - }, - notificationContent: { - flex: 1, - marginRight: 8, - }, - notificationBody: { - fontSize: 16, - marginBottom: 4, - color: Appearance.getColorScheme() === 'dark' ? '#e5e5e5' : '#333333', - }, - unreadNotificationText: { - fontWeight: '600', - color: Appearance.getColorScheme() === 'dark' ? '#ffffff' : '#000000', - }, - timestamp: { - fontSize: 12, - color: Appearance.getColorScheme() === 'dark' ? '#a3a3a3' : '#666', - }, - actionButtons: { - flexDirection: 'row', - alignItems: 'center', - }, - loadingContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - emptyContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - padding: 20, - }, - footerLoader: { - padding: 16, - alignItems: 'center', - }, -}); + backdropPressable: { + width: '100%', + height: '100%', + }, + sidebarContainer: { + position: 'absolute', + top: 0, + right: 0, + width: SIDEBAR_WIDTH, + height: '100%', + backgroundColor: isDark ? '#171717' : '#fff', + shadowColor: isDark ? '#262626' : '#e5e5e5', + shadowOffset: { + width: -2, + height: 0, + }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + elevation: 5, + zIndex: 1000, + }, + safeArea: { + flex: 1, + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 16, + paddingTop: Platform.OS === 'android' ? STATUS_BAR_HEIGHT + 16 : 16, + borderBottomWidth: 1, + borderBottomColor: '#eee', + }, + headerTitle: { + fontSize: 18, + fontWeight: 'bold', + }, + headerActions: { + flexDirection: 'row', + alignItems: 'center', + }, + actionButton: { + padding: 8, + marginRight: 8, + }, + closeButton: { + padding: 8, + }, + selectionHeader: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + }, + selectionCount: { + fontSize: 16, + fontWeight: '600', + color: isDark ? '#ffffff' : '#000000', + }, + selectionActions: { + flexDirection: 'row', + alignItems: 'center', + }, + notificationItem: { + flexDirection: 'row', + alignItems: 'center', + padding: 16, + borderBottomWidth: 1, + borderBottomColor: isDark ? '#333333' : '#eee', + position: 'relative', + }, + unreadNotificationItem: { + backgroundColor: isDark ? '#262626' : '#f0f7ff', + }, + selectedNotificationItem: { + backgroundColor: isDark ? '#1e3a8a' : '#dbeafe', + }, + unreadIndicator: { + position: 'absolute', + left: 0, + top: 0, + width: 4, + height: '100%', + backgroundColor: isDark ? '#60a5fa' : '#3b82f6', + }, + selectionIndicator: { + marginRight: 12, + }, + notificationContent: { + flex: 1, + marginRight: 8, + }, + notificationBody: { + fontSize: 16, + marginBottom: 4, + color: isDark ? '#e5e5e5' : '#333333', + }, + unreadNotificationText: { + fontWeight: '600', + color: isDark ? '#ffffff' : '#000000', + }, + timestamp: { + fontSize: 12, + color: isDark ? '#a3a3a3' : '#666', + }, + actionButtons: { + flexDirection: 'row', + alignItems: 'center', + }, + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + emptyContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 20, + }, + footerLoader: { + padding: 16, + alignItems: 'center', + }, + }); +const useStyles = () => { + const { colorScheme } = useColorScheme(); + return React.useMemo(() => createStyles(colorScheme === 'dark'), [colorScheme]); +}; diff --git a/src/components/protocols/protocol-details-sheet.tsx b/src/components/protocols/protocol-details-sheet.tsx index 2b1f44a..4729151 100644 --- a/src/components/protocols/protocol-details-sheet.tsx +++ b/src/components/protocols/protocol-details-sheet.tsx @@ -1,9 +1,9 @@ -import { Calendar, Tag, X } from 'lucide-react-native'; import { useColorScheme } from 'nativewind'; import React, { useEffect } from 'react'; import { StyleSheet } from 'react-native'; import WebView from 'react-native-webview'; +import { Calendar, Tag, X } from '@/components/ui/lucide-icons'; import { useAnalytics } from '@/hooks/use-analytics'; import { formatDateForDisplay, parseDateISOString, stripHtmlTags } from '@/lib/utils'; import { useProtocolsStore } from '@/stores/protocols/store'; diff --git a/src/components/ui/icon/index.tsx b/src/components/ui/icon/index.tsx index e2bebbb..1e57167 100644 --- a/src/components/ui/icon/index.tsx +++ b/src/components/ui/icon/index.tsx @@ -23,7 +23,23 @@ const iconStyle = tva({ }, }); -const StyledUIIcon = styled(UIIcon, { className: 'style' }); +// The nativewind v4 -> v5 migration replaced a `cssInterop(UIIcon, ...)` call with a bare +// `styled(UIIcon, { className: 'style' })`, dropping the `nativeStyleToProp` map. Without it the +// class-derived color stays in `style`, where neither gluestack's PrimitiveIcon (it reads +// `classNameColor`) nor react-native-svg (it reads the `color`/`stroke` props) ever looks — so +// every `` rendered with the icon's own default stroke. +const StyledUIIcon = styled(UIIcon, { + className: { + target: 'style', + nativeStyleMapping: { + height: true, + width: true, + fill: true, + color: 'classNameColor', + stroke: true, + }, + }, +}); type IIConProps = IPrimitiveIcon & VariantProps & React.ComponentPropsWithoutRef; diff --git a/src/components/ui/lucide-icons.tsx b/src/components/ui/lucide-icons.tsx new file mode 100644 index 0000000..227514f --- /dev/null +++ b/src/components/ui/lucide-icons.tsx @@ -0,0 +1,168 @@ +import { + AlertCircle as RawAlertCircle, + AlertTriangle as RawAlertTriangle, + ArrowLeft as RawArrowLeft, + BellIcon as RawBellIcon, + BookOpenIcon as RawBookOpenIcon, + Box as RawBox, + BuildingIcon as RawBuildingIcon, + Calendar as RawCalendar, + CalendarClock as RawCalendarClock, + CalendarClockIcon as RawCalendarClockIcon, + CalendarIcon as RawCalendarIcon, + CheckCircle as RawCheckCircle, + CheckCircleIcon as RawCheckCircleIcon, + CheckIcon as RawCheckIcon, + ChevronDownIcon as RawChevronDownIcon, + ChevronRight as RawChevronRight, + ChevronRightIcon as RawChevronRightIcon, + ChevronUpIcon as RawChevronUpIcon, + Circle as RawCircle, + ClipboardListIcon as RawClipboardListIcon, + Clock as RawClock, + ClockIcon as RawClockIcon, + CloudLightning as RawCloudLightning, + Edit2Icon as RawEdit2Icon, + EditIcon as RawEditIcon, + ExpandIcon as RawExpandIcon, + ExternalLink as RawExternalLink, + EyeIcon as RawEyeIcon, + EyeOffIcon as RawEyeOffIcon, + FileTextIcon as RawFileTextIcon, + GlobeIcon as RawGlobeIcon, + HomeIcon as RawHomeIcon, + LinkIcon as RawLinkIcon, + Loader2 as RawLoader2, + LockKeyhole as RawLockKeyhole, + type LucideProps, + Mail as RawMail, + MailIcon as RawMailIcon, + MapPinIcon as RawMapPinIcon, + MessageCircleIcon as RawMessageCircleIcon, + MessagesSquareIcon as RawMessagesSquareIcon, + MoreVertical as RawMoreVertical, + MoreVerticalIcon as RawMoreVerticalIcon, + NetworkIcon as RawNetworkIcon, + Phone as RawPhone, + PhoneIcon as RawPhoneIcon, + PlusIcon as RawPlusIcon, + RadioIcon as RawRadioIcon, + SearchIcon as RawSearchIcon, + SettingsIcon as RawSettingsIcon, + ShieldAlertIcon as RawShieldAlertIcon, + ShieldCheckIcon as RawShieldCheckIcon, + SmartphoneIcon as RawSmartphoneIcon, + StarIcon as RawStarIcon, + Tag as RawTag, + TimerIcon as RawTimerIcon, + Trash2 as RawTrash2, + Trash2Icon as RawTrash2Icon, + TrashIcon as RawTrashIcon, + Truck as RawTruck, + User as RawUser, + UserCogIcon as RawUserCogIcon, + UserIcon as RawUserIcon, + UserPlusIcon as RawUserPlusIcon, + Users as RawUsers, + UsersIcon as RawUsersIcon, + X as RawX, + XIcon as RawXIcon, +} from 'lucide-react-native'; +import { styled } from 'nativewind'; +import type React from 'react'; + +/** + * lucide icons that understand `className`. + * + * nativewind v5 dropped the JSX transform: a `className` only has an effect on a component + * that has been through `styled()`, and metro's polyfill only covers `react-native` itself. + * On a raw lucide icon the class was silently discarded -- which is why `text-*` colours and + * `mr-*` spacing had no effect and icons rendered with their default near-black stroke. + * + * `target: 'style'` keeps layout utilities working, and `nativeStyleMapping` lifts the + * resolved colour out of the style object onto lucide's `color` prop, which is where + * react-native-svg resolves `currentColor` from. + * + * Only icons used with a className live here, so the bundle is unchanged; import the rest + * straight from `lucide-react-native`. + */ +const iconMapping = { + className: { + target: 'style', + nativeStyleMapping: { + color: 'color', + }, + }, +} as const; + +type LucideIcon = React.ComponentType; + +const themed = (Component: T): T => styled(Component as LucideIcon, iconMapping) as unknown as T; + +export const AlertCircle = themed(RawAlertCircle); +export const AlertTriangle = themed(RawAlertTriangle); +export const ArrowLeft = themed(RawArrowLeft); +export const BellIcon = themed(RawBellIcon); +export const BookOpenIcon = themed(RawBookOpenIcon); +export const Box = themed(RawBox); +export const BuildingIcon = themed(RawBuildingIcon); +export const Calendar = themed(RawCalendar); +export const CalendarClock = themed(RawCalendarClock); +export const CalendarClockIcon = themed(RawCalendarClockIcon); +export const CalendarIcon = themed(RawCalendarIcon); +export const CheckCircle = themed(RawCheckCircle); +export const CheckCircleIcon = themed(RawCheckCircleIcon); +export const CheckIcon = themed(RawCheckIcon); +export const ChevronDownIcon = themed(RawChevronDownIcon); +export const ChevronRight = themed(RawChevronRight); +export const ChevronRightIcon = themed(RawChevronRightIcon); +export const ChevronUpIcon = themed(RawChevronUpIcon); +export const Circle = themed(RawCircle); +export const ClipboardListIcon = themed(RawClipboardListIcon); +export const Clock = themed(RawClock); +export const ClockIcon = themed(RawClockIcon); +export const CloudLightning = themed(RawCloudLightning); +export const Edit2Icon = themed(RawEdit2Icon); +export const EditIcon = themed(RawEditIcon); +export const ExpandIcon = themed(RawExpandIcon); +export const ExternalLink = themed(RawExternalLink); +export const EyeIcon = themed(RawEyeIcon); +export const EyeOffIcon = themed(RawEyeOffIcon); +export const FileTextIcon = themed(RawFileTextIcon); +export const GlobeIcon = themed(RawGlobeIcon); +export const HomeIcon = themed(RawHomeIcon); +export const LinkIcon = themed(RawLinkIcon); +export const Loader2 = themed(RawLoader2); +export const LockKeyhole = themed(RawLockKeyhole); +export const Mail = themed(RawMail); +export const MailIcon = themed(RawMailIcon); +export const MapPinIcon = themed(RawMapPinIcon); +export const MessageCircleIcon = themed(RawMessageCircleIcon); +export const MessagesSquareIcon = themed(RawMessagesSquareIcon); +export const MoreVertical = themed(RawMoreVertical); +export const MoreVerticalIcon = themed(RawMoreVerticalIcon); +export const NetworkIcon = themed(RawNetworkIcon); +export const Phone = themed(RawPhone); +export const PhoneIcon = themed(RawPhoneIcon); +export const PlusIcon = themed(RawPlusIcon); +export const RadioIcon = themed(RawRadioIcon); +export const SearchIcon = themed(RawSearchIcon); +export const SettingsIcon = themed(RawSettingsIcon); +export const ShieldAlertIcon = themed(RawShieldAlertIcon); +export const ShieldCheckIcon = themed(RawShieldCheckIcon); +export const SmartphoneIcon = themed(RawSmartphoneIcon); +export const StarIcon = themed(RawStarIcon); +export const Tag = themed(RawTag); +export const TimerIcon = themed(RawTimerIcon); +export const Trash2 = themed(RawTrash2); +export const Trash2Icon = themed(RawTrash2Icon); +export const TrashIcon = themed(RawTrashIcon); +export const Truck = themed(RawTruck); +export const User = themed(RawUser); +export const UserCogIcon = themed(RawUserCogIcon); +export const UserIcon = themed(RawUserIcon); +export const UserPlusIcon = themed(RawUserPlusIcon); +export const Users = themed(RawUsers); +export const UsersIcon = themed(RawUsersIcon); +export const X = themed(RawX); +export const XIcon = themed(RawXIcon);