diff --git a/.changeset/thin-wolves-push.md b/.changeset/thin-wolves-push.md new file mode 100644 index 00000000..56bbea77 --- /dev/null +++ b/.changeset/thin-wolves-push.md @@ -0,0 +1,5 @@ +--- +'create-expo-stack': minor +--- + +add redux diff --git a/cli/src/commands/create-expo-stack.ts b/cli/src/commands/create-expo-stack.ts index 16e14522..fca71ab0 100644 --- a/cli/src/commands/create-expo-stack.ts +++ b/cli/src/commands/create-expo-stack.ts @@ -309,6 +309,14 @@ const command: GluegunCommand = { cliResults.packages.push({ name: 'vexo-analytics', type: 'analytics' }); } + if (options.redux) { + // Add redux package + cliResults.packages.push({ + name: 'redux', + type: 'state-management' + }); + } + // By this point, all cliResults should be set info(''); highlight('Your project configuration:'); @@ -354,6 +362,13 @@ const command: GluegunCommand = { script += '--drawer+tabs '; } } + + const stateManagementPackage = cliResults.packages.find((p) => p.type === 'state-management'); + + if (stateManagementPackage) { + // currently only redux is supported + script += `--${stateManagementPackage.name} `; + } } else { // Add the packages cliResults.packages.forEach((p) => { @@ -407,6 +422,7 @@ const command: GluegunCommand = { const stylingPackage = packages.find((p) => p.type === 'styling'); const internalizationPackage = packages.find((p) => p.type === 'internationalization'); const analyticsPackage = packages.find((p) => p.type === 'analytics'); + const stateManagementPackage = packages.find((p) => p.type === 'state-management') || undefined; let files: string[] = []; @@ -418,7 +434,8 @@ const command: GluegunCommand = { analyticsPackage, toolbox, cliResults, - internalizationPackage + internalizationPackage, + stateManagementPackage ); // Once all the files are defined, format and generate them @@ -434,7 +451,8 @@ const command: GluegunCommand = { packageManager, stylingPackage, toolbox, - internalizationPackage + internalizationPackage, + stateManagementPackage ); await printOutput(cliResults, formattedFiles, toolbox, stylingPackage); diff --git a/cli/src/templates/base/App.tsx.ejs b/cli/src/templates/base/App.tsx.ejs index 112a2feb..dd6e272f 100644 --- a/cli/src/templates/base/App.tsx.ejs +++ b/cli/src/templates/base/App.tsx.ejs @@ -1,5 +1,9 @@ import { ScreenContent } from 'components/ScreenContent'; import { StatusBar } from 'expo-status-bar'; +<% if (props.stateManagementPackage?.name === "redux") { %> +import { Provider } from 'react-redux' +import store from 'store/store' +<% } %> <% if (props.internalizationPackage?.name === "i18next") { %> import './translation'; @@ -28,15 +32,23 @@ import { StatusBar } from 'expo-status-bar'; vexo(process.env.EXPO_PUBLIC_VEXO_API_KEY); <% } %> + + <% if (props.stylingPackage?.name === "restyle") {%> export default function App() { return ( - - <% if (props.internalizationPackage?.name === "i18next") { %> - + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> + + <% if (props.internalizationPackage?.name === "i18next") { %> + + <% } %> + + <% if (props.stateManagementPackage?.name === "redux") { %> + <% } %> - ); @@ -45,13 +57,19 @@ import { StatusBar } from 'expo-status-bar'; export default function App() { return ( - - <% if (props.internalizationPackage?.name === "i18next") { %> - - <% } else { %> - Open up App.tsx to start working on your app! + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> + + <% if (props.internalizationPackage?.name === "i18next") { %> + + <% } else { %> + Open up App.tsx to start working on your app! + <% } %> + + <% if (props.stateManagementPackage?.name === "redux") { %> + <% } %> - ); @@ -60,11 +78,17 @@ import { StatusBar } from 'expo-status-bar'; export default function App() { return ( <> - - <% if (props.internalizationPackage?.name === "i18next") { %> - + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> + + <% if (props.internalizationPackage?.name === "i18next") { %> + + <% } %> + + <% if (props.stateManagementPackage?.name === "redux") { %> + <% } %> - ); diff --git a/cli/src/templates/base/package.json.ejs b/cli/src/templates/base/package.json.ejs index e06d180e..43f77306 100644 --- a/cli/src/templates/base/package.json.ejs +++ b/cli/src/templates/base/package.json.ejs @@ -101,6 +101,11 @@ "vexo-analytics": "^1.3.15", <% } %> + <% if (props.stateManagementPackage?.name === "redux") { %> + "react-redux": "^9.1.2", + "@reduxjs/toolkit": "^2.2.7", + <% } %> + <% if ((props.navigationPackage?.name === "react-navigation") && (props.stylingPackage?.name === "tamagui")) { %> "expo-splash-screen": "~0.27.4", <% } %> diff --git a/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs index c5bf2d2a..a1f14e4f 100644 --- a/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs +++ b/cli/src/templates/packages/expo-router/drawer/app/_layout.tsx.ejs @@ -36,6 +36,11 @@ import { Stack } from 'expo-router'; vexo(process.env.EXPO_PUBLIC_VEXO_API_KEY); <% } %> +<% if (props.stateManagementPackage?.name === "redux") { %> +import { Provider } from 'react-redux' +import store from 'store/store' +<% } %> + export const unstable_settings = { // Ensure that reloading on `/modal` keeps a back button present. @@ -65,10 +70,16 @@ export default function RootLayout() { <% } %> - - - - + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> + + + + + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> <% if (props.stylingPackage?.name === "tamagui") { %> diff --git a/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs index 5220239c..237c62a3 100644 --- a/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs +++ b/cli/src/templates/packages/expo-router/stack/app/_layout.tsx.ejs @@ -27,6 +27,11 @@ import '../unistyles'; vexo(process.env.EXPO_PUBLIC_VEXO_API_KEY); <% } %> +<% if (props.stateManagementPackage?.name === "redux") { %> +import { Provider } from 'react-redux' +import store from 'store/store' +<% } %> + export default function Layout() { @@ -51,7 +56,13 @@ export default function Layout() { <% } else if (props.stylingPackage?.name === "restyle") { %> <% } %> - + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> + + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> <% if (props.stylingPackage?.name === "tamagui") { %> <% } else if (props.stylingPackage?.name === "restyle") { %> diff --git a/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs b/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs index 489ca9de..434aa0ec 100644 --- a/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs +++ b/cli/src/templates/packages/expo-router/tabs/app/_layout.tsx.ejs @@ -33,6 +33,11 @@ import '../unistyles'; vexo(process.env.EXPO_PUBLIC_VEXO_API_KEY); <% } %> +<% if (props.stateManagementPackage?.name === "redux") { %> +import { Provider } from 'react-redux' +import store from 'store/store' +<% } %> + export const unstable_settings = { // Ensure that reloading on `/modal` keeps a back button present. @@ -61,10 +66,16 @@ export default function RootLayout() { <% } else if (props.stylingPackage?.name === "restyle") { %> <% } %> - - - - + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> + + + + + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> <% if (props.stylingPackage?.name === "tamagui") { %> <% } else if (props.stylingPackage?.name === "restyle") { %> diff --git a/cli/src/templates/packages/nativewindui/drawer/app/_layout.tsx.ejs b/cli/src/templates/packages/nativewindui/drawer/app/_layout.tsx.ejs index 2972a438..81c11ae8 100644 --- a/cli/src/templates/packages/nativewindui/drawer/app/_layout.tsx.ejs +++ b/cli/src/templates/packages/nativewindui/drawer/app/_layout.tsx.ejs @@ -1,6 +1,10 @@ import '../global.css'; import 'expo-dev-client'; import { ThemeProvider as NavThemeProvider } from '@react-navigation/native'; +<% if (props.stateManagementPackage?.name === "redux") { %> +import { Provider } from 'react-redux' +import store from 'store/store' +<% } %> <% if (props.stylingPackage?.options.selectedComponents.includes('action-sheet')) { %> import { ActionSheetProvider } from '@expo/react-native-action-sheet'; <% } %> @@ -34,6 +38,10 @@ export default function RootLayout() { /> {/* WRAP YOUR APP WITH ANY ADDITIONAL PROVIDERS HERE */} {/* */} + + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> <% if (props.stylingPackage?.options.selectedComponents.includes('bottom-sheet')) { %> @@ -54,6 +62,9 @@ export default function RootLayout() { <% } %> + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> {/* */} ); diff --git a/cli/src/templates/packages/nativewindui/stack/app/_layout.tsx.ejs b/cli/src/templates/packages/nativewindui/stack/app/_layout.tsx.ejs index 39d51068..59455142 100644 --- a/cli/src/templates/packages/nativewindui/stack/app/_layout.tsx.ejs +++ b/cli/src/templates/packages/nativewindui/stack/app/_layout.tsx.ejs @@ -2,6 +2,10 @@ import '../global.css'; import 'expo-dev-client'; import { ThemeProvider as NavThemeProvider } from '@react-navigation/native'; import { Icon } from '@roninoss/icons'; +<% if (props.stateManagementPackage?.name === "redux") { %> +import { Provider } from 'react-redux' +import store from 'store/store' +<% } %> <% if (props.stylingPackage?.options.selectedComponents.includes('action-sheet')) { %> import { ActionSheetProvider } from '@expo/react-native-action-sheet'; <% } %> @@ -37,6 +41,9 @@ export default function RootLayout() { /> {/* WRAP YOUR APP WITH ANY ADDITIONAL PROVIDERS HERE */} {/* */} + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> <% if (props.stylingPackage?.options.selectedComponents.includes('bottom-sheet')) { %> @@ -57,6 +64,9 @@ export default function RootLayout() { <% } %> + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> {/* */} ); diff --git a/cli/src/templates/packages/nativewindui/tabs/app/_layout.tsx.ejs b/cli/src/templates/packages/nativewindui/tabs/app/_layout.tsx.ejs index 9d8ef337..17a8077c 100644 --- a/cli/src/templates/packages/nativewindui/tabs/app/_layout.tsx.ejs +++ b/cli/src/templates/packages/nativewindui/tabs/app/_layout.tsx.ejs @@ -2,6 +2,10 @@ import '../global.css'; import 'expo-dev-client'; import { ThemeProvider as NavThemeProvider } from '@react-navigation/native'; import { Icon } from '@roninoss/icons'; +<% if (props.stateManagementPackage?.name === "redux") { %> +import { Provider } from 'react-redux' +import store from 'store/store' +<% } %> <% if (props.stylingPackage?.options.selectedComponents.includes('action-sheet')) { %> import { ActionSheetProvider } from '@expo/react-native-action-sheet'; <% } %> @@ -37,6 +41,9 @@ export default function RootLayout() { /> {/* WRAP YOUR APP WITH ANY ADDITIONAL PROVIDERS HERE */} {/* */} + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> <% if (props.stylingPackage?.options.selectedComponents.includes('bottom-sheet')) { %> @@ -56,6 +63,9 @@ export default function RootLayout() { <% if (props.stylingPackage?.options.selectedComponents.includes('bottom-sheet')) { %> + <% } %> + <% if (props.stateManagementPackage?.name === "redux") { %> + <% } %> {/* */} diff --git a/cli/src/templates/packages/react-navigation/App.tsx.ejs b/cli/src/templates/packages/react-navigation/App.tsx.ejs index 88baafae..16121a8a 100644 --- a/cli/src/templates/packages/react-navigation/App.tsx.ejs +++ b/cli/src/templates/packages/react-navigation/App.tsx.ejs @@ -34,6 +34,10 @@ import "react-native-gesture-handler"; <% } else { %> import RootStack from "./navigation"; <% } %> +<% if (props.stateManagementPackage?.name === "redux") { %> +import { Provider } from 'react-redux' +import store from 'store/store' +<% } %> export default function App() { <% if (props.stylingPackage?.name === "tamagui") { %> @@ -54,16 +58,36 @@ export default function App() { return ( - + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> + + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> ); <% } else if (props.stylingPackage?.name === "restyle") { %> return ( - + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> + + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> ); <% } else { %> - return ; + return ( + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> + + <% if (props.stateManagementPackage?.name === "redux") { %> + + <% } %> + ); <% } %> } diff --git a/cli/src/templates/packages/redux/store/reducers/demoSlice.ts.ejs b/cli/src/templates/packages/redux/store/reducers/demoSlice.ts.ejs new file mode 100644 index 00000000..488f2d74 --- /dev/null +++ b/cli/src/templates/packages/redux/store/reducers/demoSlice.ts.ejs @@ -0,0 +1,23 @@ +import { createSlice } from '@reduxjs/toolkit'; + +interface InitialState { + demo: undefined; +} + +const initialState: InitialState = { + demo: undefined, +}; + +export const demoSlice = createSlice({ + name: 'demo', + initialState, + reducers: { + setDemo: (state, action) => { + state.demo = action.payload; + }, + }, +}); + +export const { setDemo } = demoSlice.actions; + +export default demoSlice.reducer; \ No newline at end of file diff --git a/cli/src/templates/packages/redux/store/store.ts.ejs b/cli/src/templates/packages/redux/store/store.ts.ejs new file mode 100644 index 00000000..d6834078 --- /dev/null +++ b/cli/src/templates/packages/redux/store/store.ts.ejs @@ -0,0 +1,23 @@ +import { combineReducers, configureStore } from '@reduxjs/toolkit' +import { useDispatch, useSelector } from 'react-redux' + +import demoReducer from './reducers/demoSlice' + + +const rootReducer = combineReducers({ + demo: demoReducer, +}) + + +const store = configureStore({ + reducer: rootReducer, +}) + +export default store + +export type RootState = ReturnType + +type AppDispatch = typeof store.dispatch + +export const useAppDispatch = useDispatch.withTypes() +export const useAppSelector = useSelector.withTypes() \ No newline at end of file diff --git a/cli/src/types.ts b/cli/src/types.ts index 48931900..ae30831d 100644 --- a/cli/src/types.ts +++ b/cli/src/types.ts @@ -26,7 +26,8 @@ export const availablePackages = [ 'restyle', 'unistyles', 'i18next', - 'vexo-analytics' + 'vexo-analytics', + 'redux' ] as const; export type AuthenticationSelect = 'supabase' | 'firebase' | undefined; @@ -43,6 +44,8 @@ export type Internalization = 'i18next'; export type Analytics = 'vexo-analytics'; +export type StateManagement = 'redux' | undefined; + export type SelectedComponents = | 'action-sheet' | 'activity-indicator' @@ -59,7 +62,7 @@ export type SelectedComponents = export type AvailablePackages = { name: (typeof availablePackages)[number]; - type: 'navigation' | 'styling' | 'authentication' | 'internationalization' | 'analytics'; + type: 'navigation' | 'styling' | 'authentication' | 'internationalization' | 'analytics' | 'state-management'; options?: { selectedComponents?: SelectedComponents[]; type?: NavigationTypes }; }; diff --git a/cli/src/utilities/configAnalytics.ts b/cli/src/utilities/configAnalytics.ts index 40dad50a..f66eb258 100644 --- a/cli/src/utilities/configAnalytics.ts +++ b/cli/src/utilities/configAnalytics.ts @@ -9,6 +9,7 @@ import { NavigationTypes, PackageManager, SelectedComponents, + StateManagement, StylingSelect } from '../types'; @@ -37,7 +38,8 @@ export async function storeConfigAnalytics({ osPlatform, osArch, osRelease, - analytics + analytics, + stateManagement }: { timestamp: string; cesVersion: string; @@ -54,6 +56,7 @@ export async function storeConfigAnalytics({ osArch: string; osRelease: string; analytics: Analytics; + stateManagement?: StateManagement; } & Partial) { if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') { console.log('Skipping analytics in development or test environment'); @@ -94,7 +97,8 @@ export async function storeConfigAnalytics({ osPlatform ?? '', osArch ?? '', osRelease ?? '', - analytics ?? '' + analytics ?? '', + stateManagement ?? '' ] ] }, diff --git a/cli/src/utilities/configureProjectFiles.ts b/cli/src/utilities/configureProjectFiles.ts index 9f130d99..fc706ea8 100644 --- a/cli/src/utilities/configureProjectFiles.ts +++ b/cli/src/utilities/configureProjectFiles.ts @@ -8,6 +8,7 @@ import { Internalization, NavigationSelect, NavigationTypes, + StateManagement, StylingSelect } from '../types'; import { getPackageManager, getVersionForPackageManager } from './getPackageManager'; @@ -21,7 +22,8 @@ export function configureProjectFiles( analyticsPackage: AvailablePackages | undefined, toolbox: Toolbox, cliResults: CliResults, - internalizationPackage: AvailablePackages | undefined + internalizationPackage: AvailablePackages | undefined, + stateManagementPackage: AvailablePackages | undefined ): string[] { // Define the files common to all templates to be generated let baseFiles = [ @@ -349,6 +351,13 @@ export function configureProjectFiles( } } + // add redux files if needed + if (stateManagementPackage?.name === 'redux') { + const reduxFiles = ['packages/redux/store/store.ts.ejs', 'packages/redux/store/reducers/demoSlice.ts.ejs']; + + files = [...files, ...reduxFiles]; + } + // Add npmrc file if user is using pnpm if (packageManager === 'pnpm') { files.push('base/.npmrc.ejs'); @@ -396,7 +405,8 @@ export function configureProjectFiles( osPlatform: os.platform(), osArch: os.arch(), osRelease: os.release(), - analytics: analyticsPackage?.name as Analytics + analytics: analyticsPackage?.name as Analytics, + stateManagement: stateManagementPackage?.name as StateManagement }); return files; diff --git a/cli/src/utilities/generateProjectFiles.ts b/cli/src/utilities/generateProjectFiles.ts index 15dd1ad5..db504b40 100644 --- a/cli/src/utilities/generateProjectFiles.ts +++ b/cli/src/utilities/generateProjectFiles.ts @@ -11,7 +11,8 @@ export function generateProjectFiles( packageManager: PackageManager, stylingPackage: AvailablePackages | undefined, toolbox: Toolbox, - internalizationPackage: AvailablePackages | undefined + internalizationPackage: AvailablePackages | undefined, + stateManagementPackage: AvailablePackages | undefined ) { const { projectName, packages, flags } = cliResults; @@ -67,6 +68,10 @@ export function generateProjectFiles( target = target.replace('packages/vexo-analytics/', ''); } + if (stateManagementPackage?.name === 'redux') { + target = target.replace('packages/redux/', ''); + } + const gen = toolbox.template.generate({ template, target: `./${projectName}/` + target, @@ -79,7 +84,8 @@ export function generateProjectFiles( packageManager, packages, stylingPackage, - internalizationPackage + internalizationPackage, + stateManagementPackage } }); diff --git a/cli/src/utilities/runCLI.ts b/cli/src/utilities/runCLI.ts index 6a7c6553..982fdbf6 100644 --- a/cli/src/utilities/runCLI.ts +++ b/cli/src/utilities/runCLI.ts @@ -10,7 +10,8 @@ import { NavigationTypes, PackageManager, SelectedComponents, - StylingSelect + StylingSelect, + StateManagement } from '../types'; import { loadConfigs, saveConfig } from './configStorage'; import { getDefaultPackageManagerVersion } from './getPackageManager'; @@ -346,6 +347,25 @@ export async function runCLI(toolbox: Toolbox, projectName: string): Promise