@letsoft/quq-react-native
v0.2.0
Published
Native React Native / Expo file manager for QuqManager.
Maintainers
Readme
@letsoft/quq-react-native
A native file manager for React Native apps built with Expo, backed by your QuqManager project. It is a real native screen, not a WebView. Your users can:
- browse folders, with paging and pull-to-refresh;
- upload from the photo library, the camera and the Files app, with progress and cancel;
- view images (pinch to zoom, swipe down to close), video and audio;
- create folders, rename, move to trash and share files;
- pick files for your app: the picker returns the chosen files to your code with the same
onSelect(files)shape as the QuqManager web picker.
The package has no native code of its own. It uses standard Expo modules, which you install next to it in step 1.
Before you start
An Expo app on SDK 56 or 57 (React 19, React Native 0.85 or newer). To start a new one:
npx create-expo-app@latest my-app --template default@sdk-57A React Native app without Expo needs the Expo modules system first:
npx install-expo-modules@latest.A QuqManager project with managed storage (Pro or Custom plan): in the dashboard, open the project and choose Managed R2 on the Storage tab. Self-hosted APIs don't accept mobile apps yet.
The project's API key, from the project's API Key tab.
1. Install the package
In your app's folder:
npx expo install @letsoft/quq-react-native expo-application expo-constants expo-file-system expo-image expo-image-picker expo-document-picker expo-sharing expo-video expo-audio react-native-svg react-native-safe-area-contextnpx expo install picks the versions that match your Expo SDK, and adds the expo-image,
expo-sharing, expo-video and expo-audio plugins to app.json for you.
2. Configure app.json
Add three things to your app.json, keeping everything that is already there:
ios.bundleIdentifierandandroid.package: your app's IDs. You list them in the QuqManager project in step 3.- An
expo-image-pickerentry inplugins, with the texts iOS shows when the app asks for the photo library, the camera and the microphone (the camera also records video). An app that only takes photos can leave the microphone out: see Photos only.
{
"expo": {
"ios": {
"bundleIdentifier": "com.acme.app"
},
"android": {
"package": "com.acme.app"
},
"plugins": [
["expo-image-picker", {
"photosPermission": "Allow $(PRODUCT_NAME) to choose photos to upload.",
"cameraPermission": "Allow $(PRODUCT_NAME) to take photos to upload.",
"microphonePermission": "Allow $(PRODUCT_NAME) to record sound with videos you upload."
}]
]
}
}Photos only, without the microphone
Video records sound, so by default the app declares the microphone: iOS asks for it with the text
above, and Android lists Record audio among the app's permissions. If your app only takes photos,
turn the microphone off in both plugins that add it, expo-image-picker and expo-audio (which
npx expo install added in step 1):
{
"expo": {
"plugins": [
["expo-image-picker", {
"photosPermission": "Allow $(PRODUCT_NAME) to choose photos to upload.",
"cameraPermission": "Allow $(PRODUCT_NAME) to take photos to upload.",
"microphonePermission": false
}],
["expo-audio", {
"microphonePermission": false,
"recordAudioAndroid": false
}]
]
}
}The file manager reads this from your app's config and adapts the camera:
- With the microphone, the camera takes photos and videos (Take Photo or Video).
- Without it, the camera takes only photos (Take Photo). On iOS, a camera that switched to video without the microphone declared would crash the app.
Videos already on the phone can still be uploaded from the photo library: that needs no microphone. Rebuild the app after changing the plugins.
3. Allow your app in the QuqManager project
Native apps send no Origin, so the project's allowed domains don't apply to them. In the
dashboard, open the project's General tab:
- Mobile apps: add your iOS bundle ID and Android package name, one per line, for example
com.acme.app. A development build with its own ID (com.acme.app.dev) needs its own line. Mobile apps have the same plan limit as allowed domains, counted separately. - Allow localhost: turn it on to try the app in Expo Go. Expo Go has its own ID and cannot be listed under Mobile apps.
Save. Until the app is allowed, the file manager shows a screen that names the exact ID to add.
4. Add the file manager screen
In an Expo Router app, a screen is a file in the app folder. The SDK 57 template keeps it in
src/app/. Create src/app/files.tsx:
import { useIsFocused } from 'expo-router'
import { SafeAreaView } from 'react-native-safe-area-context'
import { QuqFileManager } from '@letsoft/quq-react-native'
export default function FilesScreen() {
// Android back goes up a folder only while this tab is the one on screen.
const isFocused = useIsFocused()
return (
<SafeAreaView style={{ flex: 1 }} edges={['top']}>
<QuqFileManager apiKey="qk_your_project_key" handleHardwareBack={isFocused} />
</SafeAreaView>
)
}To show it as a tab in the SDK 57 template, add a trigger for it in
src/components/app-tabs.tsx, inside <NativeTabs>:
<NativeTabs.Trigger name="files">
<NativeTabs.Trigger.Label>Files</NativeTabs.Trigger.Label>
</NativeTabs.Trigger>With React Navigation instead of Expo Router, render the same QuqFileManager in your screen
and take useIsFocused from @react-navigation/native.
5. Run the app
- Expo Go:
npx expo start, then open the app in Expo Go. Needs Allow localhost (step 3). Expo Go asks for photo and camera access with its own texts. - Your own build:
npx expo run:iosornpx expo run:android, or an EAS build. Needs the app's ID under Mobile apps (step 3). Rebuild after any change to thepluginsinapp.json.
Open the Files tab: you see the project's files and can upload, view, rename, share and move files to trash.
Pick files for your app
QuqPickerModal opens the file manager as a sheet and hands the chosen files to your code:
import { useState } from 'react'
import { Button } from 'react-native'
import { QuqPickerModal, type QuqFile } from '@letsoft/quq-react-native'
export function AttachButton({ onAttach }: { onAttach: (files: QuqFile[]) => void }) {
const [open, setOpen] = useState(false)
return (
<>
<Button title="Attach files" onPress={() => setOpen(true)} />
<QuqPickerModal
visible={open}
apiKey="qk_your_project_key"
multiple
accept={['image/*', 'application/pdf']}
onSelect={onAttach}
onClose={() => setOpen(false)}
/>
</>
)
}- Each opening starts at
initialPathwith nothing selected. AfteronSelect, it callsonClose. - With
multiple, files get checkboxes and a Select bar. Without it, a tap picks at once. - Files that don't match
acceptstay visible but dimmed and can't be picked; a tap on one does nothing, as on the web, and its ⋮ menu has no Share. PassunpickablePress="preview"to open it instead: images, video and audio in the viewer, other files in Share. - On Android, back goes up a folder, then closes the sheet. On iOS, swipe the sheet down to close.
Each QuqFile has the file's name, path, absolute url, mime, size and modified.
QuqFileManager also takes mode="pick" if you'd rather place the picker in your own screen. In
the default browse mode, passing onSelect adds Select to a file's ⋮ menu (a long press opens
the same menu), so a files screen can hand a file to your code too.
Props
| Prop | Type | |
|---|---|---|
| apiKey | string | Project API key from the dashboard. Required. |
| apiUrl | string | API endpoint. Defaults to the managed QuqManager API. |
| initialPath | string | Folder to open first. Default /. |
| mode | 'browse' \| 'pick' | Default 'browse'. 'pick' requires onSelect. |
| multiple | boolean | Pick mode: choose several files and confirm with Select. |
| accept | string[] | Pick mode: MIME types or extensions that can be picked, e.g. image/*, .pdf. |
| unpickablePress | 'none' \| 'preview' | Pick mode: what a tap on a file outside accept does. Default 'none': nothing, and no Share in ⋮. 'preview': the viewer, or Share for files it can't show. |
| onSelect | (files: QuqFile[]) => void | The chosen files, with absolute urls. In browse mode it adds Select to the ⋮ menu. |
| onClose | () => void | Shows a close button in the header. |
| handleHardwareBack | boolean | Android back goes up a folder. Default true. Pass false, or useIsFocused(), when the screen is not focused. |
| style | StyleProp<ViewStyle> | Style of the root view. |
| ref | QuqFileManagerHandle | ref.current.goBack() goes up a folder and returns false at the root. |
QuqPickerModal takes the same props except mode, handleHardwareBack, style and ref, plus
visible, onSelect and onClose, which are required.
Good to know
- Read-only projects hide upload, new folder, rename and trash; viewing and sharing remain.
- Trash asks for confirmation. Files can be restored from Recovery in the web file manager.
- HEIC photos from the iOS photo library are uploaded as JPEG, so browsers can show them. Files picked from the Files app are uploaded as they are.
- Audio files follow the iOS silent switch; the package sets no audio mode itself (videos use expo-video's playback session).
- Strings are English for now.
License
MIT © LetSoft. See LICENSE.
