beautiful-snackbar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +233 -0
  2. package/example/.claude/settings.json +5 -0
  3. package/example/.vscode/extensions.json +1 -0
  4. package/example/.vscode/settings.json +7 -0
  5. package/example/AGENTS.md +3 -0
  6. package/example/CLAUDE.md +1 -0
  7. package/example/app.json +44 -0
  8. package/example/assets/expo.icon/Assets/expo-symbol 2.svg +3 -0
  9. package/example/assets/expo.icon/Assets/grid.png +0 -0
  10. package/example/assets/expo.icon/icon.json +40 -0
  11. package/example/assets/images/android-icon-background.png +0 -0
  12. package/example/assets/images/android-icon-foreground.png +0 -0
  13. package/example/assets/images/android-icon-monochrome.png +0 -0
  14. package/example/assets/images/expo-badge-white.png +0 -0
  15. package/example/assets/images/expo-badge.png +0 -0
  16. package/example/assets/images/expo-logo.png +0 -0
  17. package/example/assets/images/favicon.png +0 -0
  18. package/example/assets/images/icon.png +0 -0
  19. package/example/assets/images/logo-glow.png +0 -0
  20. package/example/assets/images/react-logo.png +0 -0
  21. package/example/assets/images/react-logo@2x.png +0 -0
  22. package/example/assets/images/react-logo@3x.png +0 -0
  23. package/example/assets/images/splash-icon.png +0 -0
  24. package/example/assets/images/tabIcons/explore.png +0 -0
  25. package/example/assets/images/tabIcons/explore@2x.png +0 -0
  26. package/example/assets/images/tabIcons/explore@3x.png +0 -0
  27. package/example/assets/images/tabIcons/home.png +0 -0
  28. package/example/assets/images/tabIcons/home@2x.png +0 -0
  29. package/example/assets/images/tabIcons/home@3x.png +0 -0
  30. package/example/assets/images/tutorial-web.png +0 -0
  31. package/example/metro.config.js +24 -0
  32. package/example/package.json +46 -0
  33. package/example/scripts/reset-project.js +114 -0
  34. package/example/src/app/_layout.tsx +63 -0
  35. package/example/src/app/explore.tsx +181 -0
  36. package/example/src/app/index.tsx +641 -0
  37. package/example/src/components/animated-icon.module.css +6 -0
  38. package/example/src/components/animated-icon.tsx +132 -0
  39. package/example/src/components/animated-icon.web.tsx +108 -0
  40. package/example/src/components/app-tabs.tsx +33 -0
  41. package/example/src/components/app-tabs.web.tsx +116 -0
  42. package/example/src/components/external-link.tsx +25 -0
  43. package/example/src/components/hint-row.tsx +35 -0
  44. package/example/src/components/themed-text.tsx +73 -0
  45. package/example/src/components/themed-view.tsx +16 -0
  46. package/example/src/components/ui/collapsible.tsx +65 -0
  47. package/example/src/components/web-badge.tsx +44 -0
  48. package/example/src/constants/theme.ts +66 -0
  49. package/example/src/global.css +9 -0
  50. package/example/src/hooks/use-color-scheme.ts +1 -0
  51. package/example/src/hooks/use-color-scheme.web.ts +21 -0
  52. package/example/src/hooks/use-theme.ts +14 -0
  53. package/example/tsconfig.json +35 -0
  54. package/lib/components/ActionableSnackbar.d.ts +7 -0
  55. package/lib/components/ActionableSnackbar.js +96 -0
  56. package/lib/components/BeautifulSnackbar.d.ts +11 -0
  57. package/lib/components/BeautifulSnackbar.js +189 -0
  58. package/lib/components/StandardSnackbar.d.ts +7 -0
  59. package/lib/components/StandardSnackbar.js +65 -0
  60. package/lib/constants.d.ts +7 -0
  61. package/lib/constants.js +20 -0
  62. package/lib/index.d.ts +5 -0
  63. package/lib/index.js +11 -0
  64. package/lib/manager.d.ts +58 -0
  65. package/lib/manager.js +107 -0
  66. package/lib/types.d.ts +25 -0
  67. package/lib/types.js +2 -0
  68. package/lib/useSnackbarAnimation.d.ts +14 -0
  69. package/lib/useSnackbarAnimation.js +118 -0
  70. package/package.json +33 -0
  71. package/src/components/ActionableSnackbar.tsx +109 -0
  72. package/src/components/BeautifulSnackbar.tsx +203 -0
  73. package/src/components/StandardSnackbar.tsx +70 -0
  74. package/src/constants.ts +20 -0
  75. package/src/index.ts +5 -0
  76. package/src/manager.ts +151 -0
  77. package/src/types.ts +27 -0
  78. package/src/useSnackbarAnimation.ts +145 -0
  79. package/tsconfig.json +23 -0
