├── .env.sample ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── TODO.md ├── api ├── followers.tsx ├── notifications.tsx ├── posts.tsx ├── profiles.tsx ├── storage.tsx └── users.tsx ├── app.json ├── app ├── (auth) │ ├── _layout.tsx │ ├── index.tsx │ ├── sign-in.tsx │ └── sign-up.tsx ├── +html.tsx ├── _layout.tsx ├── api │ ├── followers │ │ └── index+api.tsx │ ├── notifications+api.tsx │ ├── posts+api.tsx │ ├── presigned-post+api.tsx │ ├── profile-image+api.tsx │ ├── profiles+api.tsx │ ├── profiles │ │ └── [userId]+api.tsx │ ├── sign-in+api.tsx │ ├── sign-up+api.tsx │ └── users │ │ └── [userId] │ │ └── posts+api.tsx └── dashboard │ ├── (dashboard,post,notifications,account) │ ├── _layout.tsx │ ├── account.tsx │ ├── index.tsx │ ├── notifications.tsx │ ├── post.tsx │ └── profile │ │ └── [userId].tsx │ └── _layout.tsx ├── assets └── images │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ ├── partial-react-logo.png │ ├── react-logo.png │ ├── react-logo@2x.png │ ├── react-logo@3x.png │ ├── splash-icon.png │ └── splash.jpeg ├── babel.config.js ├── bun.lockb ├── components ├── layout │ ├── modal.module.css │ ├── modalNavigator.tsx │ └── modalNavigator.web.tsx ├── runtime │ ├── local-storage.ts │ └── local-storage.web.ts └── ui │ ├── AppButton.tsx │ ├── BodyScrollView.tsx │ ├── ContentUnavailable.tsx │ ├── FadeIn.tsx │ ├── Form.tsx │ ├── Header.tsx │ ├── IconSymbol.ios.tsx │ ├── IconSymbol.tsx │ ├── IconSymbolFallback.tsx │ ├── Segments.tsx │ ├── Skeleton.tsx │ ├── Skeleton.web.tsx │ ├── Stack.tsx │ ├── TabBarBackground.ios.tsx │ ├── TabBarBackground.tsx │ ├── Tabs.tsx │ ├── ThemeProvider.tsx │ ├── TouchableBounce.tsx │ └── TouchableBounce.web.tsx ├── cors.xml ├── db ├── index.ts └── schema.ts ├── docker-compose.yml ├── drizzle.config.ts ├── hooks ├── useAuth.tsx ├── useHeaderSearch.ts ├── useMergedRef.ts └── useTabToTop.ts ├── migrations ├── 0000_soft_electro.sql ├── 0001_remarkable_argent.sql ├── 0002_familiar_rogue.sql ├── 0003_flat_lord_tyger.sql ├── 0004_flowery_young_avengers.sql └── meta │ ├── 0000_snapshot.json │ ├── 0001_snapshot.json │ ├── 0002_snapshot.json │ ├── 0003_snapshot.json │ ├── 0004_snapshot.json │ └── _journal.json ├── package.json ├── s3.mjs ├── s3 ├── error.html └── index.html ├── tsconfig.json └── utils ├── auth.ts ├── images.ts ├── storage.ts └── withAuth.ts /.env.sample: -------------------------------------------------------------------------------- 1 | DATABASE_URL="postgresql://postgres:example@localhost:5432/postgres" 2 | JWT_SECRET="your-jwt-secret" 3 | USE_LOCAL_S3=true 4 | STORAGE_BUCKET_NAME="get-social" 5 | NODE_ENV=development 6 | EXPO_PUBLIC_STORAGE_BUCKET_NAME="http://localhost:9000/get-social" 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | expo-env.d.ts 11 | 12 | # Native 13 | *.orig.* 14 | *.jks 15 | *.p8 16 | *.p12 17 | *.key 18 | *.mobileprovision 19 | 20 | # Metro 21 | .metro-health-check* 22 | 23 | # debug 24 | npm-debug.* 25 | yarn-debug.* 26 | yarn-error.* 27 | 28 | # macOS 29 | .DS_Store 30 | *.pem 31 | 32 | # local env files 33 | .env*.local 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | 38 | app-example 39 | .env 40 | 41 | s3/get-social/* -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/amd64 node:18 2 | RUN apt-get update -y && apt-get upgrade -y 3 | 4 | RUN apt-get install -y unzip sudo build-essential 5 | 6 | RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" 7 | RUN unzip awscliv2.zip 8 | RUN sudo ./aws/install 9 | 10 | WORKDIR /home/app 11 | 12 | COPY . . -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Web Dev Cody 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Common components for Expo apps 2 | 3 | Components that I use in Expo Router apps that are generally optimized for iOS, dark mode, and servers. Main part is the forms which look like Apple's settings app. These should be replaced with proper SwiftUI/Jetpack Compose in the future, but it's still useful to have JS versions for platforms that don't have native support. 4 | 5 | 6 | 7 | 8 | 9 | For best results, just copy the files to another project. Here are the other deps: 10 | 11 | ``` 12 | bunx expo install expo-haptics expo-symbols expo-blur expo-web-browser @bacons/apple-colors vaul @react-native-segmented-control/segmented-control 13 | ``` 14 | 15 | You can also just bootstrap a project with this repo: 16 | 17 | ``` 18 | bunx create-expo -t https://github.com/EvanBacon/expo-router-forms-components 19 | ``` 20 | 21 | ## Stack 22 | 23 | Use the correct stack header settings for peak iOS defaults: 24 | 25 | ```tsx 26 | import Stack from "@/components/ui/Stack"; 27 | import ThemeProvider from "@/components/ui/ThemeProvider"; 28 | 29 | export default function Layout() { 30 | return ( 31 | 32 | 37 | 38 | ); 39 | } 40 | ``` 41 | 42 | Use `headerLargeTitle: true` to get the large header title. 43 | 44 | Use `` to add a link to the right side of the header with correct hit size and padding for Forms. The default color will be system blue. 45 | 46 | ```tsx 47 | ( 51 | 52 | Info 53 | 54 | ), 55 | }} 56 | /> 57 | ``` 58 | 59 | This stack uses `vaul` on web to make `modal` look like a native modal. 60 | 61 | ## Bottom sheet 62 | 63 | > Works on web too! 64 | 65 | 66 | 67 | 68 | You can open routes as a bottom sheet on iOS: 69 | 70 | ```tsx 71 | 72 | ``` 73 | 74 | This sets custom options for [React Native Screens](https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md#sheetalloweddetents): 75 | 76 | ```js 77 | { 78 | presentation: "formSheet", 79 | gestureDirection: "vertical", 80 | animation: "slide_from_bottom", 81 | sheetGrabberVisible: true, 82 | sheetInitialDetentIndex: 0, 83 | sheetAllowedDetents: [0.5, 1.0], 84 | } 85 | ``` 86 | 87 | - Use `sheetAllowedDetents` to change the snap points of the sheet. 88 | - Change the corder radius with `sheetCornerRadius: 48`. 89 | 90 | ## Tabs 91 | 92 | The custom tabs adds blurry backgrounds and haptics on iOS. You can also use the shortcut `systemImage` to set the icon. 93 | 94 | ```tsx 95 | import ThemeProvider from "@/components/ui/ThemeProvider"; 96 | 97 | import Tabs from "@/components/ui/Tabs"; 98 | 99 | export default function Layout() { 100 | return ( 101 | 102 | 103 | 104 | 105 | 106 | 107 | ); 108 | } 109 | ``` 110 | 111 | ## Forms 112 | 113 | Start lists with a `` and add sections with ``. Setting `navigationTitle="Settings"` will update the title of the stack header. 114 | 115 | ```tsx 116 | 117 | 118 | 119 | Evan Bacon 120 | 121 | Evan Bacon in browser 122 | 123 | 124 | ``` 125 | 126 |
127 | Internals 128 | 129 | Form list is a wrapper around a scroll view with some extra styles and helpers. 130 | 131 | ```tsx 132 | 138 | 139 | 140 | Evan Bacon 141 | 142 | Evan Bacon in browser 143 | 144 | 145 | ``` 146 | 147 |
148 | 149 | ## Form Sections 150 | 151 | All top-level children will become items. 152 | 153 | Add `title` and `footer` to a section. These can be strings or React nodes. 154 | 155 | ```tsx 156 | import * as AC from "@bacons/apple-colors"; 157 | 158 | 162 | Help improve Search by allowing Apple to store the searches you enter into 163 | Safari, Siri, and Spotlight in a way that is not linked to you.{"\n\n"} 164 | Searches include lookups of general knowledge, and requests to do things like 165 | play music and get directions.{"\n"} 166 | 167 | About Search & Privacy... 168 | 169 | 170 | } 171 | > 172 | Default 173 | ; 174 | ``` 175 | 176 | ## Form Items 177 | 178 | - `Form.Text` has extra types for `hint` and custom styles to have adaptive colors for dark mode. The font size is also larger to match the Apple defaults. 179 | - Adds the `systemImage` prop to append an SF Symbol icon before the text. The color of this icon will adopt the color of the text style. 180 | 181 | ```tsx 182 | Hey 183 | ``` 184 | 185 | Add a hint to the right-side of the form item: 186 | 187 | ```tsx 188 | Left 189 | ``` 190 | 191 | Add a custom press handler to the form item: 192 | 193 | ```tsx 194 | { 196 | console.log("Pressed"); 197 | }} 198 | > 199 | Press me 200 | 201 | ``` 202 | 203 | You can also use `