|
| 1 | +import { Ionicons } from '@expo/vector-icons'; |
| 2 | +import { |
| 3 | + BottomSheetBackdrop, |
| 4 | + BottomSheetBackdropProps, |
| 5 | + BottomSheetModal, |
| 6 | + BottomSheetScrollView, |
| 7 | +} from '@gorhom/bottom-sheet'; |
| 8 | +import { GlassView } from 'expo-glass-effect'; |
| 9 | +import React, { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; |
| 10 | +import { Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; |
| 11 | +import { useSafeAreaInsets } from 'react-native-safe-area-context'; |
| 12 | + |
| 13 | +import { MdiIcon, mdiCodeTags, mdiFileDocumentOutline, mdiFileTableBoxOutline, mdiTextBoxOutline, mdiVectorSquare } from './MdiIcon'; |
| 14 | +import { useTheme } from '../theme'; |
| 15 | + |
| 16 | +export interface CreateFileSheetRef { |
| 17 | + present: () => void; |
| 18 | + dismiss: () => void; |
| 19 | +} |
| 20 | + |
| 21 | +interface CreateFileSheetProps { |
| 22 | + onCreateNote: () => void; |
| 23 | +} |
| 24 | + |
| 25 | +interface FileTypeOption { |
| 26 | + id: 'note' | 'sheet' | 'diagram' | 'code' | 'document'; |
| 27 | + title: string; |
| 28 | + subtitle: string; |
| 29 | + iconPath: string; |
| 30 | + iconColor: string; |
| 31 | + available: boolean; |
| 32 | +} |
| 33 | + |
| 34 | +const FILE_TYPES: FileTypeOption[] = [ |
| 35 | + { |
| 36 | + id: 'note', |
| 37 | + title: 'Note', |
| 38 | + subtitle: 'Write with rich text formatting', |
| 39 | + iconPath: mdiTextBoxOutline, |
| 40 | + iconColor: '#f43f5e', |
| 41 | + available: true, |
| 42 | + }, |
| 43 | + { |
| 44 | + id: 'sheet', |
| 45 | + title: 'Spreadsheet', |
| 46 | + subtitle: 'Create tables and calculations', |
| 47 | + iconPath: mdiFileTableBoxOutline, |
| 48 | + iconColor: '#22c55e', |
| 49 | + available: false, |
| 50 | + }, |
| 51 | + { |
| 52 | + id: 'diagram', |
| 53 | + title: 'Diagram', |
| 54 | + subtitle: 'Draw flowcharts and diagrams', |
| 55 | + iconPath: mdiVectorSquare, |
| 56 | + iconColor: '#a855f7', |
| 57 | + available: false, |
| 58 | + }, |
| 59 | + { |
| 60 | + id: 'code', |
| 61 | + title: 'Code', |
| 62 | + subtitle: 'Write and save code snippets', |
| 63 | + iconPath: mdiCodeTags, |
| 64 | + iconColor: '#f59e0b', |
| 65 | + available: false, |
| 66 | + }, |
| 67 | + { |
| 68 | + id: 'document', |
| 69 | + title: 'Document', |
| 70 | + subtitle: 'Long-form writing with pages', |
| 71 | + iconPath: mdiFileDocumentOutline, |
| 72 | + iconColor: '#3b82f6', |
| 73 | + available: false, |
| 74 | + }, |
| 75 | +]; |
| 76 | + |
| 77 | +export const CreateFileSheet = forwardRef<CreateFileSheetRef, CreateFileSheetProps>( |
| 78 | + ({ onCreateNote }, ref) => { |
| 79 | + const theme = useTheme(); |
| 80 | + const insets = useSafeAreaInsets(); |
| 81 | + const sheetRef = useRef<BottomSheetModal>(null); |
| 82 | + |
| 83 | + useImperativeHandle(ref, () => ({ |
| 84 | + present: () => sheetRef.current?.present(), |
| 85 | + dismiss: () => sheetRef.current?.dismiss(), |
| 86 | + })); |
| 87 | + |
| 88 | + const renderBackdrop = useCallback( |
| 89 | + (props: BottomSheetBackdropProps) => ( |
| 90 | + <BottomSheetBackdrop |
| 91 | + {...props} |
| 92 | + disappearsOnIndex={-1} |
| 93 | + appearsOnIndex={0} |
| 94 | + opacity={0.3} |
| 95 | + /> |
| 96 | + ), |
| 97 | + [] |
| 98 | + ); |
| 99 | + |
| 100 | + const handleSelect = (option: FileTypeOption) => { |
| 101 | + if (!option.available) { |
| 102 | + Alert.alert('Coming Soon', `${option.title} creation is coming in a future update. Stay tuned!`); |
| 103 | + return; |
| 104 | + } |
| 105 | + |
| 106 | + sheetRef.current?.dismiss(); |
| 107 | + |
| 108 | + if (option.id === 'note') { |
| 109 | + onCreateNote(); |
| 110 | + } |
| 111 | + }; |
| 112 | + |
| 113 | + return ( |
| 114 | + <BottomSheetModal |
| 115 | + ref={sheetRef} |
| 116 | + backdropComponent={renderBackdrop} |
| 117 | + backgroundStyle={{ backgroundColor: theme.colors.card }} |
| 118 | + handleIndicatorStyle={{ backgroundColor: theme.colors.border }} |
| 119 | + enableDynamicSizing={true} |
| 120 | + enablePanDownToClose={true} |
| 121 | + maxDynamicContentSize={600} |
| 122 | + > |
| 123 | + <BottomSheetScrollView style={styles.container}> |
| 124 | + <View style={styles.header}> |
| 125 | + <Text style={[styles.title, { color: theme.colors.foreground }]}> |
| 126 | + Create New |
| 127 | + </Text> |
| 128 | + <GlassView |
| 129 | + glassEffectStyle="regular" |
| 130 | + style={[ |
| 131 | + styles.glassButton, |
| 132 | + { backgroundColor: theme.isDark ? 'rgba(255, 255, 255, 0.01)' : 'rgba(0, 0, 0, 0.01)' }, |
| 133 | + ]} |
| 134 | + > |
| 135 | + <TouchableOpacity |
| 136 | + style={styles.closeButton} |
| 137 | + onPress={() => sheetRef.current?.dismiss()} |
| 138 | + > |
| 139 | + <Ionicons name="close" size={20} color={theme.colors.foreground} /> |
| 140 | + </TouchableOpacity> |
| 141 | + </GlassView> |
| 142 | + </View> |
| 143 | + |
| 144 | + <View style={[styles.divider, { backgroundColor: theme.colors.border }]} /> |
| 145 | + |
| 146 | + <View style={[styles.optionsContainer, { paddingBottom: Math.max(insets.bottom, 20) }]}> |
| 147 | + {FILE_TYPES.map((option) => ( |
| 148 | + <TouchableOpacity |
| 149 | + key={option.id} |
| 150 | + style={[ |
| 151 | + styles.optionItem, |
| 152 | + { |
| 153 | + backgroundColor: theme.colors.card, |
| 154 | + borderColor: theme.colors.border, |
| 155 | + opacity: option.available ? 1 : 0.5, |
| 156 | + }, |
| 157 | + ]} |
| 158 | + onPress={() => handleSelect(option)} |
| 159 | + activeOpacity={0.7} |
| 160 | + > |
| 161 | + <View style={[styles.optionIcon, { backgroundColor: theme.colors.muted }]}> |
| 162 | + <MdiIcon path={option.iconPath} size={24} color={option.iconColor} /> |
| 163 | + </View> |
| 164 | + <View style={styles.optionText}> |
| 165 | + <Text style={[styles.optionTitle, { color: theme.colors.foreground }]}> |
| 166 | + {option.title} |
| 167 | + </Text> |
| 168 | + <Text style={[styles.optionSubtitle, { color: theme.colors.mutedForeground }]}> |
| 169 | + {option.subtitle} |
| 170 | + </Text> |
| 171 | + </View> |
| 172 | + {!option.available && ( |
| 173 | + <View style={[styles.comingSoonBadge, { backgroundColor: theme.colors.muted }]}> |
| 174 | + <Text style={[styles.comingSoonText, { color: theme.colors.mutedForeground }]}> |
| 175 | + Soon |
| 176 | + </Text> |
| 177 | + </View> |
| 178 | + )} |
| 179 | + </TouchableOpacity> |
| 180 | + ))} |
| 181 | + </View> |
| 182 | + </BottomSheetScrollView> |
| 183 | + </BottomSheetModal> |
| 184 | + ); |
| 185 | + } |
| 186 | +); |
| 187 | + |
| 188 | +CreateFileSheet.displayName = 'CreateFileSheet'; |
| 189 | + |
| 190 | +const styles = StyleSheet.create({ |
| 191 | + container: { |
| 192 | + flex: 1, |
| 193 | + }, |
| 194 | + header: { |
| 195 | + flexDirection: 'row', |
| 196 | + alignItems: 'center', |
| 197 | + justifyContent: 'space-between', |
| 198 | + paddingHorizontal: 20, |
| 199 | + paddingBottom: 12, |
| 200 | + }, |
| 201 | + title: { |
| 202 | + fontSize: 20, |
| 203 | + fontWeight: '600', |
| 204 | + }, |
| 205 | + glassButton: { |
| 206 | + borderRadius: 17, |
| 207 | + overflow: 'hidden', |
| 208 | + }, |
| 209 | + closeButton: { |
| 210 | + width: 34, |
| 211 | + height: 34, |
| 212 | + alignItems: 'center', |
| 213 | + justifyContent: 'center', |
| 214 | + }, |
| 215 | + divider: { |
| 216 | + height: 0.5, |
| 217 | + }, |
| 218 | + optionsContainer: { |
| 219 | + paddingHorizontal: 20, |
| 220 | + paddingTop: 16, |
| 221 | + gap: 12, |
| 222 | + }, |
| 223 | + optionItem: { |
| 224 | + flexDirection: 'row', |
| 225 | + alignItems: 'center', |
| 226 | + padding: 16, |
| 227 | + borderRadius: 12, |
| 228 | + borderWidth: 1, |
| 229 | + }, |
| 230 | + optionIcon: { |
| 231 | + width: 48, |
| 232 | + height: 48, |
| 233 | + borderRadius: 12, |
| 234 | + alignItems: 'center', |
| 235 | + justifyContent: 'center', |
| 236 | + marginRight: 16, |
| 237 | + }, |
| 238 | + optionText: { |
| 239 | + flex: 1, |
| 240 | + }, |
| 241 | + optionTitle: { |
| 242 | + fontSize: 16, |
| 243 | + fontWeight: '600', |
| 244 | + marginBottom: 2, |
| 245 | + }, |
| 246 | + optionSubtitle: { |
| 247 | + fontSize: 14, |
| 248 | + }, |
| 249 | + comingSoonBadge: { |
| 250 | + paddingHorizontal: 8, |
| 251 | + paddingVertical: 4, |
| 252 | + borderRadius: 6, |
| 253 | + }, |
| 254 | + comingSoonText: { |
| 255 | + fontSize: 12, |
| 256 | + fontWeight: '500', |
| 257 | + }, |
| 258 | +}); |
0 commit comments