package/README.md ADDED
@@ -0,0 +1,233 @@
1
+ # 🌈 Beautiful Snackbar
2
+
3
+ A lightweight, premium, highly customizable, and **Reanimated-free** notification system for React Native and Expo. Powered by standard React Native `Animated` with native driver transitions to guarantee buttery-smooth 60fps animations.
4
+
5
+ <div align="center">
6
+ <br />
7
+ <div style="background-color: #1e1e2e; border: 1px solid #313244; padding: 12px; border-radius: 16px; max-width: 320px; box-shadow: 0 10px 30px rgba(0,0,0,0.3);">
8
+ <div style="background-color: #11111b; border-radius: 10px; overflow: hidden; display: flex; border: 1px solid #45475a;">
9
+ <video src="https://github.com/user-attachments/assets/acf635cd-dbe8-401c-b3cc-701cde5464c7" width="100%" autoplay loop muted playsinline></video>
10
+ </div>
11
+ </div>
12
+ <br />
13
+ </div>
14
+
15
+ ---
16
+
17
+ ## šŸ’” Why Beautiful Snackbar?
18
+
19
+ Most React Native notification libraries either require heavy libraries like `react-native-reanimated` or wrap your entire code in complex React context providers.
20
+
21
+ `beautiful-snackbar` was built to solve this by providing:
22
+ * šŸ”‹ **Zero Heavy Dependencies:** Written entirely using React Native's built-in `Animated` API with `useNativeDriver: true`.
23
+ * ⚔ **Singleton Architecture:** Trigger notifications from anywhere—inside a Redux action, an Axios interceptor, or a vanilla helper function—without hooks, contexts, or prop-drilling.
24
+ * šŸŽ¹ **True Keyboard Synchronization:** Dynamically adjusts its position as the soft keyboard slides up, taking safe areas into account so that it sits naturally at a configured height without compounding inset paddings.
25
+
26
+ ---
27
+
28
+ ## ✨ Features
29
+
30
+ * šŸš€ **Buttery-Smooth Animations:** Built-in transition presets (`slide`, `fade`, `scale`) that execute entirely on the native thread.
31
+ * āŒØļø **Keyboard & Safe Area Aware:** Intelligently listens to keyboard show/hide events and automatically translates above the keyboard while cleanly canceling out safe area bottom margins.
32
+ * šŸ› ļø **Custom UI Templates:** Register your own custom layouts at the root, and trigger them dynamically from anywhere by passing custom metadata.
33
+ * āš™ļø **Global Configuration Manager:** Tweak properties (offsets, animations, placement) dynamically at runtime via manager setters.
34
+ * 🧭 **Navigation Listener:** Automatically dismisses active snackbars on navigation transitions to keep user experience clean.
35
+
36
+ ---
37
+
38
+ ## šŸ“¦ Installation
39
+
40
+ Install the package using your favorite package manager:
41
+
42
+ ```bash
43
+ # Using yarn
44
+ yarn add beautiful-snackbar
45
+
46
+ # Using npm
47
+ npm install beautiful-snackbar
48
+ ```
49
+
50
+ ### Peer Dependencies
51
+ To handle safe area offsets and screen layouts cleanly, ensure you have `react-native-safe-area-context` installed:
52
+
53
+ ```bash
54
+ npx expo install react-native-safe-area-context
55
+ ```
56
+
57
+ ---
58
+
59
+ ## šŸš€ Quick Start
60
+
61
+ ### 1. Mount the Root Provider
62
+ Render `<BeautifulSnackbar />` at the root of your application (usually inside `_layout.tsx` or `App.tsx`). This acts as the overlay portal.
63
+
64
+ ```tsx
65
+ import React from 'react';
66
+ import { View } from 'react-native';
67
+ import { BeautifulSnackbar } from 'beautiful-snackbar';
68
+
69
+ export default function Layout() {
70
+ return (
71
+ <View style={{ flex: 1 }}>
72
+ {/* Your main app views (Tabs, Stack, etc.) */}
73
+
74
+ {/* Overlay container */}
75
+ <BeautifulSnackbar />
76
+ </View>
77
+ );
78
+ }
79
+ ```
80
+
81
+ ### 2. Trigger Notifications Anywhere
82
+ Simply import the `snackbar` singleton instance and call `.show()` from anywhere in your codebase:
83
+
84
+ ```tsx
85
+ import { snackbar } from 'beautiful-snackbar';
86
+
87
+ // Trigger a default bottom toast
88
+ snackbar.show({
89
+ message: '✨ Profile updated successfully!',
90
+ backgroundColor: '#10B981', // Emerald green
91
+ duration: 'short',
92
+ });
93
+ ```
94
+
95
+ ---
96
+
97
+ ## šŸ› ļø Advanced Usage & Customization
98
+
99
+ ### 1. Global Configuration Manager
100
+ You can modify the default behaviors dynamically. This is typically done during app startup:
101
+
102
+ ```tsx
103
+ import { snackbar } from 'beautiful-snackbar';
104
+
105
+ // Set global configuration defaults
106
+ snackbar.setPosition('bottom'); // Set default position ('top' | 'bottom')
107
+ snackbar.setAnimationType('scale'); // Set default entry animation ('slide' | 'fade' | 'scale')
108
+ snackbar.setBottomOffset(24); // Set default bottom spacing in pixels
109
+ snackbar.setAvoidKeyboard(true); // Enable/disable soft keyboard avoidance
110
+ ```
111
+
112
+ ### 2. Custom UI Template Registration
113
+ Do you need custom cards, badge overlays, or promotional layouts? Register your custom React layouts at the root using the `templates` prop:
114
+
115
+ ```tsx
116
+ import React from 'react';
117
+ import { View, Text, TouchableOpacity } from 'react-native';
118
+ import { BeautifulSnackbar } from 'beautiful-snackbar';
119
+
120
+ // Define your custom template layout
121
+ const customTemplates = {
122
+ promoCard: ({ item, dismiss }) => (
123
+ <View style={{ padding: 16, backgroundColor: '#31115F', borderRadius: 12 }}>
124
+ <Text style={{ color: '#ffffff', fontWeight: 'bold' }}>{item.message}</Text>
125
+ <Text style={{ color: '#C084FC' }}>{item.data?.subtitle}</Text>
126
+
127
+ <TouchableOpacity onPress={dismiss}>
128
+ <Text style={{ color: '#F59E0B', marginTop: 8 }}>Got it</Text>
129
+ </TouchableOpacity>
130
+ </View>
131
+ ),
132
+ };
133
+
134
+ export default function App() {
135
+ return (
136
+ <View style={{ flex: 1 }}>
137
+ <BeautifulSnackbar templates={customTemplates} />
138
+ </View>
139
+ );
140
+ }
141
+ ```
142
+
143
+ Trigger your custom UI card programmatically from anywhere:
144
+ ```tsx
145
+ import { snackbar } from 'beautiful-snackbar';
146
+
147
+ snackbar.show({
148
+ type: 'promoCard', // Match the template key
149
+ message: 'Premium Feature Unlocked!',
150
+ data: {
151
+ subtitle: 'Enjoy unlimited ad-free streaming.',
152
+ },
153
+ duration: 'infinite', // Keep visible until dismissed manually
154
+ });
155
+ ```
156
+
157
+ ### 3. Actionable Buttons
158
+ Create standard, actionable snackbars with button press handlers:
159
+
160
+ ```tsx
161
+ snackbar.show({
162
+ message: 'šŸ—‘ļø Message permanently deleted.',
163
+ backgroundColor: '#1E293B',
164
+ actionLabel: 'Undo',
165
+ actionColor: '#818CF8',
166
+ onActionPress: () => {
167
+ // Perform undo action
168
+ snackbar.show({ message: 'Restored!', backgroundColor: '#10B981' });
169
+ },
170
+ });
171
+ ```
172
+
173
+ ---
174
+
175
+ ## šŸ“– API Reference
176
+
177
+ ### `<BeautifulSnackbar />` Props
178
+
179
+ | Prop | Type | Default | Description |
180
+ | :--- | :--- | :--- | :--- |
181
+ | `navigation` | `any` | `undefined` | Optional React Navigation controller reference to automatically clear notifications on screen changes. |
182
+ | `avoidKeyboard` | `boolean` | `true` | When `true`, snackbars automatically shift up dynamically when the keyboard appears. |
183
+ | `templates` | `Record<string, Component>` | `undefined` | Map of key-value pairs representing custom templates. |
184
+
185
+ ### `snackbar.show()` Configuration Options
186
+
187
+ | Property | Type | Default | Description |
188
+ | :--- | :--- | :--- | :--- |
189
+ | `message` | `string` | **Required** | The message string to display inside standard alerts. |
190
+ | `duration` | `'short' \| 'medium' \| 'long' \| 'infinite' \| number` | `'short'` | Time on screen. Short (1500ms), Medium (2750ms), Long (4000ms), Infinite (requires dismissal), or custom duration in milliseconds. |
191
+ | `position` | `'top' \| 'bottom'` | `'bottom'` (config) | Placement on screen. Overrides global manager position. |
192
+ | `animationType` | `'slide' \| 'fade' \| 'scale'` | `'slide'` (config) | Transition style preset. Overrides global manager type. |
193
+ | `bottomOffset` | `number` | `24` (config) | Pixel spacing from screen bottom or soft keyboard. |
194
+ | `topOffset` | `number` | `50` (iOS) / `24` (Android) | Pixel spacing from screen top. |
195
+ | `backgroundColor` | `string` | `'#1E293B'` | Custom container background color. |
196
+ | `textColor` | `string` | `'#F8FAFC'` | Custom color for message text. |
197
+ | `actionLabel` | `string` | `undefined` | Interactive button label text. |
198
+ | `actionColor` | `string` | `'#6366F1'` | Text color of the interactive button. |
199
+ | `onActionPress` | `() => void` | `undefined` | Callback function triggered when action button is clicked. |
200
+ | `icon` | `React.ReactNode` | `undefined` | Optional leading element (e.g. image, custom SVG, text emoji). |
201
+ | `dismissOnNavigation` | `boolean` | `true` | If `true`, dismisses the notification when user changes pages. |
202
+ | `type` | `string` | `undefined` | Key matching a registered custom template component. |
203
+ | `data` | `any` | `undefined` | Arbitrary context payload passed down to custom templates. |
204
+
205
+ ---
206
+
207
+ ## šŸ› ļø Testing & Running the Example App
208
+
209
+ To inspect the package interactively, run the local Expo example app:
210
+
211
+ 1. **Clone and Install dependencies:**
212
+ Ensure `"private": true` is set in the root `package.json` before running `yarn` (this is required to prevent Yarn workspace resolution conflicts).
213
+ ```bash
214
+ yarn install
215
+ ```
216
+
217
+ 2. **Build the parent library:**
218
+ ```bash
219
+ yarn run build
220
+ ```
221
+
222
+ 3. **Start the Expo development server:**
223
+ ```bash
224
+ cd example
225
+ npx expo start
226
+ ```
227
+
228
+ 4. Press `i` to open in iOS simulator, or `a` to open in Android.
229
+
230
+ ---
231
+
232
+ ## šŸ“„ License
233
+ This project is licensed under the **MIT License**.
@@ -0,0 +1,5 @@
1
+ {
2
+ "enabledPlugins": {
3
+ "expo@claude-plugins-official": true
4
+ }
5
+ }
@@ -0,0 +1 @@
1
+ { "recommendations": ["expo.vscode-expo-tools"] }
@@ -0,0 +1,7 @@
1
+ {
2
+ "editor.codeActionsOnSave": {
3
+ "source.fixAll": "explicit",
4
+ "source.organizeImports": "explicit",
5
+ "source.sortMembers": "explicit"
6
+ }
7
+ }
@@ -0,0 +1,3 @@
1
+ # Expo HAS CHANGED
2
+
3
+ Read the exact versioned docs at https://docs.expo.dev/versions/v55.0.0/ before writing any code.
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,44 @@
1
+ {
2
+ "expo": {
3
+ "name": "example",
4
+ "slug": "example",
5
+ "version": "1.0.0",
6
+ "orientation": "portrait",
7
+ "icon": "./assets/images/icon.png",
8
+ "scheme": "example",
9
+ "userInterfaceStyle": "automatic",
10
+ "ios": {
11
+ "icon": "./assets/expo.icon"
12
+ },
13
+ "android": {
14
+ "adaptiveIcon": {
15
+ "backgroundColor": "#E6F4FE",
16
+ "foregroundImage": "./assets/images/android-icon-foreground.png",
17
+ "backgroundImage": "./assets/images/android-icon-background.png",
18
+ "monochromeImage": "./assets/images/android-icon-monochrome.png"
19
+ },
20
+ "predictiveBackGestureEnabled": false
21
+ },
22
+ "web": {
23
+ "output": "static",
24
+ "favicon": "./assets/images/favicon.png"
25
+ },
26
+ "plugins": [
27
+ "expo-router",
28
+ [
29
+ "expo-splash-screen",
30
+ {
31
+ "backgroundColor": "#208AEF",
32
+ "android": {
33
+ "image": "./assets/images/splash-icon.png",
34
+ "imageWidth": 76
35
+ }
36
+ }
37
+ ]
38
+ ],
39
+ "experiments": {
40
+ "typedRoutes": true,
41
+ "reactCompiler": true
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,3 @@
1
+ <svg width="652" height="606" viewBox="0 0 652 606" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M353.554 0H298.446C273.006 0 249.684 14.6347 237.962 37.9539L4.37994 502.646C-1.04325 513.435 -1.45067 526.178 3.2716 537.313L22.6123 582.918C34.6475 611.297 72.5404 614.156 88.4414 587.885L309.863 222.063C313.34 216.317 319.439 212.826 326 212.826C332.561 212.826 338.659 216.317 342.137 222.063L563.559 587.885C579.46 614.156 617.352 611.297 629.388 582.918L648.728 537.313C653.451 526.178 653.043 513.435 647.62 502.646L414.038 37.9539C402.316 14.6347 378.994 0 353.554 0Z" fill="white"/>
3
+ </svg>
@@ -0,0 +1,40 @@
1
+ {
2
+ "fill" : {
3
+ "automatic-gradient" : "extended-srgb:0.00000,0.47843,1.00000,1.00000"
4
+ },
5
+ "groups" : [
6
+ {
7
+ "layers" : [
8
+ {
9
+ "image-name" : "expo-symbol 2.svg",
10
+ "name" : "expo-symbol 2",
11
+ "position" : {
12
+ "scale" : 1,
13
+ "translation-in-points" : [
14
+ 1.1008400065293245e-05,
15
+ -16.046875
16
+ ]
17
+ }
18
+ },
19
+ {
20
+ "image-name" : "grid.png",
21
+ "name" : "grid"
22
+ }
23
+ ],
24
+ "shadow" : {
25
+ "kind" : "neutral",
26
+ "opacity" : 0.5
27
+ },
28
+ "translucency" : {
29
+ "enabled" : true,
30
+ "value" : 0.5
31
+ }
32
+ }
33
+ ],
34
+ "supported-platforms" : {
35
+ "circles" : [
36
+ "watchOS"
37
+ ],
38
+ "squares" : "shared"
39
+ }
40
+ }
Binary file
@@ -0,0 +1,24 @@
1
+ const { getDefaultConfig } = require('expo/metro-config');
2
+ const path = require('path');
3
+
4
+ const projectRoot = __dirname;
5
+ const workspaceRoot = path.resolve(projectRoot, '..');
6
+
7
+ const config = getDefaultConfig(projectRoot);
8
+
9
+ config.watchFolders = [workspaceRoot];
10
+
11
+ config.resolver.unstable_enableSymlinks = true;
12
+
13
+ config.resolver.nodeModulesPaths = [
14
+ path.resolve(projectRoot, 'node_modules'),
15
+ path.resolve(workspaceRoot, 'node_modules'),
16
+ ];
17
+
18
+ config.resolver.extraNodeModules = {
19
+ 'beautiful-snackbar': workspaceRoot,
20
+ 'react': path.resolve(projectRoot, 'node_modules/react'),
21
+ 'react-native': path.resolve(projectRoot, 'node_modules/react-native'),
22
+ };
23
+
24
+ module.exports = config;
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "example",
3
+ "main": "expo-router/entry",
4
+ "version": "1.0.0",
5
+ "scripts": {
6
+ "start": "expo start",
7
+ "reset-project": "node ./scripts/reset-project.js",
8
+ "android": "expo start --android",
9
+ "ios": "expo start --ios",
10
+ "web": "expo start --web",
11
+ "lint": "expo lint"
12
+ },
13
+ "dependencies": {
14
+ "@react-navigation/bottom-tabs": "^7.15.5",
15
+ "@react-navigation/elements": "^2.9.10",
16
+ "@react-navigation/native": "^7.1.33",
17
+ "expo": "~55.0.25",
18
+ "expo-constants": "~55.0.16",
19
+ "expo-device": "~55.0.17",
20
+ "expo-font": "~55.0.8",
21
+ "expo-glass-effect": "~55.0.11",
22
+ "expo-image": "~55.0.10",
23
+ "expo-linking": "~55.0.15",
24
+ "expo-router": "~55.0.15",
25
+ "expo-splash-screen": "~55.0.21",
26
+ "expo-status-bar": "~55.0.6",
27
+ "expo-symbols": "~55.0.9",
28
+ "expo-system-ui": "~55.0.18",
29
+ "expo-web-browser": "~55.0.16",
30
+ "react": "19.2.0",
31
+ "react-dom": "19.2.0",
32
+ "react-native": "0.83.6",
33
+ "beautiful-snackbar": "..",
34
+ "react-native-gesture-handler": "~2.30.0",
35
+ "react-native-worklets": "0.7.4",
36
+ "react-native-reanimated": "4.2.1",
37
+ "react-native-safe-area-context": "~5.6.2",
38
+ "react-native-screens": "~4.23.0",
39
+ "react-native-web": "~0.21.0"
40
+ },
41
+ "devDependencies": {
42
+ "@types/react": "~19.2.2",
43
+ "typescript": "~5.9.2"
44
+ },
45
+ "private": true
46
+ }
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * This script is used to reset the project to a blank state.
5
+ * It deletes or moves the /src and /scripts directories to /example based on user input and creates a new /src/app directory with an index.tsx and _layout.tsx file.
6
+ * You can remove the `reset-project` script from package.json and safely delete this file after running it.
7
+ */
8
+
9
+ const fs = require("fs");
10
+ const path = require("path");
11
+ const readline = require("readline");
12
+
13
+ const root = process.cwd();
14
+ const oldDirs = ["src", "scripts"];
15
+ const exampleDir = "example";
16
+ const newAppDir = "src/app";
17
+ const exampleDirPath = path.join(root, exampleDir);
18
+
19
+ const indexContent = `import { Text, View, StyleSheet } from "react-native";
20
+
21
+ export default function Index() {
22
+ return (
23
+ <View style={styles.container}>
24
+ <Text>Edit src/app/index.tsx to edit this screen.</Text>
25
+ </View>
26
+ );
27
+ }
28
+
29
+ const styles = StyleSheet.create({
30
+ container: {
31
+ flex: 1,
32
+ alignItems: "center",
33
+ justifyContent: "center",
34
+ },
35
+ });
36
+ `;
37
+
38
+ const layoutContent = `import { Stack } from "expo-router";
39
+
40
+ export default function RootLayout() {
41
+ return <Stack />;
42
+ }
43
+ `;
44
+
45
+ const rl = readline.createInterface({
46
+ input: process.stdin,
47
+ output: process.stdout,
48
+ });
49
+
50
+ const moveDirectories = async (userInput) => {
51
+ try {
52
+ if (userInput === "y") {
53
+ // Create the app-example directory
54
+ await fs.promises.mkdir(exampleDirPath, { recursive: true });
55
+ console.log(`šŸ“ /${exampleDir} directory created.`);
56
+ }
57
+
58
+ // Move old directories to new app-example directory or delete them
59
+ for (const dir of oldDirs) {
60
+ const oldDirPath = path.join(root, dir);
61
+ if (fs.existsSync(oldDirPath)) {
62
+ if (userInput === "y") {
63
+ const newDirPath = path.join(root, exampleDir, dir);
64
+ await fs.promises.rename(oldDirPath, newDirPath);
65
+ console.log(`āž”ļø /${dir} moved to /${exampleDir}/${dir}.`);
66
+ } else {
67
+ await fs.promises.rm(oldDirPath, { recursive: true, force: true });
68
+ console.log(`āŒ /${dir} deleted.`);
69
+ }
70
+ } else {
71
+ console.log(`āž”ļø /${dir} does not exist, skipping.`);
72
+ }
73
+ }
74
+
75
+ // Create new /src/app directory
76
+ const newAppDirPath = path.join(root, newAppDir);
77
+ await fs.promises.mkdir(newAppDirPath, { recursive: true });
78
+ console.log("\nšŸ“ New /src/app directory created.");
79
+
80
+ // Create index.tsx
81
+ const indexPath = path.join(newAppDirPath, "index.tsx");
82
+ await fs.promises.writeFile(indexPath, indexContent);
83
+ console.log("šŸ“„ src/app/index.tsx created.");
84
+
85
+ // Create _layout.tsx
86
+ const layoutPath = path.join(newAppDirPath, "_layout.tsx");
87
+ await fs.promises.writeFile(layoutPath, layoutContent);
88
+ console.log("šŸ“„ src/app/_layout.tsx created.");
89
+
90
+ console.log("\nāœ… Project reset complete. Next steps:");
91
+ console.log(
92
+ `1. Run \`npx expo start\` to start a development server.\n2. Edit src/app/index.tsx to edit the main screen.\n3. Put all your application code in /src, only screens and layout files should be in /src/app.${
93
+ userInput === "y"
94
+ ? `\n4. Delete the /${exampleDir} directory when you're done referencing it.`
95
+ : ""
96
+ }`
97
+ );
98
+ } catch (error) {
99
+ console.error(`āŒ Error during script execution: ${error.message}`);
100
+ }
101
+ };
102
+
103
+ rl.question(
104
+ "Do you want to move existing files to /example instead of deleting them? (Y/n): ",
105
+ (answer) => {
106
+ const userInput = answer.trim().toLowerCase() || "y";
107
+ if (userInput === "y" || userInput === "n") {
108
+ moveDirectories(userInput).finally(() => rl.close());
109
+ } else {
110
+ console.log("āŒ Invalid input. Please enter 'Y' or 'N'.");
111
+ rl.close();
112
+ }
113
+ }
114
+ );
@@ -0,0 +1,63 @@
1
+ import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
2
+ import React from 'react';
3
+ import { View, Text, useColorScheme } from 'react-native';
4
+ import { BeautifulSnackbar } from 'beautiful-snackbar';
5
+
6
+ import { AnimatedSplashOverlay } from '@/components/animated-icon';
7
+ import AppTabs from '@/components/app-tabs';
8
+
9
+ const customTemplates = {
10
+ customCard: ({ item }: { item: any }) => (
11
+ <View style={{
12
+ marginHorizontal: 16,
13
+ padding: 16,
14
+ backgroundColor: '#31115F', // Premium dark purple/indigo
15
+ borderRadius: 16,
16
+ borderColor: '#4c1d95',
17
+ borderWidth: 1.5,
18
+ flexDirection: 'row',
19
+ alignItems: 'center',
20
+ justifyContent: 'space-between',
21
+ shadowColor: '#000',
22
+ shadowOffset: { width: 0, height: 6 },
23
+ shadowOpacity: 0.25,
24
+ shadowRadius: 10,
25
+ elevation: 8,
26
+ }}>
27
+ <View style={{ flex: 1, paddingRight: 8 }}>
28
+ <Text style={{ color: '#F3E8FF', fontWeight: '800', fontSize: 15, letterSpacing: -0.2 }}>
29
+ {item.message}
30
+ </Text>
31
+ {item.data?.subtitle && (
32
+ <Text style={{ color: '#C084FC', fontSize: 12, marginTop: 2, fontWeight: '500' }}>
33
+ {item.data.subtitle}
34
+ </Text>
35
+ )}
36
+ </View>
37
+ {item.data?.badge && (
38
+ <View style={{
39
+ backgroundColor: '#F59E0B',
40
+ paddingHorizontal: 8,
41
+ paddingVertical: 4,
42
+ borderRadius: 8,
43
+ }}>
44
+ <Text style={{ color: '#000', fontWeight: 'bold', fontSize: 11 }}>
45
+ {item.data.badge}
46
+ </Text>
47
+ </View>
48
+ )}
49
+ </View>
50
+ )
51
+ };
52
+
53
+ export default function TabLayout() {
54
+ const colorScheme = useColorScheme();
55
+
56
+ return (
57
+ <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
58
+ <AnimatedSplashOverlay />
59
+ <AppTabs />
60
+ <BeautifulSnackbar templates={customTemplates} />
61
+ </ThemeProvider>
62
+ );
63
+ }