Skip to content

Commit 7e916bd

Browse files
authored
Merge pull request #52 from typelets/feature/mobile-sheets-viewer
feat(mobile): add file type selection sheet for creating new files
2 parents 0d87285 + 676a38c commit 7e916bd

File tree

20 files changed

+1111
-110
lines changed

20 files changed

+1111
-110
lines changed

apps/mobile/v1/eas.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
"development": {
88
"developmentClient": true,
99
"distribution": "internal",
10+
"node": "22.12.0",
1011
"env": {
1112
"RCT_NEW_ARCH_ENABLED": "0"
1213
}
1314
},
1415
"preview": {
1516
"distribution": "internal",
17+
"node": "22.12.0",
1618
"env": {
1719
"RCT_NEW_ARCH_ENABLED": "1",
1820
"SENTRY_ORG": "bata-labs",
@@ -21,6 +23,7 @@
2123
},
2224
"production": {
2325
"autoIncrement": true,
26+
"node": "22.12.0",
2427
"env": {
2528
"RCT_NEW_ARCH_ENABLED": "1",
2629
"SENTRY_ORG": "bata-labs",

apps/mobile/v1/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "v1",
33
"main": "index.js",
4-
"version": "1.45.2",
4+
"version": "1.45.4",
55
"scripts": {
66
"start": "expo start",
77
"reset-project": "node ./scripts/reset-project.js",
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
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+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import Svg, { Path } from 'react-native-svg';
3+
4+
// MDI icon paths (from @mdi/js) - hardcoded to avoid Node version issues
5+
export const mdiTextBoxOutline = 'M5,3C3.89,3 3,3.89 3,5V19C3,20.11 3.89,21 5,21H19C20.11,21 21,20.11 21,19V5C21,3.89 20.11,3 19,3H5M5,5H19V19H5V5M7,7V9H17V7H7M7,11V13H17V11H7M7,15V17H14V15H7Z';
6+
export const mdiVectorSquare = 'M2,2H8V4H16V2H22V8H20V16H22V22H16V20H8V22H2V16H4V8H2V2M16,8V6H8V8H6V16H8V18H16V16H18V8H16M4,4V6H6V4H4M18,4V6H20V4H18M4,18V20H6V18H4M18,18V20H20V18H18Z';
7+
export const mdiFileTableBoxOutline = 'M19 3H5C3.89 3 3 3.89 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.89 20.1 3 19 3M19 19H5V5H19V19M7 7H11V11H7V7M7 13H11V17H7V13M13 7H17V11H13V7M13 13H17V17H13V13Z';
8+
export const mdiCodeTags = 'M14.6,16.6L19.2,12L14.6,7.4L16,6L22,12L16,18L14.6,16.6M9.4,16.6L4.8,12L9.4,7.4L8,6L2,12L8,18L9.4,16.6Z';
9+
export const mdiFileDocumentOutline = 'M6,2A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6M6,4H13V9H18V20H6V4M8,12V14H16V12H8M8,16V18H13V16H8Z';
10+
11+
interface MdiIconProps {
12+
path: string;
13+
size?: number;
14+
color?: string;
15+
}
16+
17+
/**
18+
* Renders MDI icons using react-native-svg
19+
* MDI icons use a 24x24 viewBox
20+
*/
21+
export function MdiIcon({ path, size = 24, color = '#000' }: MdiIconProps) {
22+
return (
23+
<Svg width={size} height={size} viewBox="0 0 24 24">
24+
<Path d={path} fill={color} />
25+
</Svg>
26+
);
27+
}
28+
29+
export default MdiIcon;

0 commit comments

Comments
 (0)