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.
- package/README.md +233 -0
- package/example/.claude/settings.json +5 -0
- package/example/.vscode/extensions.json +1 -0
- package/example/.vscode/settings.json +7 -0
- package/example/AGENTS.md +3 -0
- package/example/CLAUDE.md +1 -0
- package/example/app.json +44 -0
- package/example/assets/expo.icon/Assets/expo-symbol 2.svg +3 -0
- package/example/assets/expo.icon/Assets/grid.png +0 -0
- package/example/assets/expo.icon/icon.json +40 -0
- package/example/assets/images/android-icon-background.png +0 -0
- package/example/assets/images/android-icon-foreground.png +0 -0
- package/example/assets/images/android-icon-monochrome.png +0 -0
- package/example/assets/images/expo-badge-white.png +0 -0
- package/example/assets/images/expo-badge.png +0 -0
- package/example/assets/images/expo-logo.png +0 -0
- package/example/assets/images/favicon.png +0 -0
- package/example/assets/images/icon.png +0 -0
- package/example/assets/images/logo-glow.png +0 -0
- package/example/assets/images/react-logo.png +0 -0
- package/example/assets/images/react-logo@2x.png +0 -0
- package/example/assets/images/react-logo@3x.png +0 -0
- package/example/assets/images/splash-icon.png +0 -0
- package/example/assets/images/tabIcons/explore.png +0 -0
- package/example/assets/images/tabIcons/explore@2x.png +0 -0
- package/example/assets/images/tabIcons/explore@3x.png +0 -0
- package/example/assets/images/tabIcons/home.png +0 -0
- package/example/assets/images/tabIcons/home@2x.png +0 -0
- package/example/assets/images/tabIcons/home@3x.png +0 -0
- package/example/assets/images/tutorial-web.png +0 -0
- package/example/metro.config.js +24 -0
- package/example/package.json +46 -0
- package/example/scripts/reset-project.js +114 -0
- package/example/src/app/_layout.tsx +63 -0
- package/example/src/app/explore.tsx +181 -0
- package/example/src/app/index.tsx +641 -0
- package/example/src/components/animated-icon.module.css +6 -0
- package/example/src/components/animated-icon.tsx +132 -0
- package/example/src/components/animated-icon.web.tsx +108 -0
- package/example/src/components/app-tabs.tsx +33 -0
- package/example/src/components/app-tabs.web.tsx +116 -0
- package/example/src/components/external-link.tsx +25 -0
- package/example/src/components/hint-row.tsx +35 -0
- package/example/src/components/themed-text.tsx +73 -0
- package/example/src/components/themed-view.tsx +16 -0
- package/example/src/components/ui/collapsible.tsx +65 -0
- package/example/src/components/web-badge.tsx +44 -0
- package/example/src/constants/theme.ts +66 -0
- package/example/src/global.css +9 -0
- package/example/src/hooks/use-color-scheme.ts +1 -0
- package/example/src/hooks/use-color-scheme.web.ts +21 -0
- package/example/src/hooks/use-theme.ts +14 -0
- package/example/tsconfig.json +35 -0
- package/lib/components/ActionableSnackbar.d.ts +7 -0
- package/lib/components/ActionableSnackbar.js +96 -0
- package/lib/components/BeautifulSnackbar.d.ts +11 -0
- package/lib/components/BeautifulSnackbar.js +189 -0
- package/lib/components/StandardSnackbar.d.ts +7 -0
- package/lib/components/StandardSnackbar.js +65 -0
- package/lib/constants.d.ts +7 -0
- package/lib/constants.js +20 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +11 -0
- package/lib/manager.d.ts +58 -0
- package/lib/manager.js +107 -0
- package/lib/types.d.ts +25 -0
- package/lib/types.js +2 -0
- package/lib/useSnackbarAnimation.d.ts +14 -0
- package/lib/useSnackbarAnimation.js +118 -0
- package/package.json +33 -0
- package/src/components/ActionableSnackbar.tsx +109 -0
- package/src/components/BeautifulSnackbar.tsx +203 -0
- package/src/components/StandardSnackbar.tsx +70 -0
- package/src/constants.ts +20 -0
- package/src/index.ts +5 -0
- package/src/manager.ts +151 -0
- package/src/types.ts +27 -0
- package/src/useSnackbarAnimation.ts +145 -0
- 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 @@
|
|
|
1
|
+
{ "recommendations": ["expo.vscode-expo-tools"] }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/example/app.json
ADDED
|
@@ -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>
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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
|
+
}
|