diff --git a/assets/js/37f4af6f.a6d80bfc.js b/assets/js/37f4af6f.a6d80bfc.js
new file mode 100644
index 00000000..4a9a29c3
--- /dev/null
+++ b/assets/js/37f4af6f.a6d80bfc.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[6922],{4816:e=>{e.exports=JSON.parse('{"label":"Ignite","permalink":"/docs/tags/ignite","allTagsPath":"/docs/tags","count":1,"items":[{"id":"recipes/SettingUpYarnMonorepo","title":"Setting up a Yarn monorepo with Ignite","description":"How to set up a Yarn monorepo using Ignite and two extra shared utilities","permalink":"/docs/recipes/SettingUpYarnMonorepo"}],"unlisted":false}')}}]);
\ No newline at end of file
diff --git a/assets/js/55960ee5.d8b58ca8.js b/assets/js/55960ee5.0cf15c61.js
similarity index 77%
rename from assets/js/55960ee5.d8b58ca8.js
rename to assets/js/55960ee5.0cf15c61.js
index d2cec9a5..d534b0d1 100644
--- a/assets/js/55960ee5.d8b58ca8.js
+++ b/assets/js/55960ee5.0cf15c61.js
@@ -1 +1 @@
-"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[4296],{2416:a=>{a.exports=JSON.parse('[{"label":"Archive","permalink":"/docs/tags/archive","count":1},{"label":"Expo","permalink":"/docs/tags/expo","count":7},{"label":"VectorIcons","permalink":"/docs/tags/vector-icons","count":1},{"label":"FontAwesome","permalink":"/docs/tags/font-awesome","count":1},{"label":"Icons","permalink":"/docs/tags/icons","count":1},{"label":"Community","permalink":"/docs/tags/community","count":1},{"label":"Intro","permalink":"/docs/tags/intro","count":2},{"label":"Accessibility","permalink":"/docs/tags/accessibility","count":3},{"label":"Apollo Client","permalink":"/docs/tags/apollo-client","count":1},{"label":"Cache","permalink":"/docs/tags/cache","count":1},{"label":"Reactotron","permalink":"/docs/tags/reactotron","count":1},{"label":"Custom Commands","permalink":"/docs/tags/custom-commands","count":1},{"label":"authentication","permalink":"/docs/tags/authentication","count":2},{"label":"supabase","permalink":"/docs/tags/supabase","count":1},{"label":"login","permalink":"/docs/tags/login","count":1},{"label":"signup","permalink":"/docs/tags/signup","count":1},{"label":"session","permalink":"/docs/tags/session","count":1},{"label":"Guide","permalink":"/docs/tags/guide","count":7},{"label":"CI/CD","permalink":"/docs/tags/ci-cd","count":2},{"label":"iOS","permalink":"/docs/tags/i-os","count":5},{"label":"Android","permalink":"/docs/tags/android","count":6},{"label":"Testing","permalink":"/docs/tags/testing","count":2},{"label":"Apisauce","permalink":"/docs/tags/apisauce","count":1},{"label":"expo-updates","permalink":"/docs/tags/expo-updates","count":1},{"label":"EAS Update","permalink":"/docs/tags/eas-update","count":1},{"label":"imports","permalink":"/docs/tags/imports","count":1},{"label":"prettier","permalink":"/docs/tags/prettier","count":1},{"label":"Environment Variables","permalink":"/docs/tags/environment-variables","count":1},{"label":"expo-router","permalink":"/docs/tags/expo-router","count":1},{"label":"react-navigation","permalink":"/docs/tags/react-navigation","count":1},{"label":"Generator","permalink":"/docs/tags/generator","count":1},{"label":"PowerSync","permalink":"/docs/tags/power-sync","count":1},{"label":"React Native","permalink":"/docs/tags/react-native","count":1},{"label":"Backend","permalink":"/docs/tags/backend","count":1},{"label":"State management","permalink":"/docs/tags/state-management","count":4},{"label":"Database","permalink":"/docs/tags/database","count":1},{"label":"Data Synchronization","permalink":"/docs/tags/data-synchronization","count":1},{"label":"Offline Support","permalink":"/docs/tags/offline-support","count":1},{"label":"Maestro","permalink":"/docs/tags/maestro","count":1},{"label":"i18n","permalink":"/docs/tags/i-18-n","count":1},{"label":"MMKV","permalink":"/docs/tags/mmkv","count":1},{"label":"AsyncStorage","permalink":"/docs/tags/async-storage","count":1},{"label":"Monorepo","permalink":"/docs/tags/monorepo","count":1},{"label":"Yarn","permalink":"/docs/tags/yarn","count":2},{"label":"Debug","permalink":"/docs/tags/debug","count":1},{"label":"EAS","permalink":"/docs/tags/eas","count":1},{"label":"expo-dev-client","permalink":"/docs/tags/expo-dev-client","count":1},{"label":"VisionCamera","permalink":"/docs/tags/vision-camera","count":1},{"label":"react-native-vision-camera","permalink":"/docs/tags/react-native-vision-camera","count":1},{"label":"Redux","permalink":"/docs/tags/redux","count":1},{"label":"MobX","permalink":"/docs/tags/mob-x","count":3},{"label":"Hardware","permalink":"/docs/tags/hardware","count":1},{"label":"UIRequiredDeviceCapabilities","permalink":"/docs/tags/ui-required-device-capabilities","count":1},{"label":"uses-feature","permalink":"/docs/tags/uses-feature","count":1},{"label":"prebuild","permalink":"/docs/tags/prebuild","count":1},{"label":"cng","permalink":"/docs/tags/cng","count":1},{"label":"TextField","permalink":"/docs/tags/text-field","count":1},{"label":"SelectField","permalink":"/docs/tags/select-field","count":1},{"label":"UI","permalink":"/docs/tags/ui","count":2},{"label":"Theming","permalink":"/docs/tags/theming","count":3},{"label":"colors","permalink":"/docs/tags/colors","count":3},{"label":"darkmode","permalink":"/docs/tags/darkmode","count":3},{"label":"emotion.js","permalink":"/docs/tags/emotion-js","count":1},{"label":"styled-components","permalink":"/docs/tags/styled-components","count":1},{"label":"unistyles","permalink":"/docs/tags/unistyles","count":1},{"label":"TypeScript","permalink":"/docs/tags/type-script","count":1},{"label":"Babel","permalink":"/docs/tags/babel","count":1},{"label":"FlatList","permalink":"/docs/tags/flat-list","count":1},{"label":"SectionList","permalink":"/docs/tags/section-list","count":1},{"label":"scrollTo","permalink":"/docs/tags/scroll-to","count":1},{"label":"Dependencies","permalink":"/docs/tags/dependencies","count":2},{"label":"Zustand","permalink":"/docs/tags/zustand","count":1}]')}}]);
\ No newline at end of file
+"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[4296],{2416:a=>{a.exports=JSON.parse('[{"label":"Archive","permalink":"/docs/tags/archive","count":1},{"label":"Expo","permalink":"/docs/tags/expo","count":7},{"label":"VectorIcons","permalink":"/docs/tags/vector-icons","count":1},{"label":"FontAwesome","permalink":"/docs/tags/font-awesome","count":1},{"label":"Icons","permalink":"/docs/tags/icons","count":1},{"label":"Community","permalink":"/docs/tags/community","count":1},{"label":"Intro","permalink":"/docs/tags/intro","count":2},{"label":"Accessibility","permalink":"/docs/tags/accessibility","count":3},{"label":"Apollo Client","permalink":"/docs/tags/apollo-client","count":1},{"label":"Cache","permalink":"/docs/tags/cache","count":1},{"label":"Reactotron","permalink":"/docs/tags/reactotron","count":1},{"label":"Custom Commands","permalink":"/docs/tags/custom-commands","count":1},{"label":"authentication","permalink":"/docs/tags/authentication","count":2},{"label":"supabase","permalink":"/docs/tags/supabase","count":1},{"label":"login","permalink":"/docs/tags/login","count":1},{"label":"signup","permalink":"/docs/tags/signup","count":1},{"label":"session","permalink":"/docs/tags/session","count":1},{"label":"Guide","permalink":"/docs/tags/guide","count":7},{"label":"CI/CD","permalink":"/docs/tags/ci-cd","count":2},{"label":"iOS","permalink":"/docs/tags/i-os","count":5},{"label":"Android","permalink":"/docs/tags/android","count":6},{"label":"Testing","permalink":"/docs/tags/testing","count":2},{"label":"Apisauce","permalink":"/docs/tags/apisauce","count":1},{"label":"expo-updates","permalink":"/docs/tags/expo-updates","count":1},{"label":"EAS Update","permalink":"/docs/tags/eas-update","count":1},{"label":"imports","permalink":"/docs/tags/imports","count":1},{"label":"prettier","permalink":"/docs/tags/prettier","count":1},{"label":"Environment Variables","permalink":"/docs/tags/environment-variables","count":1},{"label":"expo-router","permalink":"/docs/tags/expo-router","count":1},{"label":"react-navigation","permalink":"/docs/tags/react-navigation","count":1},{"label":"Generator","permalink":"/docs/tags/generator","count":1},{"label":"PowerSync","permalink":"/docs/tags/power-sync","count":1},{"label":"React Native","permalink":"/docs/tags/react-native","count":1},{"label":"Backend","permalink":"/docs/tags/backend","count":1},{"label":"State management","permalink":"/docs/tags/state-management","count":4},{"label":"Database","permalink":"/docs/tags/database","count":1},{"label":"Data Synchronization","permalink":"/docs/tags/data-synchronization","count":1},{"label":"Offline Support","permalink":"/docs/tags/offline-support","count":1},{"label":"Maestro","permalink":"/docs/tags/maestro","count":1},{"label":"i18n","permalink":"/docs/tags/i-18-n","count":1},{"label":"MMKV","permalink":"/docs/tags/mmkv","count":1},{"label":"AsyncStorage","permalink":"/docs/tags/async-storage","count":1},{"label":"Monorepo","permalink":"/docs/tags/monorepo","count":2},{"label":"Yarn","permalink":"/docs/tags/yarn","count":3},{"label":"Debug","permalink":"/docs/tags/debug","count":1},{"label":"EAS","permalink":"/docs/tags/eas","count":1},{"label":"expo-dev-client","permalink":"/docs/tags/expo-dev-client","count":1},{"label":"VisionCamera","permalink":"/docs/tags/vision-camera","count":1},{"label":"react-native-vision-camera","permalink":"/docs/tags/react-native-vision-camera","count":1},{"label":"Redux","permalink":"/docs/tags/redux","count":1},{"label":"MobX","permalink":"/docs/tags/mob-x","count":3},{"label":"Hardware","permalink":"/docs/tags/hardware","count":1},{"label":"UIRequiredDeviceCapabilities","permalink":"/docs/tags/ui-required-device-capabilities","count":1},{"label":"uses-feature","permalink":"/docs/tags/uses-feature","count":1},{"label":"prebuild","permalink":"/docs/tags/prebuild","count":1},{"label":"cng","permalink":"/docs/tags/cng","count":1},{"label":"TextField","permalink":"/docs/tags/text-field","count":1},{"label":"SelectField","permalink":"/docs/tags/select-field","count":1},{"label":"UI","permalink":"/docs/tags/ui","count":2},{"label":"Ignite","permalink":"/docs/tags/ignite","count":1},{"label":"Theming","permalink":"/docs/tags/theming","count":3},{"label":"colors","permalink":"/docs/tags/colors","count":3},{"label":"darkmode","permalink":"/docs/tags/darkmode","count":3},{"label":"emotion.js","permalink":"/docs/tags/emotion-js","count":1},{"label":"styled-components","permalink":"/docs/tags/styled-components","count":1},{"label":"unistyles","permalink":"/docs/tags/unistyles","count":1},{"label":"TypeScript","permalink":"/docs/tags/type-script","count":1},{"label":"Babel","permalink":"/docs/tags/babel","count":1},{"label":"FlatList","permalink":"/docs/tags/flat-list","count":1},{"label":"SectionList","permalink":"/docs/tags/section-list","count":1},{"label":"scrollTo","permalink":"/docs/tags/scroll-to","count":1},{"label":"Dependencies","permalink":"/docs/tags/dependencies","count":2},{"label":"Zustand","permalink":"/docs/tags/zustand","count":1}]')}}]);
\ No newline at end of file
diff --git a/assets/js/78a0b2f7.2498fe76.js b/assets/js/78a0b2f7.2498fe76.js
deleted file mode 100644
index be2abc6b..00000000
--- a/assets/js/78a0b2f7.2498fe76.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[4224],{4068:(e,n,o)=>{o.r(n),o.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>a,toc:()=>l});var t=o(7624),r=o(2172);const s={title:"Switch Between Expo Go and Expo CNG",description:"Switch an Expo Go project to an Expo CNG project and visa versa",tags:["Expo"],last_update:{author:"Justin Poliachik"},publish_date:new Date("2024-01-11T00:00:00.000Z")},i="Switch a Project Between Expo Go and Expo CNG",a={id:"recipes/SwitchBetweenExpoGoCNG",title:"Switch Between Expo Go and Expo CNG",description:"Switch an Expo Go project to an Expo CNG project and visa versa",source:"@site/docs/recipes/SwitchBetweenExpoGoCNG.md",sourceDirName:"recipes",slug:"/recipes/SwitchBetweenExpoGoCNG",permalink:"/docs/recipes/SwitchBetweenExpoGoCNG",draft:!1,unlisted:!1,tags:[{label:"Expo",permalink:"/docs/tags/expo"}],version:"current",lastUpdatedBy:"Justin Poliachik",lastUpdatedAt:1731512989,formattedLastUpdatedAt:"Nov 13, 2024",frontMatter:{title:"Switch Between Expo Go and Expo CNG",description:"Switch an Expo Go project to an Expo CNG project and visa versa",tags:["Expo"],last_update:{author:"Justin Poliachik"},publish_date:"2024-01-11T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"SelectField using `react-native-bottom-sheet`",permalink:"/docs/recipes/SelectFieldWithBottomSheet"},next:{title:"Theming Ignite with Emotion.js",permalink:"/docs/recipes/Theming-Emotion"}},c={},l=[{value:"Expo Go -> Expo CNG",id:"expo-go---expo-cng",level:2},{value:"Expo CNG -> Expo Go",id:"expo-cng---expo-go",level:2},{value:"Steps",id:"steps",level:3},{value:"Update
package.json
scripts",id:"update-packagejson-scripts",level:4},{value:"Remove native directories",id:"remove-native-directories",level:4},{value:"Remove
react-native-mmkv
in favor of
@react-native-async-storage/async-storage
",id:"removereact-native-mmkv-in-favor-of-react-native-async-storageasync-storage",level:4},{value:"Remove
react-native-keyboard-controller
",id:"removereact-native-keyboard-controller",level:4},{value:"Sync Expo packages to be compatible with Expo Go",id:"sync-expo-packages-to-be-compatible-with-expo-go",level:4},{value:"Run the app!",id:"run-the-app",level:4}];function p(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",...(0,r.M)(),...e.components},{Details:o}=n;return o||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"switch-a-project-between-expo-go-and-expo-cng",children:"Switch a Project Between Expo Go and Expo CNG"}),"\n",(0,t.jsx)(n.p,{children:"If you created an Ignite project using the Expo Go workflow and you need to transition to Expo CNG (Continuous Native Generation) or visa versa, this guide will teach you how to reconfigure your project."}),"\n",(0,t.jsx)(n.h2,{id:"expo-go---expo-cng",children:"Expo Go -> Expo CNG"}),"\n",(0,t.jsx)(n.p,{children:"If you started with Expo Go but now need to add a library with native code, create your own custom native code, or modify native project configuration, you'll no longer be able to run your app inside Expo Go."}),"\n",(0,t.jsxs)(n.p,{children:["Thankfully, this is super easy thanks to ",(0,t.jsx)(n.a,{href:"https://docs.expo.dev/workflow/continuous-native-generation/",children:"Expo's Continuous Native Generation"}),"!\nWe only need to slightly change how we build & run our app."]}),"\n",(0,t.jsxs)(n.p,{children:["In ",(0,t.jsx)(n.code,{children:"package.json"}),", modify scripts:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-diff",children:'--"android": "npx expo start --android",\n--"ios": "npx expo start --ios",\n++"android": "npx expo run:android",\n++"ios": "npx expo run:ios",\n'})}),"\n",(0,t.jsx)(n.p,{children:"Expo handles the rest!"}),"\n",(0,t.jsxs)(n.p,{children:["When you run ",(0,t.jsx)(n.code,{children:"npm run ios"})," or ",(0,t.jsx)(n.code,{children:"npm run android"}),", Expo will generate native projects and create ",(0,t.jsx)(n.code,{children:"ios"})," and ",(0,t.jsx)(n.code,{children:"android"})," directories, create a development build, and launch your standalone app. You are now successfully using Expo CNG!"]}),"\n",(0,t.jsxs)(n.p,{children:["To learn more, check out Expo's documentation on ",(0,t.jsx)(n.a,{href:"https://docs.expo.dev/workflow/customizing/",children:"adding custom native code"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"expo-cng---expo-go",children:"Expo CNG -> Expo Go"}),"\n",(0,t.jsx)(n.p,{children:"If you started with Expo CNG workflow, but your app isn't utilizing any custom native functionality and you want to use Expo Go for developing your app, follow these steps!"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Important Note"}),": To successfully run your app using Expo Go, your project must not contain ",(0,t.jsx)(n.em,{children:"any"})," custom native code, project configuration, or native libraries outside of the ",(0,t.jsx)(n.a,{href:"https://docs.expo.dev/versions/latest/",children:"Expo SDK"}),". Your project also can't contain any ",(0,t.jsx)(n.code,{children:"expo.plugins"})," inside your ",(0,t.jsx)(n.code,{children:"app.json"}),". If your app contains native code, libraries, configuration, or plugins and you attempt to run inside Expo Go, expect your app to crash or not function properly."]}),"\n",(0,t.jsx)(n.h3,{id:"steps",children:"Steps"}),"\n",(0,t.jsxs)(n.h4,{id:"update-packagejson-scripts",children:["Update ",(0,t.jsx)(n.code,{children:"package.json"})," scripts"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-diff",children:'--"android": "npx expo run:android",\n--"ios": "npx expo run:ios",\n++"android": "npx expo start --android",\n++"ios": "npx expo start --ios",\n'})}),"\n",(0,t.jsx)(n.h4,{id:"remove-native-directories",children:"Remove native directories"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"rm -rf android ios\n"})}),"\n",(0,t.jsxs)(n.h4,{id:"removereact-native-mmkv-in-favor-of-react-native-async-storageasync-storage",children:["Remove",(0,t.jsx)(n.code,{children:"react-native-mmkv"})," in favor of ",(0,t.jsx)(n.code,{children:"@react-native-async-storage/async-storage"})]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsx)(n.li,{children:"Swap packages"}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"yarn remove react-native-mmkv\nnpx expo install @react-native-async-storage/async-storage\n"})}),"\n",(0,t.jsxs)(n.ol,{start:"2",children:["\n",(0,t.jsxs)(n.li,{children:["Update the storage util in ",(0,t.jsx)(n.code,{children:"app/utils/storage.ts"})]}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"removereact-native-keyboard-controller",children:["Remove",(0,t.jsx)(n.code,{children:"react-native-keyboard-controller"})]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsx)(n.li,{children:"Remove the package"}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"yarn remove react-native-keyboard-controller\n"})}),"\n",(0,t.jsxs)(n.ol,{start:"2",children:["\n",(0,t.jsxs)(n.li,{children:["Remove the ",(0,t.jsx)(n.code,{children:"
"})," in ",(0,t.jsx)(n.code,{children:"app/app.tsx"})]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-diff",children:'-import { KeyboardProvider } from "react-native-keyboard-controller"\n\n// ...\n\nreturn (\n \n \n- \n \n- \n \n \n)\n'})}),"\n",(0,t.jsxs)(n.ol,{start:"3",children:["\n",(0,t.jsxs)(n.li,{children:["Update ",(0,t.jsx)(n.code,{children:"app/components/Screen.tsx"})]}),"\n"]}),"\n",(0,t.jsxs)(o,{children:[(0,t.jsx)("summary",{children:"Screen.tsx (expand to copy)"}),(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-tsx",metastring:'title="/app/components/Screen.tsx"',children:'import { useScrollToTop } from "@react-navigation/native";\nimport { StatusBar, StatusBarProps, StatusBarStyle } from "expo-status-bar";\nimport React, { useRef, useState } from "react";\nimport {\n KeyboardAvoidingView,\n KeyboardAvoidingViewProps,\n LayoutChangeEvent,\n Platform,\n ScrollView,\n ScrollViewProps,\n StyleProp,\n View,\n ViewStyle,\n} from "react-native";\nimport { $styles } from "@/theme";\nimport { ExtendedEdge, useSafeAreaInsetsStyle } from "@/utils/useSafeAreaInsetsStyle";\nimport { useAppTheme } from "@/utils/useAppTheme";\n\ninterface BaseScreenProps {\n /**\n * Children components.\n */\n children?: React.ReactNode;\n /**\n * Style for the outer content container useful for padding & margin.\n */\n style?: StyleProp;\n /**\n * Style for the inner content container useful for padding & margin.\n */\n contentContainerStyle?: StyleProp;\n /**\n * Override the default edges for the safe area.\n */\n safeAreaEdges?: ExtendedEdge[];\n /**\n * Background color\n */\n backgroundColor?: string;\n /**\n * Status bar setting. Defaults to dark.\n */\n statusBarStyle?: StatusBarStyle;\n /**\n * By how much should we offset the keyboard? Defaults to 0.\n */\n keyboardOffset?: number;\n /**\n * Pass any additional props directly to the StatusBar component.\n */\n StatusBarProps?: StatusBarProps;\n /**\n * Pass any additional props directly to the KeyboardAvoidingView component.\n */\n KeyboardAvoidingViewProps?: KeyboardAvoidingViewProps;\n}\n\ninterface FixedScreenProps extends BaseScreenProps {\n preset?: "fixed";\n}\ninterface ScrollScreenProps extends BaseScreenProps {\n preset?: "scroll";\n /**\n * Should keyboard persist on screen tap. Defaults to handled.\n * Only applies to scroll preset.\n */\n keyboardShouldPersistTaps?: "handled" | "always" | "never";\n /**\n * Pass any additional props directly to the ScrollView component.\n */\n ScrollViewProps?: ScrollViewProps;\n}\n\ninterface AutoScreenProps extends Omit {\n preset?: "auto";\n /**\n * Threshold to trigger the automatic disabling/enabling of scroll ability.\n * Defaults to `{ percent: 0.92 }`.\n */\n scrollEnabledToggleThreshold?: { percent?: number; point?: number };\n}\n\nexport type ScreenProps = ScrollScreenProps | FixedScreenProps | AutoScreenProps;\n\nconst isIos = Platform.OS === "ios";\n\ntype ScreenPreset = "fixed" | "scroll" | "auto";\n\n/**\n * @param {ScreenPreset?} preset - The preset to check.\n * @returns {boolean} - Whether the preset is non-scrolling.\n */\nfunction isNonScrolling(preset?: ScreenPreset) {\n return !preset || preset === "fixed";\n}\n\n/**\n * Custom hook that handles the automatic enabling/disabling of scroll ability based on the content size and screen size.\n * @param {UseAutoPresetProps} props - The props for the `useAutoPreset` hook.\n * @returns {{boolean, Function, Function}} - The scroll state, and the `onContentSizeChange` and `onLayout` functions.\n */\nfunction useAutoPreset(props: AutoScreenProps): {\n scrollEnabled: boolean;\n onContentSizeChange: (w: number, h: number) => void;\n onLayout: (e: LayoutChangeEvent) => void;\n} {\n const { preset, scrollEnabledToggleThreshold } = props;\n const { percent = 0.92, point = 0 } = scrollEnabledToggleThreshold || {};\n\n const scrollViewHeight = useRef(null);\n const scrollViewContentHeight = useRef(null);\n const [scrollEnabled, setScrollEnabled] = useState(true);\n\n function updateScrollState() {\n if (scrollViewHeight.current === null || scrollViewContentHeight.current === null) return;\n\n // check whether content fits the screen then toggle scroll state according to it\n const contentFitsScreen = (function () {\n if (point) {\n return scrollViewContentHeight.current < scrollViewHeight.current - point;\n } else {\n return scrollViewContentHeight.current < scrollViewHeight.current * percent;\n }\n })();\n\n // content is less than the size of the screen, so we can disable scrolling\n if (scrollEnabled && contentFitsScreen) setScrollEnabled(false);\n\n // content is greater than the size of the screen, so let\'s enable scrolling\n if (!scrollEnabled && !contentFitsScreen) setScrollEnabled(true);\n }\n\n /**\n * @param {number} w - The width of the content.\n * @param {number} h - The height of the content.\n */\n function onContentSizeChange(w: number, h: number) {\n // update scroll-view content height\n scrollViewContentHeight.current = h;\n updateScrollState();\n }\n\n /**\n * @param {LayoutChangeEvent} e = The layout change event.\n */\n function onLayout(e: LayoutChangeEvent) {\n const { height } = e.nativeEvent.layout;\n // update scroll-view height\n scrollViewHeight.current = height;\n updateScrollState();\n }\n\n // update scroll state on every render\n if (preset === "auto") updateScrollState();\n\n return {\n scrollEnabled: preset === "auto" ? scrollEnabled : true,\n onContentSizeChange,\n onLayout,\n };\n}\n\n/**\n * @param {ScreenProps} props - The props for the `ScreenWithoutScrolling` component.\n * @returns {JSX.Element} - The rendered `ScreenWithoutScrolling` component.\n */\nfunction ScreenWithoutScrolling(props: ScreenProps) {\n const { style, contentContainerStyle, children } = props;\n return (\n \n {children}\n \n );\n}\n\n/**\n * @param {ScreenProps} props - The props for the `ScreenWithScrolling` component.\n * @returns {JSX.Element} - The rendered `ScreenWithScrolling` component.\n */\nfunction ScreenWithScrolling(props: ScreenProps) {\n const {\n children,\n keyboardShouldPersistTaps = "handled",\n contentContainerStyle,\n ScrollViewProps,\n style,\n } = props as ScrollScreenProps;\n\n const ref = useRef(null);\n\n const { scrollEnabled, onContentSizeChange, onLayout } = useAutoPreset(props as AutoScreenProps);\n\n // Add native behavior of pressing the active tab to scroll to the top of the content\n // More info at: https://reactnavigation.org/docs/use-scroll-to-top/\n useScrollToTop(ref);\n\n return (\n {\n onLayout(e);\n ScrollViewProps?.onLayout?.(e);\n }}\n onContentSizeChange={(w: number, h: number) => {\n onContentSizeChange(w, h);\n ScrollViewProps?.onContentSizeChange?.(w, h);\n }}\n style={[$outerStyle, ScrollViewProps?.style, style]}\n contentContainerStyle={[\n $innerStyle,\n ScrollViewProps?.contentContainerStyle,\n contentContainerStyle,\n ]}\n >\n {children}\n \n );\n}\n\n/**\n * Represents a screen component that provides a consistent layout and behavior for different screen presets.\n * The `Screen` component can be used with different presets such as "fixed", "scroll", or "auto".\n * It handles safe area insets, status bar settings, keyboard avoiding behavior, and scrollability based on the preset.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Screen/}\n * @param {ScreenProps} props - The props for the `Screen` component.\n * @returns {JSX.Element} The rendered `Screen` component.\n */\nexport function Screen(props: ScreenProps) {\n const {\n theme: { colors },\n themeContext,\n } = useAppTheme();\n const {\n backgroundColor,\n KeyboardAvoidingViewProps,\n keyboardOffset = 0,\n safeAreaEdges,\n StatusBarProps,\n statusBarStyle,\n } = props;\n\n const $containerInsets = useSafeAreaInsetsStyle(safeAreaEdges);\n\n return (\n \n \n\n \n {isNonScrolling(props.preset) ? (\n \n ) : (\n \n )}\n \n \n );\n}\n\nconst $containerStyle: ViewStyle = {\n flex: 1,\n height: "100%",\n width: "100%",\n};\n\nconst $outerStyle: ViewStyle = {\n flex: 1,\n height: "100%",\n width: "100%",\n};\n\nconst $innerStyle: ViewStyle = {\n justifyContent: "flex-start",\n alignItems: "stretch",\n};\n'})})]}),"\n",(0,t.jsx)("br",{}),"\n",(0,t.jsx)(n.h4,{id:"sync-expo-packages-to-be-compatible-with-expo-go",children:"Sync Expo packages to be compatible with Expo Go"}),"\n",(0,t.jsxs)(n.p,{children:["Running ",(0,t.jsx)(n.code,{children:"npx expo install --check"})," will check all of the expo packages in their SDK against the version of ",(0,t.jsx)(n.code,{children:"expo"})," that is installed to ensure compatibility."]}),"\n",(0,t.jsxs)(n.p,{children:["You can accept these changes or run ",(0,t.jsx)(n.code,{children:"npx expo install --fix"})," to apply them directly without running the check."]}),"\n",(0,t.jsx)(n.h4,{id:"run-the-app",children:"Run the app!"}),"\n",(0,t.jsxs)(n.p,{children:["That's it! You should be able to run ",(0,t.jsx)(n.code,{children:"yarn start"})," and tap ",(0,t.jsx)(n.code,{children:"i"})," or ",(0,t.jsx)(n.code,{children:"a"})," in terminal to launch iOS or Android respectively in Expo Go."]})]})}function d(e={}){const{wrapper:n}={...(0,r.M)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(p,{...e})}):p(e)}},2172:(e,n,o)=>{o.d(n,{I:()=>a,M:()=>i});var t=o(1504);const r={},s=t.createContext(r);function i(e){const n=t.useContext(s);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/78a0b2f7.3e1b380d.js b/assets/js/78a0b2f7.3e1b380d.js
new file mode 100644
index 00000000..50a2ecde
--- /dev/null
+++ b/assets/js/78a0b2f7.3e1b380d.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[4224],{4068:(e,n,o)=>{o.r(n),o.d(n,{assets:()=>c,contentTitle:()=>i,default:()=>d,frontMatter:()=>s,metadata:()=>a,toc:()=>l});var t=o(7624),r=o(2172);const s={title:"Switch Between Expo Go and Expo CNG",description:"Switch an Expo Go project to an Expo CNG project and visa versa",tags:["Expo"],last_update:{author:"Justin Poliachik"},publish_date:new Date("2024-01-11T00:00:00.000Z")},i="Switch a Project Between Expo Go and Expo CNG",a={id:"recipes/SwitchBetweenExpoGoCNG",title:"Switch Between Expo Go and Expo CNG",description:"Switch an Expo Go project to an Expo CNG project and visa versa",source:"@site/docs/recipes/SwitchBetweenExpoGoCNG.md",sourceDirName:"recipes",slug:"/recipes/SwitchBetweenExpoGoCNG",permalink:"/docs/recipes/SwitchBetweenExpoGoCNG",draft:!1,unlisted:!1,tags:[{label:"Expo",permalink:"/docs/tags/expo"}],version:"current",lastUpdatedBy:"Justin Poliachik",lastUpdatedAt:1731512989,formattedLastUpdatedAt:"Nov 13, 2024",frontMatter:{title:"Switch Between Expo Go and Expo CNG",description:"Switch an Expo Go project to an Expo CNG project and visa versa",tags:["Expo"],last_update:{author:"Justin Poliachik"},publish_date:"2024-01-11T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Setting up a Yarn monorepo with Ignite",permalink:"/docs/recipes/SettingUpYarnMonorepo"},next:{title:"Theming Ignite with Emotion.js",permalink:"/docs/recipes/Theming-Emotion"}},c={},l=[{value:"Expo Go -> Expo CNG",id:"expo-go---expo-cng",level:2},{value:"Expo CNG -> Expo Go",id:"expo-cng---expo-go",level:2},{value:"Steps",id:"steps",level:3},{value:"Update package.json
scripts",id:"update-packagejson-scripts",level:4},{value:"Remove native directories",id:"remove-native-directories",level:4},{value:"Removereact-native-mmkv
in favor of @react-native-async-storage/async-storage
",id:"removereact-native-mmkv-in-favor-of-react-native-async-storageasync-storage",level:4},{value:"Removereact-native-keyboard-controller
",id:"removereact-native-keyboard-controller",level:4},{value:"Sync Expo packages to be compatible with Expo Go",id:"sync-expo-packages-to-be-compatible-with-expo-go",level:4},{value:"Run the app!",id:"run-the-app",level:4}];function p(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",...(0,r.M)(),...e.components},{Details:o}=n;return o||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"switch-a-project-between-expo-go-and-expo-cng",children:"Switch a Project Between Expo Go and Expo CNG"}),"\n",(0,t.jsx)(n.p,{children:"If you created an Ignite project using the Expo Go workflow and you need to transition to Expo CNG (Continuous Native Generation) or visa versa, this guide will teach you how to reconfigure your project."}),"\n",(0,t.jsx)(n.h2,{id:"expo-go---expo-cng",children:"Expo Go -> Expo CNG"}),"\n",(0,t.jsx)(n.p,{children:"If you started with Expo Go but now need to add a library with native code, create your own custom native code, or modify native project configuration, you'll no longer be able to run your app inside Expo Go."}),"\n",(0,t.jsxs)(n.p,{children:["Thankfully, this is super easy thanks to ",(0,t.jsx)(n.a,{href:"https://docs.expo.dev/workflow/continuous-native-generation/",children:"Expo's Continuous Native Generation"}),"!\nWe only need to slightly change how we build & run our app."]}),"\n",(0,t.jsxs)(n.p,{children:["In ",(0,t.jsx)(n.code,{children:"package.json"}),", modify scripts:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-diff",children:'--"android": "npx expo start --android",\n--"ios": "npx expo start --ios",\n++"android": "npx expo run:android",\n++"ios": "npx expo run:ios",\n'})}),"\n",(0,t.jsx)(n.p,{children:"Expo handles the rest!"}),"\n",(0,t.jsxs)(n.p,{children:["When you run ",(0,t.jsx)(n.code,{children:"npm run ios"})," or ",(0,t.jsx)(n.code,{children:"npm run android"}),", Expo will generate native projects and create ",(0,t.jsx)(n.code,{children:"ios"})," and ",(0,t.jsx)(n.code,{children:"android"})," directories, create a development build, and launch your standalone app. You are now successfully using Expo CNG!"]}),"\n",(0,t.jsxs)(n.p,{children:["To learn more, check out Expo's documentation on ",(0,t.jsx)(n.a,{href:"https://docs.expo.dev/workflow/customizing/",children:"adding custom native code"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"expo-cng---expo-go",children:"Expo CNG -> Expo Go"}),"\n",(0,t.jsx)(n.p,{children:"If you started with Expo CNG workflow, but your app isn't utilizing any custom native functionality and you want to use Expo Go for developing your app, follow these steps!"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Important Note"}),": To successfully run your app using Expo Go, your project must not contain ",(0,t.jsx)(n.em,{children:"any"})," custom native code, project configuration, or native libraries outside of the ",(0,t.jsx)(n.a,{href:"https://docs.expo.dev/versions/latest/",children:"Expo SDK"}),". Your project also can't contain any ",(0,t.jsx)(n.code,{children:"expo.plugins"})," inside your ",(0,t.jsx)(n.code,{children:"app.json"}),". If your app contains native code, libraries, configuration, or plugins and you attempt to run inside Expo Go, expect your app to crash or not function properly."]}),"\n",(0,t.jsx)(n.h3,{id:"steps",children:"Steps"}),"\n",(0,t.jsxs)(n.h4,{id:"update-packagejson-scripts",children:["Update ",(0,t.jsx)(n.code,{children:"package.json"})," scripts"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-diff",children:'--"android": "npx expo run:android",\n--"ios": "npx expo run:ios",\n++"android": "npx expo start --android",\n++"ios": "npx expo start --ios",\n'})}),"\n",(0,t.jsx)(n.h4,{id:"remove-native-directories",children:"Remove native directories"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"rm -rf android ios\n"})}),"\n",(0,t.jsxs)(n.h4,{id:"removereact-native-mmkv-in-favor-of-react-native-async-storageasync-storage",children:["Remove",(0,t.jsx)(n.code,{children:"react-native-mmkv"})," in favor of ",(0,t.jsx)(n.code,{children:"@react-native-async-storage/async-storage"})]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsx)(n.li,{children:"Swap packages"}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"yarn remove react-native-mmkv\nnpx expo install @react-native-async-storage/async-storage\n"})}),"\n",(0,t.jsxs)(n.ol,{start:"2",children:["\n",(0,t.jsxs)(n.li,{children:["Update the storage util in ",(0,t.jsx)(n.code,{children:"app/utils/storage.ts"})]}),"\n"]}),"\n",(0,t.jsxs)(n.h4,{id:"removereact-native-keyboard-controller",children:["Remove",(0,t.jsx)(n.code,{children:"react-native-keyboard-controller"})]}),"\n",(0,t.jsxs)(n.ol,{children:["\n",(0,t.jsx)(n.li,{children:"Remove the package"}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"yarn remove react-native-keyboard-controller\n"})}),"\n",(0,t.jsxs)(n.ol,{start:"2",children:["\n",(0,t.jsxs)(n.li,{children:["Remove the ",(0,t.jsx)(n.code,{children:""})," in ",(0,t.jsx)(n.code,{children:"app/app.tsx"})]}),"\n"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-diff",children:'-import { KeyboardProvider } from "react-native-keyboard-controller"\n\n// ...\n\nreturn (\n \n \n- \n \n- \n \n \n)\n'})}),"\n",(0,t.jsxs)(n.ol,{start:"3",children:["\n",(0,t.jsxs)(n.li,{children:["Update ",(0,t.jsx)(n.code,{children:"app/components/Screen.tsx"})]}),"\n"]}),"\n",(0,t.jsxs)(o,{children:[(0,t.jsx)("summary",{children:"Screen.tsx (expand to copy)"}),(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-tsx",metastring:'title="/app/components/Screen.tsx"',children:'import { useScrollToTop } from "@react-navigation/native";\nimport { StatusBar, StatusBarProps, StatusBarStyle } from "expo-status-bar";\nimport React, { useRef, useState } from "react";\nimport {\n KeyboardAvoidingView,\n KeyboardAvoidingViewProps,\n LayoutChangeEvent,\n Platform,\n ScrollView,\n ScrollViewProps,\n StyleProp,\n View,\n ViewStyle,\n} from "react-native";\nimport { $styles } from "@/theme";\nimport { ExtendedEdge, useSafeAreaInsetsStyle } from "@/utils/useSafeAreaInsetsStyle";\nimport { useAppTheme } from "@/utils/useAppTheme";\n\ninterface BaseScreenProps {\n /**\n * Children components.\n */\n children?: React.ReactNode;\n /**\n * Style for the outer content container useful for padding & margin.\n */\n style?: StyleProp;\n /**\n * Style for the inner content container useful for padding & margin.\n */\n contentContainerStyle?: StyleProp;\n /**\n * Override the default edges for the safe area.\n */\n safeAreaEdges?: ExtendedEdge[];\n /**\n * Background color\n */\n backgroundColor?: string;\n /**\n * Status bar setting. Defaults to dark.\n */\n statusBarStyle?: StatusBarStyle;\n /**\n * By how much should we offset the keyboard? Defaults to 0.\n */\n keyboardOffset?: number;\n /**\n * Pass any additional props directly to the StatusBar component.\n */\n StatusBarProps?: StatusBarProps;\n /**\n * Pass any additional props directly to the KeyboardAvoidingView component.\n */\n KeyboardAvoidingViewProps?: KeyboardAvoidingViewProps;\n}\n\ninterface FixedScreenProps extends BaseScreenProps {\n preset?: "fixed";\n}\ninterface ScrollScreenProps extends BaseScreenProps {\n preset?: "scroll";\n /**\n * Should keyboard persist on screen tap. Defaults to handled.\n * Only applies to scroll preset.\n */\n keyboardShouldPersistTaps?: "handled" | "always" | "never";\n /**\n * Pass any additional props directly to the ScrollView component.\n */\n ScrollViewProps?: ScrollViewProps;\n}\n\ninterface AutoScreenProps extends Omit {\n preset?: "auto";\n /**\n * Threshold to trigger the automatic disabling/enabling of scroll ability.\n * Defaults to `{ percent: 0.92 }`.\n */\n scrollEnabledToggleThreshold?: { percent?: number; point?: number };\n}\n\nexport type ScreenProps = ScrollScreenProps | FixedScreenProps | AutoScreenProps;\n\nconst isIos = Platform.OS === "ios";\n\ntype ScreenPreset = "fixed" | "scroll" | "auto";\n\n/**\n * @param {ScreenPreset?} preset - The preset to check.\n * @returns {boolean} - Whether the preset is non-scrolling.\n */\nfunction isNonScrolling(preset?: ScreenPreset) {\n return !preset || preset === "fixed";\n}\n\n/**\n * Custom hook that handles the automatic enabling/disabling of scroll ability based on the content size and screen size.\n * @param {UseAutoPresetProps} props - The props for the `useAutoPreset` hook.\n * @returns {{boolean, Function, Function}} - The scroll state, and the `onContentSizeChange` and `onLayout` functions.\n */\nfunction useAutoPreset(props: AutoScreenProps): {\n scrollEnabled: boolean;\n onContentSizeChange: (w: number, h: number) => void;\n onLayout: (e: LayoutChangeEvent) => void;\n} {\n const { preset, scrollEnabledToggleThreshold } = props;\n const { percent = 0.92, point = 0 } = scrollEnabledToggleThreshold || {};\n\n const scrollViewHeight = useRef(null);\n const scrollViewContentHeight = useRef(null);\n const [scrollEnabled, setScrollEnabled] = useState(true);\n\n function updateScrollState() {\n if (scrollViewHeight.current === null || scrollViewContentHeight.current === null) return;\n\n // check whether content fits the screen then toggle scroll state according to it\n const contentFitsScreen = (function () {\n if (point) {\n return scrollViewContentHeight.current < scrollViewHeight.current - point;\n } else {\n return scrollViewContentHeight.current < scrollViewHeight.current * percent;\n }\n })();\n\n // content is less than the size of the screen, so we can disable scrolling\n if (scrollEnabled && contentFitsScreen) setScrollEnabled(false);\n\n // content is greater than the size of the screen, so let\'s enable scrolling\n if (!scrollEnabled && !contentFitsScreen) setScrollEnabled(true);\n }\n\n /**\n * @param {number} w - The width of the content.\n * @param {number} h - The height of the content.\n */\n function onContentSizeChange(w: number, h: number) {\n // update scroll-view content height\n scrollViewContentHeight.current = h;\n updateScrollState();\n }\n\n /**\n * @param {LayoutChangeEvent} e = The layout change event.\n */\n function onLayout(e: LayoutChangeEvent) {\n const { height } = e.nativeEvent.layout;\n // update scroll-view height\n scrollViewHeight.current = height;\n updateScrollState();\n }\n\n // update scroll state on every render\n if (preset === "auto") updateScrollState();\n\n return {\n scrollEnabled: preset === "auto" ? scrollEnabled : true,\n onContentSizeChange,\n onLayout,\n };\n}\n\n/**\n * @param {ScreenProps} props - The props for the `ScreenWithoutScrolling` component.\n * @returns {JSX.Element} - The rendered `ScreenWithoutScrolling` component.\n */\nfunction ScreenWithoutScrolling(props: ScreenProps) {\n const { style, contentContainerStyle, children } = props;\n return (\n \n {children}\n \n );\n}\n\n/**\n * @param {ScreenProps} props - The props for the `ScreenWithScrolling` component.\n * @returns {JSX.Element} - The rendered `ScreenWithScrolling` component.\n */\nfunction ScreenWithScrolling(props: ScreenProps) {\n const {\n children,\n keyboardShouldPersistTaps = "handled",\n contentContainerStyle,\n ScrollViewProps,\n style,\n } = props as ScrollScreenProps;\n\n const ref = useRef(null);\n\n const { scrollEnabled, onContentSizeChange, onLayout } = useAutoPreset(props as AutoScreenProps);\n\n // Add native behavior of pressing the active tab to scroll to the top of the content\n // More info at: https://reactnavigation.org/docs/use-scroll-to-top/\n useScrollToTop(ref);\n\n return (\n {\n onLayout(e);\n ScrollViewProps?.onLayout?.(e);\n }}\n onContentSizeChange={(w: number, h: number) => {\n onContentSizeChange(w, h);\n ScrollViewProps?.onContentSizeChange?.(w, h);\n }}\n style={[$outerStyle, ScrollViewProps?.style, style]}\n contentContainerStyle={[\n $innerStyle,\n ScrollViewProps?.contentContainerStyle,\n contentContainerStyle,\n ]}\n >\n {children}\n \n );\n}\n\n/**\n * Represents a screen component that provides a consistent layout and behavior for different screen presets.\n * The `Screen` component can be used with different presets such as "fixed", "scroll", or "auto".\n * It handles safe area insets, status bar settings, keyboard avoiding behavior, and scrollability based on the preset.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Screen/}\n * @param {ScreenProps} props - The props for the `Screen` component.\n * @returns {JSX.Element} The rendered `Screen` component.\n */\nexport function Screen(props: ScreenProps) {\n const {\n theme: { colors },\n themeContext,\n } = useAppTheme();\n const {\n backgroundColor,\n KeyboardAvoidingViewProps,\n keyboardOffset = 0,\n safeAreaEdges,\n StatusBarProps,\n statusBarStyle,\n } = props;\n\n const $containerInsets = useSafeAreaInsetsStyle(safeAreaEdges);\n\n return (\n \n \n\n \n {isNonScrolling(props.preset) ? (\n \n ) : (\n \n )}\n \n \n );\n}\n\nconst $containerStyle: ViewStyle = {\n flex: 1,\n height: "100%",\n width: "100%",\n};\n\nconst $outerStyle: ViewStyle = {\n flex: 1,\n height: "100%",\n width: "100%",\n};\n\nconst $innerStyle: ViewStyle = {\n justifyContent: "flex-start",\n alignItems: "stretch",\n};\n'})})]}),"\n",(0,t.jsx)("br",{}),"\n",(0,t.jsx)(n.h4,{id:"sync-expo-packages-to-be-compatible-with-expo-go",children:"Sync Expo packages to be compatible with Expo Go"}),"\n",(0,t.jsxs)(n.p,{children:["Running ",(0,t.jsx)(n.code,{children:"npx expo install --check"})," will check all of the expo packages in their SDK against the version of ",(0,t.jsx)(n.code,{children:"expo"})," that is installed to ensure compatibility."]}),"\n",(0,t.jsxs)(n.p,{children:["You can accept these changes or run ",(0,t.jsx)(n.code,{children:"npx expo install --fix"})," to apply them directly without running the check."]}),"\n",(0,t.jsx)(n.h4,{id:"run-the-app",children:"Run the app!"}),"\n",(0,t.jsxs)(n.p,{children:["That's it! You should be able to run ",(0,t.jsx)(n.code,{children:"yarn start"})," and tap ",(0,t.jsx)(n.code,{children:"i"})," or ",(0,t.jsx)(n.code,{children:"a"})," in terminal to launch iOS or Android respectively in Expo Go."]})]})}function d(e={}){const{wrapper:n}={...(0,r.M)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(p,{...e})}):p(e)}},2172:(e,n,o)=>{o.d(n,{I:()=>a,M:()=>i});var t=o(1504);const r={},s=t.createContext(r);function i(e){const n=t.useContext(s);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/7acb6f50.049a03a2.js b/assets/js/7acb6f50.049a03a2.js
deleted file mode 100644
index db4dd129..00000000
--- a/assets/js/7acb6f50.049a03a2.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[56],{9200:e=>{e.exports=JSON.parse('{"label":"Yarn","permalink":"/docs/tags/yarn","allTagsPath":"/docs/tags","count":2,"items":[{"id":"recipes/MonoreposOverview","title":"Choosing the right monorepo strategy for your project","description":"This document outlines the advantages and challenges of adopting a monorepo strategy for software development projects, particularly in the context of React Native and the Ignite framework. It provides guidance on when to use or avoid a monorepo, explores common monorepo tools, and discusses typical setups to help teams select the most suitable approach for their needs.","permalink":"/docs/recipes/MonoreposOverview"},{"id":"recipes/UpdatingDependencies","title":"Updating Dependencies with Yarn Audit, Outdated and Upgrade","description":"If you get a bunch of warnings in the git command output about vulnerabilities, similar to this Github found 80 vulnerabilities on ..., you can examine these vulnerabilities with yarn audit, get a list of outdated packages with yarn outdated, and update each dependency using yarn update","permalink":"/docs/recipes/UpdatingDependencies"}],"unlisted":false}')}}]);
\ No newline at end of file
diff --git a/assets/js/7acb6f50.3e4a398a.js b/assets/js/7acb6f50.3e4a398a.js
new file mode 100644
index 00000000..270a81a3
--- /dev/null
+++ b/assets/js/7acb6f50.3e4a398a.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[56],{9200:e=>{e.exports=JSON.parse('{"label":"Yarn","permalink":"/docs/tags/yarn","allTagsPath":"/docs/tags","count":3,"items":[{"id":"recipes/MonoreposOverview","title":"Choosing the right monorepo strategy for your project","description":"This document outlines the advantages and challenges of adopting a monorepo strategy for software development projects, particularly in the context of React Native and the Ignite framework. It provides guidance on when to use or avoid a monorepo, explores common monorepo tools, and discusses typical setups to help teams select the most suitable approach for their needs.","permalink":"/docs/recipes/MonoreposOverview"},{"id":"recipes/SettingUpYarnMonorepo","title":"Setting up a Yarn monorepo with Ignite","description":"How to set up a Yarn monorepo using Ignite and two extra shared utilities","permalink":"/docs/recipes/SettingUpYarnMonorepo"},{"id":"recipes/UpdatingDependencies","title":"Updating Dependencies with Yarn Audit, Outdated and Upgrade","description":"If you get a bunch of warnings in the git command output about vulnerabilities, similar to this Github found 80 vulnerabilities on ..., you can examine these vulnerabilities with yarn audit, get a list of outdated packages with yarn outdated, and update each dependency using yarn update","permalink":"/docs/recipes/UpdatingDependencies"}],"unlisted":false}')}}]);
\ No newline at end of file
diff --git a/assets/js/935f2afb.494b2b1d.js b/assets/js/935f2afb.494b2b1d.js
deleted file mode 100644
index 2ebcf624..00000000
--- a/assets/js/935f2afb.494b2b1d.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[5696],{5988:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"mainSidebar":[{"type":"link","label":"Intro to Recipes","href":"/docs/intro","docId":"intro","unlisted":false},{"type":"link","label":"Browse By Tag","href":"/docs/tags"},{"type":"category","label":"Infinite Red Recipes","collapsed":true,"items":[{"type":"link","label":"Accessiblity Font Sizes","href":"/docs/recipes/AccessibilityFontSizes","docId":"recipes/AccessibilityFontSizes","unlisted":false},{"type":"link","label":"Extracting Apollo Client\'s Cache in Reactotron","href":"/docs/recipes/ApolloClientCache","docId":"recipes/ApolloClientCache","unlisted":false},{"type":"link","label":"Authentication with Supabase","href":"/docs/recipes/Authentication","docId":"recipes/Authentication","unlisted":false},{"type":"link","label":"CircleCI CD Setup - React Native","href":"/docs/recipes/CircleCIRNSetup","docId":"recipes/CircleCIRNSetup","unlisted":false},{"type":"link","label":"Creating a Good Experience for Screen Readers","href":"/docs/recipes/CreatingGreateExperienceForScreenReaders","docId":"recipes/CreatingGreateExperienceForScreenReaders","unlisted":false},{"type":"link","label":"Detox Intro","href":"/docs/recipes/DetoxIntro","docId":"recipes/DetoxIntro","unlisted":false},{"type":"link","label":"Distributing Auth Token to APIs","href":"/docs/recipes/DistributingAuthTokenToAPI","docId":"recipes/DistributingAuthTokenToAPI","unlisted":false},{"type":"link","label":"EAS Update","href":"/docs/recipes/EASUpdate","docId":"recipes/EASUpdate","unlisted":false},{"type":"link","label":"Enforcing JS/TS Import Order","href":"/docs/recipes/EnforcingImportOrder","docId":"recipes/EnforcingImportOrder","unlisted":false},{"type":"link","label":"Environment Variables","href":"/docs/recipes/EnvironmentVariables","docId":"recipes/EnvironmentVariables","unlisted":false},{"type":"link","label":"Expo Router","href":"/docs/recipes/ExpoRouter","docId":"recipes/ExpoRouter","unlisted":false},{"type":"link","label":"Generator for Component Tests","href":"/docs/recipes/GeneratorComponentTests","docId":"recipes/GeneratorComponentTests","unlisted":false},{"type":"link","label":"PowerSync and Supabase for Local-First Data Management","href":"/docs/recipes/LocalFirstDataWithPowerSync","docId":"recipes/LocalFirstDataWithPowerSync","unlisted":false},{"type":"link","label":"Maestro Setup","href":"/docs/recipes/MaestroSetup","docId":"recipes/MaestroSetup","unlisted":false},{"type":"link","label":"Migrating from i18n-js to react-i18next","href":"/docs/recipes/MigratingToI18Next","docId":"recipes/MigratingToI18Next","unlisted":false},{"type":"link","label":"Migrating to MMKV","href":"/docs/recipes/MigratingToMMKV","docId":"recipes/MigratingToMMKV","unlisted":false},{"type":"link","label":"Choosing the right monorepo strategy for your project","href":"/docs/recipes/MonoreposOverview","docId":"recipes/MonoreposOverview","unlisted":false},{"type":"link","label":"Patching/Building Android .aar From Source","href":"/docs/recipes/PatchingBuildingAndroid","docId":"recipes/PatchingBuildingAndroid","unlisted":false},{"type":"link","label":"Prepping Ignite for EAS Build","href":"/docs/recipes/PrepForEASBuild","docId":"recipes/PrepForEASBuild","unlisted":false},{"type":"link","label":"React Native Vision Camera","href":"/docs/recipes/ReactNativeVisionCamera","docId":"recipes/ReactNativeVisionCamera","unlisted":false},{"type":"link","label":"Redux","href":"/docs/recipes/Redux","docId":"recipes/Redux","unlisted":false},{"type":"link","label":"Remove MobX-State-Tree","href":"/docs/recipes/RemoveMobxStateTree","docId":"recipes/RemoveMobxStateTree","unlisted":false},{"type":"link","label":"Requiring Hardware Features with Expo","href":"/docs/recipes/RequiringHardwareFeaturesWithExpo","docId":"recipes/RequiringHardwareFeaturesWithExpo","unlisted":false},{"type":"link","label":"Sample YAML for CircleCi for Ignite","href":"/docs/recipes/SampleYAMLCircleCI","docId":"recipes/SampleYAMLCircleCI","unlisted":false},{"type":"link","label":"SelectField using `react-native-bottom-sheet`","href":"/docs/recipes/SelectFieldWithBottomSheet","docId":"recipes/SelectFieldWithBottomSheet","unlisted":false},{"type":"link","label":"Switch Between Expo Go and Expo CNG","href":"/docs/recipes/SwitchBetweenExpoGoCNG","docId":"recipes/SwitchBetweenExpoGoCNG","unlisted":false},{"type":"link","label":"Theming Ignite with Emotion.js","href":"/docs/recipes/Theming-Emotion","docId":"recipes/Theming-Emotion","unlisted":false},{"type":"link","label":"Theming Ignite with styled-components","href":"/docs/recipes/Theming-StyledComponents","docId":"recipes/Theming-StyledComponents","unlisted":false},{"type":"link","label":"Theming Ignite with Unistyles","href":"/docs/recipes/Theming-Unistyles","docId":"recipes/Theming-Unistyles","unlisted":false},{"type":"link","label":"TypeScript baseUrl Configuration","href":"/docs/recipes/TypeScriptBaseURL","docId":"recipes/TypeScriptBaseURL","unlisted":false},{"type":"link","label":"Scrolling to a location that hasn\'t been rendered using FlatList or SectionList","href":"/docs/recipes/UnrenderedItemInScrollView","docId":"recipes/UnrenderedItemInScrollView","unlisted":false},{"type":"link","label":"Updating Dependencies with Yarn Audit, Outdated and Upgrade","href":"/docs/recipes/UpdatingDependencies","docId":"recipes/UpdatingDependencies","unlisted":false},{"type":"link","label":"Updating Ignite boilerplate with ignite-diff-purge","href":"/docs/recipes/UpdatingIgnite","docId":"recipes/UpdatingIgnite","unlisted":false},{"type":"link","label":"Using Screen Readers","href":"/docs/recipes/UsingScreenReaders","docId":"recipes/UsingScreenReaders","unlisted":false},{"type":"link","label":"Zustand","href":"/docs/recipes/Zustand","docId":"recipes/Zustand","unlisted":false}],"collapsible":true},{"type":"category","label":"Community Recipes","collapsed":true,"items":[{"type":"link","label":"Overview","href":"/docs/communityRecipes/","docId":"communityRecipes/index","unlisted":false},{"type":"link","label":"Using Custom Vector Icons","href":"/docs/communityRecipes/CustomVectorIcons","docId":"communityRecipes/CustomVectorIcons","unlisted":false}],"collapsible":true},{"type":"category","label":"Archive","collapsed":true,"items":[{"type":"link","label":"Overview","href":"/docs/archive/","docId":"archive/index","unlisted":false},{"type":"link","label":"Pristine Expo Project","href":"/docs/archive/PristineExpoProject","docId":"archive/PristineExpoProject","unlisted":false}],"collapsible":true}]},"docs":{"archive/index":{"id":"archive/index","title":"Overview","description":"These recipes apply to older versions of Ignite for those with existing projects who may not be in sync with the latest dependencies.","sidebar":"mainSidebar"},"archive/PristineExpoProject":{"id":"archive/PristineExpoProject","title":"Pristine Expo Project","description":"How to remove native related code from a unified Ignite project","sidebar":"mainSidebar"},"communityRecipes/CustomVectorIcons":{"id":"communityRecipes/CustomVectorIcons","title":"Using Custom Vector Icons","description":"How to use your own vector icons using @expo/vector-icons","sidebar":"mainSidebar"},"communityRecipes/index":{"id":"communityRecipes/index","title":"Overview","description":"These recipes in this section are contributed by our amazing community! If you have a recipe you\'d like to share, please submit a PR to the Ignite Cookbook Github Repo.","sidebar":"mainSidebar"},"intro":{"id":"intro","title":"Intro to Recipes","description":"Welcome to the Ignite Cookbook! This is a collection of recipes for common patterns in Ignite projects.","sidebar":"mainSidebar"},"recipes/AccessibilityFontSizes":{"id":"recipes/AccessibilityFontSizes","title":"Accessiblity Font Sizes","description":"Dealing With Accessibility Font Sizes in React Native","sidebar":"mainSidebar"},"recipes/ApolloClientCache":{"id":"recipes/ApolloClientCache","title":"Extracting Apollo Client\'s Cache in Reactotron","description":"How to enhance your Ignite debugging experience when using the Apollo client with Custom Commands in Reactotron","sidebar":"mainSidebar"},"recipes/Authentication":{"id":"recipes/Authentication","title":"Authentication with Supabase","description":"How to implement authentication with your React Native project using Supabase as the backend.","sidebar":"mainSidebar"},"recipes/CircleCIRNSetup":{"id":"recipes/CircleCIRNSetup","title":"CircleCI CD Setup - React Native","description":"Learn how to set up your CircleCI CD instance for React Native","sidebar":"mainSidebar"},"recipes/CreatingGreateExperienceForScreenReaders":{"id":"recipes/CreatingGreateExperienceForScreenReaders","title":"Creating a Good Experience for Screen Readers","description":"Learn how to improve the experience of screen readers using your app!","sidebar":"mainSidebar"},"recipes/DetoxIntro":{"id":"recipes/DetoxIntro","title":"Detox Intro","description":"A quick look at Detox and what makes it useful","sidebar":"mainSidebar"},"recipes/DistributingAuthTokenToAPI":{"id":"recipes/DistributingAuthTokenToAPI","title":"Distributing Auth Token to APIs","description":"Use token stored in Authentication Store with API Sauce","sidebar":"mainSidebar"},"recipes/EASUpdate":{"id":"recipes/EASUpdate","title":"EAS Update","description":"Setting up Ignite to deploy over-the-air (OTA) updates via EAS","sidebar":"mainSidebar"},"recipes/EnforcingImportOrder":{"id":"recipes/EnforcingImportOrder","title":"Enforcing JS/TS Import Order","description":"Ensuring that file imports are ordered in a consistent manner","sidebar":"mainSidebar"},"recipes/EnvironmentVariables":{"id":"recipes/EnvironmentVariables","title":"Environment Variables","description":"A universal way to set up environment variables for bare and Expo projects","sidebar":"mainSidebar"},"recipes/ExpoRouter":{"id":"recipes/ExpoRouter","title":"Expo Router","description":"How to convert Ignite v9 demo app to utilize `expo-router`","sidebar":"mainSidebar"},"recipes/GeneratorComponentTests":{"id":"recipes/GeneratorComponentTests","title":"Generator for Component Tests","description":"Customize `npx ignite-cli generate component` to add test files for each component generated","sidebar":"mainSidebar"},"recipes/LocalFirstDataWithPowerSync":{"id":"recipes/LocalFirstDataWithPowerSync","title":"PowerSync and Supabase for Local-First Data Management","description":"Enhance your app with PowerSync and Supabase for efficient data synchronization between your app\'s local database and backend","sidebar":"mainSidebar"},"recipes/MaestroSetup":{"id":"recipes/MaestroSetup","title":"Maestro Setup","description":"Setting up e2e testing with Maestro in Ignite","sidebar":"mainSidebar"},"recipes/MigratingToI18Next":{"id":"recipes/MigratingToI18Next","title":"Migrating from i18n-js to react-i18next","description":"How to migrate from i18n-js to react-i18next","sidebar":"mainSidebar"},"recipes/MigratingToMMKV":{"id":"recipes/MigratingToMMKV","title":"Migrating to MMKV","description":"How to migrate from React Native\'s AsyncStorage to MMKV","sidebar":"mainSidebar"},"recipes/MonoreposOverview":{"id":"recipes/MonoreposOverview","title":"Choosing the right monorepo strategy for your project","description":"This document outlines the advantages and challenges of adopting a monorepo strategy for software development projects, particularly in the context of React Native and the Ignite framework. It provides guidance on when to use or avoid a monorepo, explores common monorepo tools, and discusses typical setups to help teams select the most suitable approach for their needs.","sidebar":"mainSidebar"},"recipes/PatchingBuildingAndroid":{"id":"recipes/PatchingBuildingAndroid","title":"Patching/Building Android .aar From Source","description":"Instructions for updating the RN Android source code","sidebar":"mainSidebar"},"recipes/PrepForEASBuild":{"id":"recipes/PrepForEASBuild","title":"Prepping Ignite for EAS Build","description":"Setting up Ignite to build a custom Expo development client for use with Config Plugins","sidebar":"mainSidebar"},"recipes/ReactNativeVisionCamera":{"id":"recipes/ReactNativeVisionCamera","title":"React Native Vision Camera","description":"How to integrate VisionCamera in Ignite v9+","sidebar":"mainSidebar"},"recipes/Redux":{"id":"recipes/Redux","title":"Redux","description":"How to migrate a MobX-State-Tree project to Redux","sidebar":"mainSidebar"},"recipes/RemoveMobxStateTree":{"id":"recipes/RemoveMobxStateTree","title":"Remove MobX-State-Tree","description":"How to remove MobX-State-Tree from an Ignite project","sidebar":"mainSidebar"},"recipes/RequiringHardwareFeaturesWithExpo":{"id":"recipes/RequiringHardwareFeaturesWithExpo","title":"Requiring Hardware Features with Expo","description":"How to specify hardware requirements for your app","sidebar":"mainSidebar"},"recipes/SampleYAMLCircleCI":{"id":"recipes/SampleYAMLCircleCI","title":"Sample YAML for CircleCi for Ignite","description":"A Copy/Paste Sample YAML for your Ignite Project","sidebar":"mainSidebar"},"recipes/SelectFieldWithBottomSheet":{"id":"recipes/SelectFieldWithBottomSheet","title":"SelectField using `react-native-bottom-sheet`","description":"Extending Ignite\'s TextField to be used as a SelectField with react-native-bottom-sheet","sidebar":"mainSidebar"},"recipes/SwitchBetweenExpoGoCNG":{"id":"recipes/SwitchBetweenExpoGoCNG","title":"Switch Between Expo Go and Expo CNG","description":"Switch an Expo Go project to an Expo CNG project and visa versa","sidebar":"mainSidebar"},"recipes/Theming-Emotion":{"id":"recipes/Theming-Emotion","title":"Theming Ignite with Emotion.js","description":"Learn how to use different styling libraries to theme your Ignited app!","sidebar":"mainSidebar"},"recipes/Theming-StyledComponents":{"id":"recipes/Theming-StyledComponents","title":"Theming Ignite with styled-components","description":"Learn how to use different styling libraries to theme your Ignited app!","sidebar":"mainSidebar"},"recipes/Theming-Unistyles":{"id":"recipes/Theming-Unistyles","title":"Theming Ignite with Unistyles","description":"Learn how to use different styling libraries to theme your Ignited app!","sidebar":"mainSidebar"},"recipes/TypeScriptBaseURL":{"id":"recipes/TypeScriptBaseURL","title":"TypeScript baseUrl Configuration","description":"How to configure TypeScript\'s baseUrl module for rewriting relative imports","sidebar":"mainSidebar"},"recipes/UnrenderedItemInScrollView":{"id":"recipes/UnrenderedItemInScrollView","title":"Scrolling to a location that hasn\'t been rendered using FlatList or SectionList","description":"This article explains how to scroll to a location of a FlatList or SectionList that hasn\'t rendered yet","sidebar":"mainSidebar"},"recipes/UpdatingDependencies":{"id":"recipes/UpdatingDependencies","title":"Updating Dependencies with Yarn Audit, Outdated and Upgrade","description":"If you get a bunch of warnings in the git command output about vulnerabilities, similar to this Github found 80 vulnerabilities on ..., you can examine these vulnerabilities with yarn audit, get a list of outdated packages with yarn outdated, and update each dependency using yarn update","sidebar":"mainSidebar"},"recipes/UpdatingIgnite":{"id":"recipes/UpdatingIgnite","title":"Updating Ignite boilerplate with ignite-diff-purge","description":"Many React Native developers aks this question:","sidebar":"mainSidebar"},"recipes/UsingScreenReaders":{"id":"recipes/UsingScreenReaders","title":"Using Screen Readers","description":"Learn how to use a screen reader to improve accesibility!","sidebar":"mainSidebar"},"recipes/Zustand":{"id":"recipes/Zustand","title":"Zustand","description":"How to migrate a Mobx-State-Tree project to Zustand","sidebar":"mainSidebar"}}}')}}]);
\ No newline at end of file
diff --git a/assets/js/935f2afb.64145e38.js b/assets/js/935f2afb.64145e38.js
new file mode 100644
index 00000000..40fefe68
--- /dev/null
+++ b/assets/js/935f2afb.64145e38.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[5696],{5988:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"mainSidebar":[{"type":"link","label":"Intro to Recipes","href":"/docs/intro","docId":"intro","unlisted":false},{"type":"link","label":"Browse By Tag","href":"/docs/tags"},{"type":"category","label":"Infinite Red Recipes","collapsed":true,"items":[{"type":"link","label":"Accessiblity Font Sizes","href":"/docs/recipes/AccessibilityFontSizes","docId":"recipes/AccessibilityFontSizes","unlisted":false},{"type":"link","label":"Extracting Apollo Client\'s Cache in Reactotron","href":"/docs/recipes/ApolloClientCache","docId":"recipes/ApolloClientCache","unlisted":false},{"type":"link","label":"Authentication with Supabase","href":"/docs/recipes/Authentication","docId":"recipes/Authentication","unlisted":false},{"type":"link","label":"CircleCI CD Setup - React Native","href":"/docs/recipes/CircleCIRNSetup","docId":"recipes/CircleCIRNSetup","unlisted":false},{"type":"link","label":"Creating a Good Experience for Screen Readers","href":"/docs/recipes/CreatingGreateExperienceForScreenReaders","docId":"recipes/CreatingGreateExperienceForScreenReaders","unlisted":false},{"type":"link","label":"Detox Intro","href":"/docs/recipes/DetoxIntro","docId":"recipes/DetoxIntro","unlisted":false},{"type":"link","label":"Distributing Auth Token to APIs","href":"/docs/recipes/DistributingAuthTokenToAPI","docId":"recipes/DistributingAuthTokenToAPI","unlisted":false},{"type":"link","label":"EAS Update","href":"/docs/recipes/EASUpdate","docId":"recipes/EASUpdate","unlisted":false},{"type":"link","label":"Enforcing JS/TS Import Order","href":"/docs/recipes/EnforcingImportOrder","docId":"recipes/EnforcingImportOrder","unlisted":false},{"type":"link","label":"Environment Variables","href":"/docs/recipes/EnvironmentVariables","docId":"recipes/EnvironmentVariables","unlisted":false},{"type":"link","label":"Expo Router","href":"/docs/recipes/ExpoRouter","docId":"recipes/ExpoRouter","unlisted":false},{"type":"link","label":"Generator for Component Tests","href":"/docs/recipes/GeneratorComponentTests","docId":"recipes/GeneratorComponentTests","unlisted":false},{"type":"link","label":"PowerSync and Supabase for Local-First Data Management","href":"/docs/recipes/LocalFirstDataWithPowerSync","docId":"recipes/LocalFirstDataWithPowerSync","unlisted":false},{"type":"link","label":"Maestro Setup","href":"/docs/recipes/MaestroSetup","docId":"recipes/MaestroSetup","unlisted":false},{"type":"link","label":"Migrating from i18n-js to react-i18next","href":"/docs/recipes/MigratingToI18Next","docId":"recipes/MigratingToI18Next","unlisted":false},{"type":"link","label":"Migrating to MMKV","href":"/docs/recipes/MigratingToMMKV","docId":"recipes/MigratingToMMKV","unlisted":false},{"type":"link","label":"Choosing the right monorepo strategy for your project","href":"/docs/recipes/MonoreposOverview","docId":"recipes/MonoreposOverview","unlisted":false},{"type":"link","label":"Patching/Building Android .aar From Source","href":"/docs/recipes/PatchingBuildingAndroid","docId":"recipes/PatchingBuildingAndroid","unlisted":false},{"type":"link","label":"Prepping Ignite for EAS Build","href":"/docs/recipes/PrepForEASBuild","docId":"recipes/PrepForEASBuild","unlisted":false},{"type":"link","label":"React Native Vision Camera","href":"/docs/recipes/ReactNativeVisionCamera","docId":"recipes/ReactNativeVisionCamera","unlisted":false},{"type":"link","label":"Redux","href":"/docs/recipes/Redux","docId":"recipes/Redux","unlisted":false},{"type":"link","label":"Remove MobX-State-Tree","href":"/docs/recipes/RemoveMobxStateTree","docId":"recipes/RemoveMobxStateTree","unlisted":false},{"type":"link","label":"Requiring Hardware Features with Expo","href":"/docs/recipes/RequiringHardwareFeaturesWithExpo","docId":"recipes/RequiringHardwareFeaturesWithExpo","unlisted":false},{"type":"link","label":"Sample YAML for CircleCi for Ignite","href":"/docs/recipes/SampleYAMLCircleCI","docId":"recipes/SampleYAMLCircleCI","unlisted":false},{"type":"link","label":"SelectField using `react-native-bottom-sheet`","href":"/docs/recipes/SelectFieldWithBottomSheet","docId":"recipes/SelectFieldWithBottomSheet","unlisted":false},{"type":"link","label":"Setting up a Yarn monorepo with Ignite","href":"/docs/recipes/SettingUpYarnMonorepo","docId":"recipes/SettingUpYarnMonorepo","unlisted":false},{"type":"link","label":"Switch Between Expo Go and Expo CNG","href":"/docs/recipes/SwitchBetweenExpoGoCNG","docId":"recipes/SwitchBetweenExpoGoCNG","unlisted":false},{"type":"link","label":"Theming Ignite with Emotion.js","href":"/docs/recipes/Theming-Emotion","docId":"recipes/Theming-Emotion","unlisted":false},{"type":"link","label":"Theming Ignite with styled-components","href":"/docs/recipes/Theming-StyledComponents","docId":"recipes/Theming-StyledComponents","unlisted":false},{"type":"link","label":"Theming Ignite with Unistyles","href":"/docs/recipes/Theming-Unistyles","docId":"recipes/Theming-Unistyles","unlisted":false},{"type":"link","label":"TypeScript baseUrl Configuration","href":"/docs/recipes/TypeScriptBaseURL","docId":"recipes/TypeScriptBaseURL","unlisted":false},{"type":"link","label":"Scrolling to a location that hasn\'t been rendered using FlatList or SectionList","href":"/docs/recipes/UnrenderedItemInScrollView","docId":"recipes/UnrenderedItemInScrollView","unlisted":false},{"type":"link","label":"Updating Dependencies with Yarn Audit, Outdated and Upgrade","href":"/docs/recipes/UpdatingDependencies","docId":"recipes/UpdatingDependencies","unlisted":false},{"type":"link","label":"Updating Ignite boilerplate with ignite-diff-purge","href":"/docs/recipes/UpdatingIgnite","docId":"recipes/UpdatingIgnite","unlisted":false},{"type":"link","label":"Using Screen Readers","href":"/docs/recipes/UsingScreenReaders","docId":"recipes/UsingScreenReaders","unlisted":false},{"type":"link","label":"Zustand","href":"/docs/recipes/Zustand","docId":"recipes/Zustand","unlisted":false}],"collapsible":true},{"type":"category","label":"Community Recipes","collapsed":true,"items":[{"type":"link","label":"Overview","href":"/docs/communityRecipes/","docId":"communityRecipes/index","unlisted":false},{"type":"link","label":"Using Custom Vector Icons","href":"/docs/communityRecipes/CustomVectorIcons","docId":"communityRecipes/CustomVectorIcons","unlisted":false}],"collapsible":true},{"type":"category","label":"Archive","collapsed":true,"items":[{"type":"link","label":"Overview","href":"/docs/archive/","docId":"archive/index","unlisted":false},{"type":"link","label":"Pristine Expo Project","href":"/docs/archive/PristineExpoProject","docId":"archive/PristineExpoProject","unlisted":false}],"collapsible":true}]},"docs":{"archive/index":{"id":"archive/index","title":"Overview","description":"These recipes apply to older versions of Ignite for those with existing projects who may not be in sync with the latest dependencies.","sidebar":"mainSidebar"},"archive/PristineExpoProject":{"id":"archive/PristineExpoProject","title":"Pristine Expo Project","description":"How to remove native related code from a unified Ignite project","sidebar":"mainSidebar"},"communityRecipes/CustomVectorIcons":{"id":"communityRecipes/CustomVectorIcons","title":"Using Custom Vector Icons","description":"How to use your own vector icons using @expo/vector-icons","sidebar":"mainSidebar"},"communityRecipes/index":{"id":"communityRecipes/index","title":"Overview","description":"These recipes in this section are contributed by our amazing community! If you have a recipe you\'d like to share, please submit a PR to the Ignite Cookbook Github Repo.","sidebar":"mainSidebar"},"intro":{"id":"intro","title":"Intro to Recipes","description":"Welcome to the Ignite Cookbook! This is a collection of recipes for common patterns in Ignite projects.","sidebar":"mainSidebar"},"recipes/AccessibilityFontSizes":{"id":"recipes/AccessibilityFontSizes","title":"Accessiblity Font Sizes","description":"Dealing With Accessibility Font Sizes in React Native","sidebar":"mainSidebar"},"recipes/ApolloClientCache":{"id":"recipes/ApolloClientCache","title":"Extracting Apollo Client\'s Cache in Reactotron","description":"How to enhance your Ignite debugging experience when using the Apollo client with Custom Commands in Reactotron","sidebar":"mainSidebar"},"recipes/Authentication":{"id":"recipes/Authentication","title":"Authentication with Supabase","description":"How to implement authentication with your React Native project using Supabase as the backend.","sidebar":"mainSidebar"},"recipes/CircleCIRNSetup":{"id":"recipes/CircleCIRNSetup","title":"CircleCI CD Setup - React Native","description":"Learn how to set up your CircleCI CD instance for React Native","sidebar":"mainSidebar"},"recipes/CreatingGreateExperienceForScreenReaders":{"id":"recipes/CreatingGreateExperienceForScreenReaders","title":"Creating a Good Experience for Screen Readers","description":"Learn how to improve the experience of screen readers using your app!","sidebar":"mainSidebar"},"recipes/DetoxIntro":{"id":"recipes/DetoxIntro","title":"Detox Intro","description":"A quick look at Detox and what makes it useful","sidebar":"mainSidebar"},"recipes/DistributingAuthTokenToAPI":{"id":"recipes/DistributingAuthTokenToAPI","title":"Distributing Auth Token to APIs","description":"Use token stored in Authentication Store with API Sauce","sidebar":"mainSidebar"},"recipes/EASUpdate":{"id":"recipes/EASUpdate","title":"EAS Update","description":"Setting up Ignite to deploy over-the-air (OTA) updates via EAS","sidebar":"mainSidebar"},"recipes/EnforcingImportOrder":{"id":"recipes/EnforcingImportOrder","title":"Enforcing JS/TS Import Order","description":"Ensuring that file imports are ordered in a consistent manner","sidebar":"mainSidebar"},"recipes/EnvironmentVariables":{"id":"recipes/EnvironmentVariables","title":"Environment Variables","description":"A universal way to set up environment variables for bare and Expo projects","sidebar":"mainSidebar"},"recipes/ExpoRouter":{"id":"recipes/ExpoRouter","title":"Expo Router","description":"How to convert Ignite v9 demo app to utilize `expo-router`","sidebar":"mainSidebar"},"recipes/GeneratorComponentTests":{"id":"recipes/GeneratorComponentTests","title":"Generator for Component Tests","description":"Customize `npx ignite-cli generate component` to add test files for each component generated","sidebar":"mainSidebar"},"recipes/LocalFirstDataWithPowerSync":{"id":"recipes/LocalFirstDataWithPowerSync","title":"PowerSync and Supabase for Local-First Data Management","description":"Enhance your app with PowerSync and Supabase for efficient data synchronization between your app\'s local database and backend","sidebar":"mainSidebar"},"recipes/MaestroSetup":{"id":"recipes/MaestroSetup","title":"Maestro Setup","description":"Setting up e2e testing with Maestro in Ignite","sidebar":"mainSidebar"},"recipes/MigratingToI18Next":{"id":"recipes/MigratingToI18Next","title":"Migrating from i18n-js to react-i18next","description":"How to migrate from i18n-js to react-i18next","sidebar":"mainSidebar"},"recipes/MigratingToMMKV":{"id":"recipes/MigratingToMMKV","title":"Migrating to MMKV","description":"How to migrate from React Native\'s AsyncStorage to MMKV","sidebar":"mainSidebar"},"recipes/MonoreposOverview":{"id":"recipes/MonoreposOverview","title":"Choosing the right monorepo strategy for your project","description":"This document outlines the advantages and challenges of adopting a monorepo strategy for software development projects, particularly in the context of React Native and the Ignite framework. It provides guidance on when to use or avoid a monorepo, explores common monorepo tools, and discusses typical setups to help teams select the most suitable approach for their needs.","sidebar":"mainSidebar"},"recipes/PatchingBuildingAndroid":{"id":"recipes/PatchingBuildingAndroid","title":"Patching/Building Android .aar From Source","description":"Instructions for updating the RN Android source code","sidebar":"mainSidebar"},"recipes/PrepForEASBuild":{"id":"recipes/PrepForEASBuild","title":"Prepping Ignite for EAS Build","description":"Setting up Ignite to build a custom Expo development client for use with Config Plugins","sidebar":"mainSidebar"},"recipes/ReactNativeVisionCamera":{"id":"recipes/ReactNativeVisionCamera","title":"React Native Vision Camera","description":"How to integrate VisionCamera in Ignite v9+","sidebar":"mainSidebar"},"recipes/Redux":{"id":"recipes/Redux","title":"Redux","description":"How to migrate a MobX-State-Tree project to Redux","sidebar":"mainSidebar"},"recipes/RemoveMobxStateTree":{"id":"recipes/RemoveMobxStateTree","title":"Remove MobX-State-Tree","description":"How to remove MobX-State-Tree from an Ignite project","sidebar":"mainSidebar"},"recipes/RequiringHardwareFeaturesWithExpo":{"id":"recipes/RequiringHardwareFeaturesWithExpo","title":"Requiring Hardware Features with Expo","description":"How to specify hardware requirements for your app","sidebar":"mainSidebar"},"recipes/SampleYAMLCircleCI":{"id":"recipes/SampleYAMLCircleCI","title":"Sample YAML for CircleCi for Ignite","description":"A Copy/Paste Sample YAML for your Ignite Project","sidebar":"mainSidebar"},"recipes/SelectFieldWithBottomSheet":{"id":"recipes/SelectFieldWithBottomSheet","title":"SelectField using `react-native-bottom-sheet`","description":"Extending Ignite\'s TextField to be used as a SelectField with react-native-bottom-sheet","sidebar":"mainSidebar"},"recipes/SettingUpYarnMonorepo":{"id":"recipes/SettingUpYarnMonorepo","title":"Setting up a Yarn monorepo with Ignite","description":"How to set up a Yarn monorepo using Ignite and two extra shared utilities","sidebar":"mainSidebar"},"recipes/SwitchBetweenExpoGoCNG":{"id":"recipes/SwitchBetweenExpoGoCNG","title":"Switch Between Expo Go and Expo CNG","description":"Switch an Expo Go project to an Expo CNG project and visa versa","sidebar":"mainSidebar"},"recipes/Theming-Emotion":{"id":"recipes/Theming-Emotion","title":"Theming Ignite with Emotion.js","description":"Learn how to use different styling libraries to theme your Ignited app!","sidebar":"mainSidebar"},"recipes/Theming-StyledComponents":{"id":"recipes/Theming-StyledComponents","title":"Theming Ignite with styled-components","description":"Learn how to use different styling libraries to theme your Ignited app!","sidebar":"mainSidebar"},"recipes/Theming-Unistyles":{"id":"recipes/Theming-Unistyles","title":"Theming Ignite with Unistyles","description":"Learn how to use different styling libraries to theme your Ignited app!","sidebar":"mainSidebar"},"recipes/TypeScriptBaseURL":{"id":"recipes/TypeScriptBaseURL","title":"TypeScript baseUrl Configuration","description":"How to configure TypeScript\'s baseUrl module for rewriting relative imports","sidebar":"mainSidebar"},"recipes/UnrenderedItemInScrollView":{"id":"recipes/UnrenderedItemInScrollView","title":"Scrolling to a location that hasn\'t been rendered using FlatList or SectionList","description":"This article explains how to scroll to a location of a FlatList or SectionList that hasn\'t rendered yet","sidebar":"mainSidebar"},"recipes/UpdatingDependencies":{"id":"recipes/UpdatingDependencies","title":"Updating Dependencies with Yarn Audit, Outdated and Upgrade","description":"If you get a bunch of warnings in the git command output about vulnerabilities, similar to this Github found 80 vulnerabilities on ..., you can examine these vulnerabilities with yarn audit, get a list of outdated packages with yarn outdated, and update each dependency using yarn update","sidebar":"mainSidebar"},"recipes/UpdatingIgnite":{"id":"recipes/UpdatingIgnite","title":"Updating Ignite boilerplate with ignite-diff-purge","description":"Many React Native developers aks this question:","sidebar":"mainSidebar"},"recipes/UsingScreenReaders":{"id":"recipes/UsingScreenReaders","title":"Using Screen Readers","description":"Learn how to use a screen reader to improve accesibility!","sidebar":"mainSidebar"},"recipes/Zustand":{"id":"recipes/Zustand","title":"Zustand","description":"How to migrate a Mobx-State-Tree project to Zustand","sidebar":"mainSidebar"}}}')}}]);
\ No newline at end of file
diff --git a/assets/js/b16fadc1.c927db5c.js b/assets/js/b16fadc1.214b74e4.js
similarity index 62%
rename from assets/js/b16fadc1.c927db5c.js
rename to assets/js/b16fadc1.214b74e4.js
index c05e4600..0737ffd3 100644
--- a/assets/js/b16fadc1.c927db5c.js
+++ b/assets/js/b16fadc1.214b74e4.js
@@ -1 +1 @@
-"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[1176],{9390:e=>{e.exports=JSON.parse('{"label":"Monorepo","permalink":"/docs/tags/monorepo","allTagsPath":"/docs/tags","count":1,"items":[{"id":"recipes/MonoreposOverview","title":"Choosing the right monorepo strategy for your project","description":"This document outlines the advantages and challenges of adopting a monorepo strategy for software development projects, particularly in the context of React Native and the Ignite framework. It provides guidance on when to use or avoid a monorepo, explores common monorepo tools, and discusses typical setups to help teams select the most suitable approach for their needs.","permalink":"/docs/recipes/MonoreposOverview"}],"unlisted":false}')}}]);
\ No newline at end of file
+"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[1176],{9390:e=>{e.exports=JSON.parse('{"label":"Monorepo","permalink":"/docs/tags/monorepo","allTagsPath":"/docs/tags","count":2,"items":[{"id":"recipes/MonoreposOverview","title":"Choosing the right monorepo strategy for your project","description":"This document outlines the advantages and challenges of adopting a monorepo strategy for software development projects, particularly in the context of React Native and the Ignite framework. It provides guidance on when to use or avoid a monorepo, explores common monorepo tools, and discusses typical setups to help teams select the most suitable approach for their needs.","permalink":"/docs/recipes/MonoreposOverview"},{"id":"recipes/SettingUpYarnMonorepo","title":"Setting up a Yarn monorepo with Ignite","description":"How to set up a Yarn monorepo using Ignite and two extra shared utilities","permalink":"/docs/recipes/SettingUpYarnMonorepo"}],"unlisted":false}')}}]);
\ No newline at end of file
diff --git a/assets/js/d63d2b89.5ce11e23.js b/assets/js/d63d2b89.5ce11e23.js
new file mode 100644
index 00000000..476c40f7
--- /dev/null
+++ b/assets/js/d63d2b89.5ce11e23.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[5276],{6960:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>a,default:()=>h,frontMatter:()=>l,metadata:()=>r,toc:()=>d});var s=t(7624),o=t(2172),i=t(2180);const l={title:"SelectField using `react-native-bottom-sheet`",description:"Extending Ignite's TextField to be used as a SelectField with react-native-bottom-sheet",tags:["TextField","SelectField","UI"],last_update:{author:"Yulian Glukhenko"},publish_date:new Date("2023-02-15T00:00:00.000Z")},a="SelectField using react-native-bottom-sheet",r={id:"recipes/SelectFieldWithBottomSheet",title:"SelectField using `react-native-bottom-sheet`",description:"Extending Ignite's TextField to be used as a SelectField with react-native-bottom-sheet",source:"@site/docs/recipes/SelectFieldWithBottomSheet.mdx",sourceDirName:"recipes",slug:"/recipes/SelectFieldWithBottomSheet",permalink:"/docs/recipes/SelectFieldWithBottomSheet",draft:!1,unlisted:!1,tags:[{label:"TextField",permalink:"/docs/tags/text-field"},{label:"SelectField",permalink:"/docs/tags/select-field"},{label:"UI",permalink:"/docs/tags/ui"}],version:"current",lastUpdatedBy:"Yulian Glukhenko",lastUpdatedAt:1728940271,formattedLastUpdatedAt:"Oct 14, 2024",frontMatter:{title:"SelectField using `react-native-bottom-sheet`",description:"Extending Ignite's TextField to be used as a SelectField with react-native-bottom-sheet",tags:["TextField","SelectField","UI"],last_update:{author:"Yulian Glukhenko"},publish_date:"2023-02-15T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Sample YAML for CircleCi for Ignite",permalink:"/docs/recipes/SampleYAMLCircleCI"},next:{title:"Setting up a Yarn monorepo with Ignite",permalink:"/docs/recipes/SettingUpYarnMonorepo"}},c={},d=[{value:"1. Installation",id:"1-installation",level:2},{value:"2. Create the SelectField.tsx
Component File",id:"2-create-the-selectfieldtsx-component-file",level:2},{value:"3. Add New Props and Customize the TextField",id:"3-add-new-props-and-customize-the-textfield",level:2},{value:"Add a Caret Icon Accessory",id:"add-a-caret-icon-accessory",level:3},{value:"Add Props",id:"add-props",level:3},{value:"Add Logic to Display Selected Options",id:"add-logic-to-display-selected-options",level:3},{value:"Full Code For This Step",id:"full-code-for-this-step",level:3},{value:"4. Add the Sheet Components",id:"4-add-the-sheet-components",level:2},{value:"Add the BottomSheetModalProvider
",id:"add-the-bottomsheetmodalprovider",level:3},{value:"Add the Necessary Components to SelectField
",id:"add-the-necessary-components-to-selectfield",level:3},{value:"5. Add Selected State to Options and Hook Up Callback",id:"5-add-selected-state-to-options-and-hook-up-callback",level:2}];function p(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",img:"img",p:"p",pre:"pre",...(0,o.M)(),...e.components},{Details:t}=n;return t||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(n.h1,{id:"selectfield-using-react-native-bottom-sheet",children:["SelectField using ",(0,s.jsx)(n.code,{children:"react-native-bottom-sheet"})]}),"\n",(0,s.jsxs)(n.p,{children:["In this guide, we'll be creating a ",(0,s.jsx)(n.code,{children:"SelectField"})," component by extending the ",(0,s.jsx)(n.code,{children:"TextField"})," with a scrollable options View and additional props to handle its customization."]}),"\n",(0,s.jsx)(i.c,{width:"100%",controls:!0,url:"https://user-images.githubusercontent.com/1775841/219038677-bcc9c61d-1776-4aad-bb50-1e932721bc04.mp4"}),"\n",(0,s.jsxs)(n.p,{children:["We will be using the ",(0,s.jsx)(n.a,{href:"https://gorhom.github.io/react-native-bottom-sheet/",children:(0,s.jsx)(n.code,{children:"react-native-bottom-sheet"})})," library for the options list, the ",(0,s.jsx)(n.code,{children:"ListItem"})," component for displaying individual options, and the ",(0,s.jsx)(n.code,{children:"TextField"})," component for opening the options list and displaying selected options."]}),"\n",(0,s.jsxs)(n.p,{children:["There are many ways you can setup ",(0,s.jsx)(n.code,{children:"react-native-bottom-sheet"})," to function as a ",(0,s.jsx)(n.code,{children:"Picker"}),". We'll keep it simple - pressing the ",(0,s.jsx)(n.code,{children:"TextField"})," will open the options-list. Pressing the option(s) will update the value via callback. You can customize this to fit your usecase."]}),"\n",(0,s.jsx)(n.h2,{id:"1-installation",children:"1. Installation"}),"\n",(0,s.jsxs)(n.p,{children:["Let's start by installing the necessary dependencies. You can see complete installation instructions for ",(0,s.jsx)(n.code,{children:"react-native-bottom-sheet"})," ",(0,s.jsx)(n.a,{href:"https://gorhom.github.io/react-native-bottom-sheet/",children:"here"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"yarn add @gorhom/bottom-sheet@^4\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The library requires the ",(0,s.jsx)(n.code,{children:"react-native-gesture-handler"})," and ",(0,s.jsx)(n.code,{children:"react-native-reanimated"})," dependencies, but if you're using a newer Ignite boilerplate version, those should already be installed. Just check your ",(0,s.jsx)(n.code,{children:"package.json"})," file and if you don't see them, follow these steps:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"yarn add react-native-reanimated react-native-gesture-handler\n# or\nexpo install react-native-reanimated react-native-gesture-handler\n"})}),"\n",(0,s.jsxs)(n.h2,{id:"2-create-the-selectfieldtsx-component-file",children:["2. Create the ",(0,s.jsx)(n.code,{children:"SelectField.tsx"})," Component File"]}),"\n",(0,s.jsxs)(n.p,{children:["Instead of extending the ",(0,s.jsx)(n.code,{children:"TextField"})," component with more props and functionality, we'll be creating a wrapper for the ",(0,s.jsx)(n.code,{children:"TextField"})," component that contains additional functionality."]}),"\n",(0,s.jsx)(n.p,{children:"We'll start by creating a new file in the components directory."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"touch ./app/components/SelectField.tsx\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Let's add some preliminary code to the file. Since the ",(0,s.jsx)(n.code,{children:"TextInput"})," has its own touch handlers for focus, we'll want to disable that by wrapping it in a ",(0,s.jsx)(n.code,{children:"View"})," with no pointer-events. The new ",(0,s.jsx)(n.code,{children:"TouchableOpacity"})," will trigger our options sheet."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import React, { forwardRef, Ref, useImperativeHandle } from "react";\nimport { View, TouchableOpacity } from "react-native";\nimport { TextField, TextFieldProps } from "./TextField";\n\nexport interface SelectFieldProps\n extends Omit {}\nexport interface SelectFieldRef {}\n\nexport const SelectField = forwardRef(function SelectField(\n props: SelectFieldProps,\n ref: Ref\n) {\n const { ...TextFieldProps } = props;\n\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === "disabled";\n\n useImperativeHandle(ref, () => ({}));\n\n return (\n <>\n \n \n \n \n \n >\n );\n});\n'})}),"\n",(0,s.jsxs)(t,{children:[(0,s.jsx)("summary",{children:"Demo Preview"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import { SelectField } from "../components/SelectField";\n\nfunction FavoriteNBATeamsScreen() {\n return (\n \n );\n}\n'})}),(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{src:"https://user-images.githubusercontent.com/1775841/219003766-5331678b-a5b9-42fb-b393-3851bf2ebeaf.jpg",alt:"yulolimum-capture-2023-02-15--02-34-52"})})]}),"\n",(0,s.jsx)(n.h2,{id:"3-add-new-props-and-customize-the-textfield",children:"3. Add New Props and Customize the TextField"}),"\n",(0,s.jsxs)(n.p,{children:["Now, we can start modifying the code we added in the previous step to support multiple options as well as making the ",(0,s.jsx)(n.code,{children:"TextField"})," ",(0,s.jsx)(n.em,{children:"look"})," like a ",(0,s.jsx)(n.code,{children:"SelectField"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"add-a-caret-icon-accessory",children:"Add a Caret Icon Accessory"}),"\n",(0,s.jsxs)(n.p,{children:["Let's add an accessory to the input to make it look like a ",(0,s.jsx)(n.code,{children:"SelectField"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:' }\n/>\n'})}),"\n",(0,s.jsx)(n.h3,{id:"add-props",children:"Add Props"}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"options"})," prop can be any structure that you want (e.g. flat array of values, object where the key is the option value and the value is the label, etc). For our ",(0,s.jsx)(n.code,{children:"SelectField"})," guide, we'll be doing an array of objects."]}),"\n",(0,s.jsx)(n.p,{children:"We will support multi-select (by default) as well as a single select."}),"\n",(0,s.jsxs)(n.p,{children:["We will override the ",(0,s.jsx)(n.code,{children:"value"})," prop."]}),"\n",(0,s.jsxs)(n.p,{children:["A new ",(0,s.jsx)(n.code,{children:"renderValue"})," prop can be used to format and display a custom text value. This can be useful when the ",(0,s.jsx)(n.code,{children:"TextField"})," is not multiline, but your ",(0,s.jsx)(n.code,{children:"SelectField"})," is."]}),"\n",(0,s.jsxs)(n.p,{children:["Additionally, we'll add a new event callback called ",(0,s.jsx)(n.code,{children:"onSelect"})," since that makes more sense for a ",(0,s.jsx)(n.code,{children:"SelectField"}),". However, feel free to override ",(0,s.jsx)(n.code,{children:"TextField"}),"'s ",(0,s.jsx)(n.code,{children:"onChange"})," if you prefer."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'export interface SelectFieldProps\n extends Omit {\n value?: string[];\n renderValue?: (value: string[]) => string;\n onSelect?: (newValue: string[]) => void;\n multiple?: boolean;\n options: { label: string; value: string }[];\n}\n\n// ...\n\nconst {\n value = [],\n renderValue,\n onSelect,\n options = [],\n multiple = true,\n ...TextFieldProps\n} = props;\n'})}),"\n",(0,s.jsx)(n.h3,{id:"add-logic-to-display-selected-options",children:"Add Logic to Display Selected Options"}),"\n",(0,s.jsxs)(n.p,{children:["We'll add some code to display the selected options inside the ",(0,s.jsx)(n.code,{children:"TextField"}),". This will attempt to use the ",(0,s.jsx)(n.code,{children:"renderValue"})," formatter function and fallback to a joined string."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'const valueString =\n renderValue?.(value) ??\n value\n .map((v) => options.find((o) => o.value === v)?.label)\n .filter(Boolean)\n .join(", ");\n'})}),"\n",(0,s.jsx)(n.h3,{id:"full-code-for-this-step",children:"Full Code For This Step"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import React, { forwardRef, Ref, useImperativeHandle } from "react";\nimport { TouchableOpacity, View } from "react-native";\n// success-line\nimport { Icon } from "./Icon";\nimport { TextField, TextFieldProps } from "./TextField";\n\nexport interface SelectFieldProps\n extends Omit {\n // success-line-start\n value?: string[];\n renderValue?: (value: string[]) => string;\n onSelect?: (newValue: string[]) => void;\n multiple?: boolean;\n options: { label: string; value: string }[];\n // success-line-end\n}\nexport interface SelectFieldRef {}\n\nexport const SelectField = forwardRef(function SelectField(\n props: SelectFieldProps,\n ref: Ref\n) {\n const {\n // success-line-start\n value = [],\n onSelect,\n renderValue,\n options = [],\n multiple = true,\n // success-line-end\n ...TextFieldProps\n } = props;\n\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === "disabled";\n\n useImperativeHandle(ref, () => ({}));\n\n // success-line-start\n const valueString =\n renderValue?.(value) ??\n value\n .map((v) => options.find((o) => o.value === v)?.label)\n .filter(Boolean)\n .join(", ");\n // success-line-end\n\n return (\n <>\n \n \n }\n // success-line-end\n />\n \n \n >\n );\n});\n'})}),"\n",(0,s.jsxs)(t,{children:[(0,s.jsx)("summary",{children:"Demo Preview"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import { SelectField } from "../components/SelectField";\n\nconst teams = [\n { label: "Hawks", value: "ATL" },\n { label: "Celtics", value: "BOS" },\n // ...\n { label: "Jazz", value: "UTA" },\n { label: "Wizards", value: "WAS" },\n];\n\n// prettier-ignore\nfunction FavoriteNBATeamsScreen() {\n return (\n <>\n \n\n \n\n `Selected ${value.length} Teams`}\n />\n >\n )\n}\n'})}),(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{src:"https://user-images.githubusercontent.com/1775841/219011088-688696b8-05a6-43e8-8e9b-43578320d70a.jpg",alt:"yulolimum-capture-2023-02-15--03-07-33"})})]}),"\n",(0,s.jsx)(n.h2,{id:"4-add-the-sheet-components",children:"4. Add the Sheet Components"}),"\n",(0,s.jsxs)(n.p,{children:["In this step, we'll be adding the ",(0,s.jsx)(n.code,{children:"BottomSheetModal"})," and related components and setting up the touch-events to show/hide it."]}),"\n",(0,s.jsxs)(n.h3,{id:"add-the-bottomsheetmodalprovider",children:["Add the ",(0,s.jsx)(n.code,{children:"BottomSheetModalProvider"})]}),"\n",(0,s.jsxs)(n.p,{children:["Since we will be using the ",(0,s.jsx)(n.code,{children:"BottomSheetModal"})," component instead of ",(0,s.jsx)(n.code,{children:"BottomSheet"}),", we will need to add a provider to your entry file."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",metastring:'title="./app/app.tsx"',children:'//...\n// success-line\nimport { BottomSheetModalProvider } from "@gorhom/bottom-sheet";\n\n//...\n\nreturn (\n \n \n // success-line\n \n \n // success-line\n \n \n \n);\n\n//...\n'})}),"\n",(0,s.jsxs)(n.h3,{id:"add-the-necessary-components-to-selectfield",children:["Add the Necessary Components to ",(0,s.jsx)(n.code,{children:"SelectField"})]}),"\n",(0,s.jsx)(n.p,{children:"Now we will add the UI components that will display our options. This will be a basic example and can be customized as needed."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'// success-line-start\nimport {\n BottomSheetBackdrop,\n BottomSheetFlatList,\n BottomSheetFooter,\n BottomSheetModal,\n} from "@gorhom/bottom-sheet";\n// success-line-end\nimport React, { forwardRef, Ref, useImperativeHandle, useRef } from "react";\nimport { TouchableOpacity, View, ViewStyle } from "react-native";\nimport type { ThemedStyle } from "app/theme";\nimport { useAppTheme } from "app/utils/useAppTheme";\n// success-line\nimport { useSafeAreaInsets } from "react-native-safe-area-context";\n// success-line\nimport { spacing } from "../theme";\n// success-line\nimport { Button } from "./Button";\nimport { Icon } from "./Icon";\n// success-line\nimport { ListItem } from "./ListItem";\nimport { TextField, TextFieldProps } from "./TextField";\n\nexport interface SelectFieldProps\n extends Omit {\n value?: string[];\n renderValue?: (value: string[]) => string;\n onSelect?: (newValue: string[]) => void;\n multiple?: boolean;\n options: { label: string; value: string }[];\n}\nexport interface SelectFieldRef {\n // success-line-start\n presentOptions: () => void;\n dismissOptions: () => void;\n // success-line-end\n}\n\nexport const SelectField = forwardRef(function SelectField(\n props: SelectFieldProps,\n ref: Ref\n) {\n const {\n value = [],\n onSelect,\n renderValue,\n options = [],\n multiple = true,\n ...TextFieldProps\n } = props;\n // success-line-start\n const sheet = useRef(null);\n const { bottom } = useSafeAreaInsets();\n // success-line-end\n\n const { themed } = useAppTheme();\n\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === "disabled";\n\n // success-line\n useImperativeHandle(ref, () => ({ presentOptions, dismissOptions }));\n\n const valueString =\n renderValue?.(value) ??\n value\n .map((v) => options.find((o) => o.value === v)?.label)\n .filter(Boolean)\n .join(", ");\n\n // success-line-start\n function presentOptions() {\n if (disabled) return;\n sheet.current?.present();\n }\n\n function dismissOptions() {\n sheet.current?.dismiss();\n }\n // success-line-end\n\n return (\n <>\n \n \n }\n />\n \n \n\n {/* success-line-start */}\n (\n \n )}\n footerComponent={\n !multiple\n ? undefined\n : (props) => (\n \n \n \n )\n }\n >\n o.value}\n renderItem={({ item, index }) => (\n \n )}\n />\n \n {/* success-line-end */}\n >\n );\n});\n\n// success-line-start\nconst $bottomSheetFooter: ThemedStyle = ({ spacing }) => ({\n paddingHorizontal: spacing.lg,\n paddingBottom: spacing.xs,\n});\n\nconst $listItem: ThemedStyle = ({ spacing }) => ({\n paddingHorizontal: spacing.lg,\n});\n// success-line-end\n'})}),"\n",(0,s.jsxs)(t,{children:[(0,s.jsx)("summary",{children:"Demo Preview"}),(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{src:"https://user-images.githubusercontent.com/1775841/219029547-3c92dbdc-5f04-4f02-a82e-0af9392af6ad.gif",alt:"yulolimum-capture-2023-02-15--04-38-11"})})]}),"\n",(0,s.jsx)(n.h2,{id:"5-add-selected-state-to-options-and-hook-up-callback",children:"5. Add Selected State to Options and Hook Up Callback"}),"\n",(0,s.jsx)(n.p,{children:"The last step is to add the selected state to our options inside the sheet as well as hook up the callback to change the value."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import {\n BottomSheetBackdrop,\n BottomSheetFlatList,\n BottomSheetFooter,\n BottomSheetModal,\n} from "@gorhom/bottom-sheet";\nimport React, { forwardRef, Ref, useImperativeHandle, useRef } from "react";\nimport { TouchableOpacity, View, ViewStyle } from "react-native";\nimport { useSafeAreaInsets } from "react-native-safe-area-context";\nimport type { ThemedStyle } from "app/theme";\nimport { useAppTheme } from "app/utils/useAppTheme";\nimport { Button } from "./Button";\nimport { Icon } from "./Icon";\nimport { ListItem } from "./ListItem";\nimport { TextField, TextFieldProps } from "./TextField";\n\nexport interface SelectFieldProps\n extends Omit {\n value?: string[];\n renderValue?: (value: string[]) => string;\n onSelect?: (newValue: string[]) => void;\n multiple?: boolean;\n options: { label: string; value: string }[];\n}\nexport interface SelectFieldRef {\n presentOptions: () => void;\n dismissOptions: () => void;\n}\n\n// success-line-start\nfunction without(array: T[], value: T) {\n return array.filter((v) => v !== value);\n}\n// success-line-end\n\nexport const SelectField = forwardRef(function SelectField(\n props: SelectFieldProps,\n ref: Ref\n) {\n const {\n value = [],\n onSelect,\n renderValue,\n options = [],\n multiple = true,\n ...TextFieldProps\n } = props;\n const sheet = useRef(null);\n const { bottom } = useSafeAreaInsets();\n const {\n themed,\n // success-line-start\n theme: { colors },\n // success-line-end\n } = useAppTheme();\n\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === "disabled";\n\n useImperativeHandle(ref, () => ({ presentOptions, dismissOptions }));\n\n const valueString =\n renderValue?.(value) ??\n value\n .map((v) => options.find((o) => o.value === v)?.label)\n .filter(Boolean)\n .join(", ");\n\n function presentOptions() {\n if (disabled) return;\n\n sheet.current?.present();\n }\n\n function dismissOptions() {\n sheet.current?.dismiss();\n }\n\n // success-line-start\n function updateValue(optionValue: string) {\n if (value.includes(optionValue)) {\n onSelect?.(multiple ? without(value, optionValue) : []);\n } else {\n onSelect?.(multiple ? [...value, optionValue] : [optionValue]);\n if (!multiple) dismissOptions();\n }\n }\n // success-line-end\n\n return (\n <>\n \n \n }\n />\n \n \n\n (\n \n )}\n footerComponent={\n !multiple\n ? undefined\n : (props) => (\n \n \n \n )\n }\n >\n o.value}\n renderItem={({ item, index }) => (\n updateValue(item.value)}\n // success-line-end\n />\n )}\n />\n \n >\n );\n});\n\nconst $bottomSheetFooter: ThemedStyle = ({ spacing }) => ({\n paddingHorizontal: spacing.lg,\n paddingBottom: spacing.xs,\n});\n\nconst $listItem: ThemedStyle = ({ spacing }) => ({\n paddingHorizontal: spacing.lg,\n});\n'})}),"\n",(0,s.jsx)(n.p,{children:"And we're done!"}),"\n",(0,s.jsxs)(t,{children:[(0,s.jsx)("summary",{children:"Demo Preview"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import { SelectField } from "../components/SelectField";\n\nconst teams = [\n { label: "Hawks", value: "ATL" },\n { label: "Celtics", value: "BOS" },\n // ...\n { label: "Jazz", value: "UTA" },\n { label: "Wizards", value: "WAS" },\n];\n\nfunction FavoriteNBATeamsScreen() {\n const [selectedTeam, setSelectedTeam] = useState([]);\n const [selectedTeams, setSelectedTeams] = useState([]);\n\n return (\n <>\n \n\n `Selected ${value.length} Teams`}\n />\n >\n );\n}\n'})}),(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{src:"https://user-images.githubusercontent.com/1775841/219036892-e7e38288-b859-487d-b51a-ca67f91c83ff.gif",alt:"yulolimum-capture-2023-02-15--05-11-11"})})]})]})}function h(e={}){const{wrapper:n}={...(0,o.M)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(p,{...e})}):p(e)}}}]);
\ No newline at end of file
diff --git a/assets/js/d63d2b89.67ab2f65.js b/assets/js/d63d2b89.67ab2f65.js
deleted file mode 100644
index 793a25ff..00000000
--- a/assets/js/d63d2b89.67ab2f65.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[5276],{6960:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>a,default:()=>h,frontMatter:()=>l,metadata:()=>r,toc:()=>d});var s=t(7624),o=t(2172),i=t(2180);const l={title:"SelectField using `react-native-bottom-sheet`",description:"Extending Ignite's TextField to be used as a SelectField with react-native-bottom-sheet",tags:["TextField","SelectField","UI"],last_update:{author:"Yulian Glukhenko"},publish_date:new Date("2023-02-15T00:00:00.000Z")},a="SelectField using react-native-bottom-sheet",r={id:"recipes/SelectFieldWithBottomSheet",title:"SelectField using `react-native-bottom-sheet`",description:"Extending Ignite's TextField to be used as a SelectField with react-native-bottom-sheet",source:"@site/docs/recipes/SelectFieldWithBottomSheet.mdx",sourceDirName:"recipes",slug:"/recipes/SelectFieldWithBottomSheet",permalink:"/docs/recipes/SelectFieldWithBottomSheet",draft:!1,unlisted:!1,tags:[{label:"TextField",permalink:"/docs/tags/text-field"},{label:"SelectField",permalink:"/docs/tags/select-field"},{label:"UI",permalink:"/docs/tags/ui"}],version:"current",lastUpdatedBy:"Yulian Glukhenko",lastUpdatedAt:1728940271,formattedLastUpdatedAt:"Oct 14, 2024",frontMatter:{title:"SelectField using `react-native-bottom-sheet`",description:"Extending Ignite's TextField to be used as a SelectField with react-native-bottom-sheet",tags:["TextField","SelectField","UI"],last_update:{author:"Yulian Glukhenko"},publish_date:"2023-02-15T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Sample YAML for CircleCi for Ignite",permalink:"/docs/recipes/SampleYAMLCircleCI"},next:{title:"Switch Between Expo Go and Expo CNG",permalink:"/docs/recipes/SwitchBetweenExpoGoCNG"}},c={},d=[{value:"1. Installation",id:"1-installation",level:2},{value:"2. Create the SelectField.tsx
Component File",id:"2-create-the-selectfieldtsx-component-file",level:2},{value:"3. Add New Props and Customize the TextField",id:"3-add-new-props-and-customize-the-textfield",level:2},{value:"Add a Caret Icon Accessory",id:"add-a-caret-icon-accessory",level:3},{value:"Add Props",id:"add-props",level:3},{value:"Add Logic to Display Selected Options",id:"add-logic-to-display-selected-options",level:3},{value:"Full Code For This Step",id:"full-code-for-this-step",level:3},{value:"4. Add the Sheet Components",id:"4-add-the-sheet-components",level:2},{value:"Add the BottomSheetModalProvider
",id:"add-the-bottomsheetmodalprovider",level:3},{value:"Add the Necessary Components to SelectField
",id:"add-the-necessary-components-to-selectfield",level:3},{value:"5. Add Selected State to Options and Hook Up Callback",id:"5-add-selected-state-to-options-and-hook-up-callback",level:2}];function p(e){const n={a:"a",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",img:"img",p:"p",pre:"pre",...(0,o.M)(),...e.components},{Details:t}=n;return t||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(n.h1,{id:"selectfield-using-react-native-bottom-sheet",children:["SelectField using ",(0,s.jsx)(n.code,{children:"react-native-bottom-sheet"})]}),"\n",(0,s.jsxs)(n.p,{children:["In this guide, we'll be creating a ",(0,s.jsx)(n.code,{children:"SelectField"})," component by extending the ",(0,s.jsx)(n.code,{children:"TextField"})," with a scrollable options View and additional props to handle its customization."]}),"\n",(0,s.jsx)(i.c,{width:"100%",controls:!0,url:"https://user-images.githubusercontent.com/1775841/219038677-bcc9c61d-1776-4aad-bb50-1e932721bc04.mp4"}),"\n",(0,s.jsxs)(n.p,{children:["We will be using the ",(0,s.jsx)(n.a,{href:"https://gorhom.github.io/react-native-bottom-sheet/",children:(0,s.jsx)(n.code,{children:"react-native-bottom-sheet"})})," library for the options list, the ",(0,s.jsx)(n.code,{children:"ListItem"})," component for displaying individual options, and the ",(0,s.jsx)(n.code,{children:"TextField"})," component for opening the options list and displaying selected options."]}),"\n",(0,s.jsxs)(n.p,{children:["There are many ways you can setup ",(0,s.jsx)(n.code,{children:"react-native-bottom-sheet"})," to function as a ",(0,s.jsx)(n.code,{children:"Picker"}),". We'll keep it simple - pressing the ",(0,s.jsx)(n.code,{children:"TextField"})," will open the options-list. Pressing the option(s) will update the value via callback. You can customize this to fit your usecase."]}),"\n",(0,s.jsx)(n.h2,{id:"1-installation",children:"1. Installation"}),"\n",(0,s.jsxs)(n.p,{children:["Let's start by installing the necessary dependencies. You can see complete installation instructions for ",(0,s.jsx)(n.code,{children:"react-native-bottom-sheet"})," ",(0,s.jsx)(n.a,{href:"https://gorhom.github.io/react-native-bottom-sheet/",children:"here"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"yarn add @gorhom/bottom-sheet@^4\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The library requires the ",(0,s.jsx)(n.code,{children:"react-native-gesture-handler"})," and ",(0,s.jsx)(n.code,{children:"react-native-reanimated"})," dependencies, but if you're using a newer Ignite boilerplate version, those should already be installed. Just check your ",(0,s.jsx)(n.code,{children:"package.json"})," file and if you don't see them, follow these steps:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"yarn add react-native-reanimated react-native-gesture-handler\n# or\nexpo install react-native-reanimated react-native-gesture-handler\n"})}),"\n",(0,s.jsxs)(n.h2,{id:"2-create-the-selectfieldtsx-component-file",children:["2. Create the ",(0,s.jsx)(n.code,{children:"SelectField.tsx"})," Component File"]}),"\n",(0,s.jsxs)(n.p,{children:["Instead of extending the ",(0,s.jsx)(n.code,{children:"TextField"})," component with more props and functionality, we'll be creating a wrapper for the ",(0,s.jsx)(n.code,{children:"TextField"})," component that contains additional functionality."]}),"\n",(0,s.jsx)(n.p,{children:"We'll start by creating a new file in the components directory."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"touch ./app/components/SelectField.tsx\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Let's add some preliminary code to the file. Since the ",(0,s.jsx)(n.code,{children:"TextInput"})," has its own touch handlers for focus, we'll want to disable that by wrapping it in a ",(0,s.jsx)(n.code,{children:"View"})," with no pointer-events. The new ",(0,s.jsx)(n.code,{children:"TouchableOpacity"})," will trigger our options sheet."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import React, { forwardRef, Ref, useImperativeHandle } from "react";\nimport { View, TouchableOpacity } from "react-native";\nimport { TextField, TextFieldProps } from "./TextField";\n\nexport interface SelectFieldProps\n extends Omit {}\nexport interface SelectFieldRef {}\n\nexport const SelectField = forwardRef(function SelectField(\n props: SelectFieldProps,\n ref: Ref\n) {\n const { ...TextFieldProps } = props;\n\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === "disabled";\n\n useImperativeHandle(ref, () => ({}));\n\n return (\n <>\n \n \n \n \n \n >\n );\n});\n'})}),"\n",(0,s.jsxs)(t,{children:[(0,s.jsx)("summary",{children:"Demo Preview"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import { SelectField } from "../components/SelectField";\n\nfunction FavoriteNBATeamsScreen() {\n return (\n \n );\n}\n'})}),(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{src:"https://user-images.githubusercontent.com/1775841/219003766-5331678b-a5b9-42fb-b393-3851bf2ebeaf.jpg",alt:"yulolimum-capture-2023-02-15--02-34-52"})})]}),"\n",(0,s.jsx)(n.h2,{id:"3-add-new-props-and-customize-the-textfield",children:"3. Add New Props and Customize the TextField"}),"\n",(0,s.jsxs)(n.p,{children:["Now, we can start modifying the code we added in the previous step to support multiple options as well as making the ",(0,s.jsx)(n.code,{children:"TextField"})," ",(0,s.jsx)(n.em,{children:"look"})," like a ",(0,s.jsx)(n.code,{children:"SelectField"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"add-a-caret-icon-accessory",children:"Add a Caret Icon Accessory"}),"\n",(0,s.jsxs)(n.p,{children:["Let's add an accessory to the input to make it look like a ",(0,s.jsx)(n.code,{children:"SelectField"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:' }\n/>\n'})}),"\n",(0,s.jsx)(n.h3,{id:"add-props",children:"Add Props"}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"options"})," prop can be any structure that you want (e.g. flat array of values, object where the key is the option value and the value is the label, etc). For our ",(0,s.jsx)(n.code,{children:"SelectField"})," guide, we'll be doing an array of objects."]}),"\n",(0,s.jsx)(n.p,{children:"We will support multi-select (by default) as well as a single select."}),"\n",(0,s.jsxs)(n.p,{children:["We will override the ",(0,s.jsx)(n.code,{children:"value"})," prop."]}),"\n",(0,s.jsxs)(n.p,{children:["A new ",(0,s.jsx)(n.code,{children:"renderValue"})," prop can be used to format and display a custom text value. This can be useful when the ",(0,s.jsx)(n.code,{children:"TextField"})," is not multiline, but your ",(0,s.jsx)(n.code,{children:"SelectField"})," is."]}),"\n",(0,s.jsxs)(n.p,{children:["Additionally, we'll add a new event callback called ",(0,s.jsx)(n.code,{children:"onSelect"})," since that makes more sense for a ",(0,s.jsx)(n.code,{children:"SelectField"}),". However, feel free to override ",(0,s.jsx)(n.code,{children:"TextField"}),"'s ",(0,s.jsx)(n.code,{children:"onChange"})," if you prefer."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'export interface SelectFieldProps\n extends Omit {\n value?: string[];\n renderValue?: (value: string[]) => string;\n onSelect?: (newValue: string[]) => void;\n multiple?: boolean;\n options: { label: string; value: string }[];\n}\n\n// ...\n\nconst {\n value = [],\n renderValue,\n onSelect,\n options = [],\n multiple = true,\n ...TextFieldProps\n} = props;\n'})}),"\n",(0,s.jsx)(n.h3,{id:"add-logic-to-display-selected-options",children:"Add Logic to Display Selected Options"}),"\n",(0,s.jsxs)(n.p,{children:["We'll add some code to display the selected options inside the ",(0,s.jsx)(n.code,{children:"TextField"}),". This will attempt to use the ",(0,s.jsx)(n.code,{children:"renderValue"})," formatter function and fallback to a joined string."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'const valueString =\n renderValue?.(value) ??\n value\n .map((v) => options.find((o) => o.value === v)?.label)\n .filter(Boolean)\n .join(", ");\n'})}),"\n",(0,s.jsx)(n.h3,{id:"full-code-for-this-step",children:"Full Code For This Step"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import React, { forwardRef, Ref, useImperativeHandle } from "react";\nimport { TouchableOpacity, View } from "react-native";\n// success-line\nimport { Icon } from "./Icon";\nimport { TextField, TextFieldProps } from "./TextField";\n\nexport interface SelectFieldProps\n extends Omit {\n // success-line-start\n value?: string[];\n renderValue?: (value: string[]) => string;\n onSelect?: (newValue: string[]) => void;\n multiple?: boolean;\n options: { label: string; value: string }[];\n // success-line-end\n}\nexport interface SelectFieldRef {}\n\nexport const SelectField = forwardRef(function SelectField(\n props: SelectFieldProps,\n ref: Ref\n) {\n const {\n // success-line-start\n value = [],\n onSelect,\n renderValue,\n options = [],\n multiple = true,\n // success-line-end\n ...TextFieldProps\n } = props;\n\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === "disabled";\n\n useImperativeHandle(ref, () => ({}));\n\n // success-line-start\n const valueString =\n renderValue?.(value) ??\n value\n .map((v) => options.find((o) => o.value === v)?.label)\n .filter(Boolean)\n .join(", ");\n // success-line-end\n\n return (\n <>\n \n \n }\n // success-line-end\n />\n \n \n >\n );\n});\n'})}),"\n",(0,s.jsxs)(t,{children:[(0,s.jsx)("summary",{children:"Demo Preview"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import { SelectField } from "../components/SelectField";\n\nconst teams = [\n { label: "Hawks", value: "ATL" },\n { label: "Celtics", value: "BOS" },\n // ...\n { label: "Jazz", value: "UTA" },\n { label: "Wizards", value: "WAS" },\n];\n\n// prettier-ignore\nfunction FavoriteNBATeamsScreen() {\n return (\n <>\n \n\n \n\n `Selected ${value.length} Teams`}\n />\n >\n )\n}\n'})}),(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{src:"https://user-images.githubusercontent.com/1775841/219011088-688696b8-05a6-43e8-8e9b-43578320d70a.jpg",alt:"yulolimum-capture-2023-02-15--03-07-33"})})]}),"\n",(0,s.jsx)(n.h2,{id:"4-add-the-sheet-components",children:"4. Add the Sheet Components"}),"\n",(0,s.jsxs)(n.p,{children:["In this step, we'll be adding the ",(0,s.jsx)(n.code,{children:"BottomSheetModal"})," and related components and setting up the touch-events to show/hide it."]}),"\n",(0,s.jsxs)(n.h3,{id:"add-the-bottomsheetmodalprovider",children:["Add the ",(0,s.jsx)(n.code,{children:"BottomSheetModalProvider"})]}),"\n",(0,s.jsxs)(n.p,{children:["Since we will be using the ",(0,s.jsx)(n.code,{children:"BottomSheetModal"})," component instead of ",(0,s.jsx)(n.code,{children:"BottomSheet"}),", we will need to add a provider to your entry file."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",metastring:'title="./app/app.tsx"',children:'//...\n// success-line\nimport { BottomSheetModalProvider } from "@gorhom/bottom-sheet";\n\n//...\n\nreturn (\n \n \n // success-line\n \n \n // success-line\n \n \n \n);\n\n//...\n'})}),"\n",(0,s.jsxs)(n.h3,{id:"add-the-necessary-components-to-selectfield",children:["Add the Necessary Components to ",(0,s.jsx)(n.code,{children:"SelectField"})]}),"\n",(0,s.jsx)(n.p,{children:"Now we will add the UI components that will display our options. This will be a basic example and can be customized as needed."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'// success-line-start\nimport {\n BottomSheetBackdrop,\n BottomSheetFlatList,\n BottomSheetFooter,\n BottomSheetModal,\n} from "@gorhom/bottom-sheet";\n// success-line-end\nimport React, { forwardRef, Ref, useImperativeHandle, useRef } from "react";\nimport { TouchableOpacity, View, ViewStyle } from "react-native";\nimport type { ThemedStyle } from "app/theme";\nimport { useAppTheme } from "app/utils/useAppTheme";\n// success-line\nimport { useSafeAreaInsets } from "react-native-safe-area-context";\n// success-line\nimport { spacing } from "../theme";\n// success-line\nimport { Button } from "./Button";\nimport { Icon } from "./Icon";\n// success-line\nimport { ListItem } from "./ListItem";\nimport { TextField, TextFieldProps } from "./TextField";\n\nexport interface SelectFieldProps\n extends Omit {\n value?: string[];\n renderValue?: (value: string[]) => string;\n onSelect?: (newValue: string[]) => void;\n multiple?: boolean;\n options: { label: string; value: string }[];\n}\nexport interface SelectFieldRef {\n // success-line-start\n presentOptions: () => void;\n dismissOptions: () => void;\n // success-line-end\n}\n\nexport const SelectField = forwardRef(function SelectField(\n props: SelectFieldProps,\n ref: Ref\n) {\n const {\n value = [],\n onSelect,\n renderValue,\n options = [],\n multiple = true,\n ...TextFieldProps\n } = props;\n // success-line-start\n const sheet = useRef(null);\n const { bottom } = useSafeAreaInsets();\n // success-line-end\n\n const { themed } = useAppTheme();\n\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === "disabled";\n\n // success-line\n useImperativeHandle(ref, () => ({ presentOptions, dismissOptions }));\n\n const valueString =\n renderValue?.(value) ??\n value\n .map((v) => options.find((o) => o.value === v)?.label)\n .filter(Boolean)\n .join(", ");\n\n // success-line-start\n function presentOptions() {\n if (disabled) return;\n sheet.current?.present();\n }\n\n function dismissOptions() {\n sheet.current?.dismiss();\n }\n // success-line-end\n\n return (\n <>\n \n \n }\n />\n \n \n\n {/* success-line-start */}\n (\n \n )}\n footerComponent={\n !multiple\n ? undefined\n : (props) => (\n \n \n \n )\n }\n >\n o.value}\n renderItem={({ item, index }) => (\n \n )}\n />\n \n {/* success-line-end */}\n >\n );\n});\n\n// success-line-start\nconst $bottomSheetFooter: ThemedStyle = ({ spacing }) => ({\n paddingHorizontal: spacing.lg,\n paddingBottom: spacing.xs,\n});\n\nconst $listItem: ThemedStyle = ({ spacing }) => ({\n paddingHorizontal: spacing.lg,\n});\n// success-line-end\n'})}),"\n",(0,s.jsxs)(t,{children:[(0,s.jsx)("summary",{children:"Demo Preview"}),(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{src:"https://user-images.githubusercontent.com/1775841/219029547-3c92dbdc-5f04-4f02-a82e-0af9392af6ad.gif",alt:"yulolimum-capture-2023-02-15--04-38-11"})})]}),"\n",(0,s.jsx)(n.h2,{id:"5-add-selected-state-to-options-and-hook-up-callback",children:"5. Add Selected State to Options and Hook Up Callback"}),"\n",(0,s.jsx)(n.p,{children:"The last step is to add the selected state to our options inside the sheet as well as hook up the callback to change the value."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import {\n BottomSheetBackdrop,\n BottomSheetFlatList,\n BottomSheetFooter,\n BottomSheetModal,\n} from "@gorhom/bottom-sheet";\nimport React, { forwardRef, Ref, useImperativeHandle, useRef } from "react";\nimport { TouchableOpacity, View, ViewStyle } from "react-native";\nimport { useSafeAreaInsets } from "react-native-safe-area-context";\nimport type { ThemedStyle } from "app/theme";\nimport { useAppTheme } from "app/utils/useAppTheme";\nimport { Button } from "./Button";\nimport { Icon } from "./Icon";\nimport { ListItem } from "./ListItem";\nimport { TextField, TextFieldProps } from "./TextField";\n\nexport interface SelectFieldProps\n extends Omit {\n value?: string[];\n renderValue?: (value: string[]) => string;\n onSelect?: (newValue: string[]) => void;\n multiple?: boolean;\n options: { label: string; value: string }[];\n}\nexport interface SelectFieldRef {\n presentOptions: () => void;\n dismissOptions: () => void;\n}\n\n// success-line-start\nfunction without(array: T[], value: T) {\n return array.filter((v) => v !== value);\n}\n// success-line-end\n\nexport const SelectField = forwardRef(function SelectField(\n props: SelectFieldProps,\n ref: Ref\n) {\n const {\n value = [],\n onSelect,\n renderValue,\n options = [],\n multiple = true,\n ...TextFieldProps\n } = props;\n const sheet = useRef(null);\n const { bottom } = useSafeAreaInsets();\n const {\n themed,\n // success-line-start\n theme: { colors },\n // success-line-end\n } = useAppTheme();\n\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === "disabled";\n\n useImperativeHandle(ref, () => ({ presentOptions, dismissOptions }));\n\n const valueString =\n renderValue?.(value) ??\n value\n .map((v) => options.find((o) => o.value === v)?.label)\n .filter(Boolean)\n .join(", ");\n\n function presentOptions() {\n if (disabled) return;\n\n sheet.current?.present();\n }\n\n function dismissOptions() {\n sheet.current?.dismiss();\n }\n\n // success-line-start\n function updateValue(optionValue: string) {\n if (value.includes(optionValue)) {\n onSelect?.(multiple ? without(value, optionValue) : []);\n } else {\n onSelect?.(multiple ? [...value, optionValue] : [optionValue]);\n if (!multiple) dismissOptions();\n }\n }\n // success-line-end\n\n return (\n <>\n \n \n }\n />\n \n \n\n (\n \n )}\n footerComponent={\n !multiple\n ? undefined\n : (props) => (\n \n \n \n )\n }\n >\n o.value}\n renderItem={({ item, index }) => (\n updateValue(item.value)}\n // success-line-end\n />\n )}\n />\n \n >\n );\n});\n\nconst $bottomSheetFooter: ThemedStyle = ({ spacing }) => ({\n paddingHorizontal: spacing.lg,\n paddingBottom: spacing.xs,\n});\n\nconst $listItem: ThemedStyle = ({ spacing }) => ({\n paddingHorizontal: spacing.lg,\n});\n'})}),"\n",(0,s.jsx)(n.p,{children:"And we're done!"}),"\n",(0,s.jsxs)(t,{children:[(0,s.jsx)("summary",{children:"Demo Preview"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import { SelectField } from "../components/SelectField";\n\nconst teams = [\n { label: "Hawks", value: "ATL" },\n { label: "Celtics", value: "BOS" },\n // ...\n { label: "Jazz", value: "UTA" },\n { label: "Wizards", value: "WAS" },\n];\n\nfunction FavoriteNBATeamsScreen() {\n const [selectedTeam, setSelectedTeam] = useState([]);\n const [selectedTeams, setSelectedTeams] = useState([]);\n\n return (\n <>\n \n\n `Selected ${value.length} Teams`}\n />\n >\n );\n}\n'})}),(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{src:"https://user-images.githubusercontent.com/1775841/219036892-e7e38288-b859-487d-b51a-ca67f91c83ff.gif",alt:"yulolimum-capture-2023-02-15--05-11-11"})})]})]})}function h(e={}){const{wrapper:n}={...(0,o.M)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(p,{...e})}):p(e)}}}]);
\ No newline at end of file
diff --git a/assets/js/ea9dae2d.314b59ac.js b/assets/js/ea9dae2d.314b59ac.js
new file mode 100644
index 00000000..6e74670b
--- /dev/null
+++ b/assets/js/ea9dae2d.314b59ac.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[8568],{3860:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>p,frontMatter:()=>r,metadata:()=>a,toc:()=>c});var s=t(7624),i=t(2172);const r={title:"Setting up a Yarn monorepo with Ignite",description:"How to set up a Yarn monorepo using Ignite and two extra shared utilities",tags:["Ignite","Monorepo","Yarn"],last_update:{author:"Felipe Pe\xf1a"},publish_date:new Date("2024-08-22T00:00:00.000Z")},o="Setting up a Yarn monorepo with Ignite",a={id:"recipes/SettingUpYarnMonorepo",title:"Setting up a Yarn monorepo with Ignite",description:"How to set up a Yarn monorepo using Ignite and two extra shared utilities",source:"@site/docs/recipes/SettingUpYarnMonorepo.md",sourceDirName:"recipes",slug:"/recipes/SettingUpYarnMonorepo",permalink:"/docs/recipes/SettingUpYarnMonorepo",draft:!1,unlisted:!1,tags:[{label:"Ignite",permalink:"/docs/tags/ignite"},{label:"Monorepo",permalink:"/docs/tags/monorepo"},{label:"Yarn",permalink:"/docs/tags/yarn"}],version:"current",lastUpdatedBy:"Felipe Pe\xf1a",lastUpdatedAt:1734368632,formattedLastUpdatedAt:"Dec 16, 2024",frontMatter:{title:"Setting up a Yarn monorepo with Ignite",description:"How to set up a Yarn monorepo using Ignite and two extra shared utilities",tags:["Ignite","Monorepo","Yarn"],last_update:{author:"Felipe Pe\xf1a"},publish_date:"2024-08-22T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"SelectField using `react-native-bottom-sheet`",permalink:"/docs/recipes/SelectFieldWithBottomSheet"},next:{title:"Switch Between Expo Go and Expo CNG",permalink:"/docs/recipes/SwitchBetweenExpoGoCNG"}},l={},c=[{value:"Prerequisites",id:"prerequisites",level:2},{value:"Use case",id:"use-case",level:2},{value:"Step 1: Setting up the monorepo",id:"step-1-setting-up-the-monorepo",level:2},{value:"Step 2: Create mobile app using Ignite",id:"step-2-create-mobile-app-using-ignite",level:2},{value:"Step 3: Install dependencies",id:"step-3-install-dependencies",level:2},{value:"Step 4: Add a shared ESLint configuration with TypeScript",id:"step-4-add-a-shared-eslint-configuration-with-typescript",level:2},{value:"Step 6: Use the shared ESLint configuration in the mobile app",id:"step-6-use-the-shared-eslint-configuration-in-the-mobile-app",level:2},{value:"Step 7: Create a shared UI components package",id:"step-7-create-a-shared-ui-components-package",level:2},{value:"Step 8: Use the shared UI package in the mobile app",id:"step-8-use-the-shared-ui-package-in-the-mobile-app",level:2},{value:"Step 9: Run mobile app to make sure logic was added",id:"step-9-run-mobile-app-to-make-sure-logic-was-added",level:2},{value:"Step 10: Add Yarn global scripts (optional)",id:"step-10-add-yarn-global-scripts-optional",level:2},{value:"Conclusion",id:"conclusion",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,i.M)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"setting-up-a-yarn-monorepo-with-ignite",children:"Setting up a Yarn monorepo with Ignite"}),"\n",(0,s.jsxs)(n.p,{children:["\ud83d\udc4b Hello and welcome to this monorepo guide! We know setting up a project using a monorepo structure can be sometimes challenging, therefore we created this guide to lead you through process. We'll be focusing on ",(0,s.jsx)(n.a,{href:"https://reactnative.dev/",children:"React Native"})," projects using the ",(0,s.jsx)(n.a,{href:"https://github.com/infinitered/ignite",children:"Ignite"})," framework and the ",(0,s.jsx)(n.a,{href:"https://yarnpkg.com",children:"Yarn"})," tool."]}),"\n",(0,s.jsx)(n.p,{children:"This guide starts by setting up the monorepo structure, then create a React Native app using the Ignite CLI, to finally end up generating two shared utilities: a form-validator utility and a shared UI library, that we will be integrate into the app."}),"\n",(0,s.jsx)(n.h2,{id:"prerequisites",children:"Prerequisites"}),"\n",(0,s.jsx)(n.p,{children:"Before we begin, we want to ensure you have these standard tools installed on your machine:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://nodejs.org/en",children:"Node.js"})," (version 18 or later)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"https://yarnpkg.com",children:"Yarn"})," (version 3.8 or later)"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Now, let\u2019s dive into the specific use case this guide will address."}),"\n",(0,s.jsx)(n.h2,{id:"use-case",children:"Use case"}),"\n",(0,s.jsx)(n.p,{children:"In a monorepo setup with multiple applications, like a React Native mobile app and a React web app, can share common functionalities."}),"\n",(0,s.jsx)(n.p,{children:"In this guide we will be focusing on that premise and creating/utilizing shared utilities within the monorepo. For instance, if you have several apps that need to share an ESLint configuration or UI components, you can create reusable packages that can be integrated across all your apps."}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["Wait! How do I even know if my project will benefit from a monorepo structure? No worries! We have more documentation on monorepo tools and whether you want to choose this way of organization. You can find it ",(0,s.jsx)(n.a,{href:"MonoreposOverview",children:"here"}),"."]})}),"\n",(0,s.jsx)(n.p,{children:"By centralizing these kind of utilities, you can reduce code duplication and simplify maintenance work, ensuring any updates or bug fixes are immediately available in all your apps within the monorepo."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"So in summary we\u2019ll create a React Native app along with two shared packages: one for holding a common ESLint configuration and another for shared UI components, to finally integrate those back into the app."})}),"\n",(0,s.jsx)(n.p,{children:"Let's begin!"}),"\n",(0,s.jsx)(n.h2,{id:"step-1-setting-up-the-monorepo",children:"Step 1: Setting up the monorepo"}),"\n",(0,s.jsxs)(n.p,{children:["First, read carefully what ",(0,s.jsx)(n.a,{href:"https://docs.expo.dev/guides/monorepos/",children:"Expo documentation on setting up monorepos"})," says."]}),"\n",(0,s.jsxs)(n.p,{children:["After this step, you'll get a folder with a ",(0,s.jsx)(n.code,{children:"packages/"})," and ",(0,s.jsx)(n.code,{children:"apps/"})," directories and a ",(0,s.jsx)(n.code,{children:"package.json"})," file with basic workspace configuration."]}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Initialize the monorepo:"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"mkdir monorepo-example\ncd monorepo-example\nyarn init -y\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"2",children:["\n",(0,s.jsxs)(n.li,{children:["Configure workspaces in ",(0,s.jsx)(n.code,{children:"package.json"}),":"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "name": "monorepo-example",\n // error-line\n "packageManager": "yarn@3.8.4"\n // success-line-start\n "packageManager": "yarn@3.8.4",\n "private": true,\n "workspaces": [\n "apps/*",\n "packages/*"\n ]\n // success-line-end\n}\n'})}),"\n",(0,s.jsxs)(n.admonition,{type:"info",children:[(0,s.jsxs)(n.p,{children:["We recommend organizing your monorepo's folder structure in a way that best suits the needs of your project. While this guide suggests using ",(0,s.jsx)(n.code,{children:"apps/"})," and ",(0,s.jsx)(n.code,{children:"packages/"}),", you can rename or add directories like, for example, ",(0,s.jsx)(n.code,{children:"services/"})," or ",(0,s.jsx)(n.code,{children:"libs/"})," to fit your workflow."]}),(0,s.jsx)(n.p,{children:"The key here is to keep your monorepo clear and organized, ensuring that it\u2019s easy to manage and navigate for you and your team \ud83e\udd1c\ud83c\udffb."})]}),"\n",(0,s.jsxs)(n.ol,{start:"3",children:["\n",(0,s.jsx)(n.li,{children:"Create directory structure:"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"mkdir apps packages\n"})}),"\n",(0,s.jsx)(n.h2,{id:"step-2-create-mobile-app-using-ignite",children:"Step 2: Create mobile app using Ignite"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.a,{href:"https://github.com/infinitered/ignite",children:"Ignite"})," is Infinite's Red battle-tested React Native boilerplate. We're proud to say we use it every time we start a new project."]}),"\n",(0,s.jsx)(n.p,{children:"In this step we'll take advantage of Ignite's CLI and create a React Native app within the monorepo."}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["Install the ",(0,s.jsx)(n.a,{href:"https://www.npmjs.com/package/ignite-cli",children:"Ignite CLI"})," (if you haven't already):"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"npx ignite-cli@latest\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"2",children:["\n",(0,s.jsxs)(n.li,{children:["Generate a new app:\nNavigate to the ",(0,s.jsx)(n.code,{children:"apps/"})," directory and run the following command to create a new app:"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"cd apps\nnpx ignite-cli new mobile\n"})}),"\n",(0,s.jsx)(n.p,{children:"We recommend the following answers to the CLI prompts:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"\ud83d\udcdd Do you want to use Expo?: Expo - Recommended for almost all apps [Default]\n\ud83d\udcdd Which Expo workflow?: Expo Go - For simple apps that don't need custom native code [Default]\n\ud83d\udcdd Do you want to initialize a git repository?: No\n\ud83d\udcdd Remove demo code? We recommend leaving it in if it's your first time using Ignite: No\n\ud83d\udcdd Which package manager do you want to use?: yarn\n\ud83d\udcdd Do you want to install dependencies?: No\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"3",children:["\n",(0,s.jsxs)(n.li,{children:["Open the ",(0,s.jsx)(n.code,{children:"metro.config.js"})," file:"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"touch mobile/metro.config.js\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"4",children:["\n",(0,s.jsxs)(n.li,{children:["In order to fit a monorepo structurem we need to adjust the Metro configuration. Let's do that by updating these lines in the ",(0,s.jsx)(n.code,{children:"metro.config.js"})," file (this changes are taken from the ",(0,s.jsx)(n.a,{href:"https://docs.expo.dev/guides/monorepos/",children:"Expo guide"}),"):"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-js",children:"// Learn more https://docs.expo.io/guides/customizing-metro\nconst { getDefaultConfig } = require('expo/metro-config');\n\n// success-line-start\n// Get monorepo root folder\nconst monorepoRoot = path.resolve(projectRoot, '../..');\n// success-line-end\n\n/** @type {import('expo/metro-config').MetroConfig} */\n// error-line\nconst config = getDefaultConfig(__dirname);\n// success-line\nconst config = getDefaultConfig(projectRoot);\n\nconfig.transformer.getTransformOptions = async () => ({\n transform: {\n // Inline requires are very useful for deferring loading of large dependencies/components.\n // For example, we use it in app.tsx to conditionally load Reactotron.\n // However, this comes with some gotchas.\n // Read more here: https://reactnative.dev/docs/optimizing-javascript-loading\n // And here: https://github.com/expo/expo/issues/27279#issuecomment-1971610698\n inlineRequires: true,\n },\n});\n\n// success-line-start\n// 1. Watch all files within the monorepo\nconfig.watchFolders = [monorepoRoot];\n// 2. Let Metro know where to resolve packages and in what order\nconfig.resolver.nodeModulesPaths = [\n path.resolve(projectRoot, 'node_modules'),\n path.resolve(monorepoRoot, 'node_modules'),\n];\n// success-line-end\n\n// This helps support certain popular third-party libraries\n// such as Firebase that use the extension cjs.\nconfig.resolver.sourceExts.push(\"cjs\")\n\nmodule.exports = config;\n"})}),"\n",(0,s.jsx)(n.p,{children:"Awesome! We have our mobile app created \u2b50\ufe0f."}),"\n",(0,s.jsx)(n.h2,{id:"step-3-install-dependencies",children:"Step 3: Install dependencies"}),"\n",(0,s.jsx)(n.p,{children:"Let's make sure all of our dependencies are installed for the mobile app."}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["Run ",(0,s.jsx)(n.code,{children:"yarn"})," at the root of the project:"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"cd ..\nyarn install\n"})}),"\n",(0,s.jsx)(n.h2,{id:"step-4-add-a-shared-eslint-configuration-with-typescript",children:"Step 4: Add a shared ESLint configuration with TypeScript"}),"\n",(0,s.jsx)(n.p,{children:"Maintaining consistent code quality across TypeScript and JavaScript projects within a monorepo is crucial for a project's long-term success."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.strong,{children:"A good first step we recommend is to share a single ESLint configuration file between apps to ensure consistency and streamline the development process."})}),"\n",(0,s.jsx)(n.p,{children:"Let's create a shared utility for that purpose."}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Create a shared ESLint configuration package:"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Inside your monorepo, create a new package for your shared ESLint configuration."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"mkdir packages/eslint-config\ncd packages/eslint-config\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"2",children:["\n",(0,s.jsx)(n.li,{children:"Initialize the package:"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Initialize the package with a ",(0,s.jsx)(n.code,{children:"package.json"})," file."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"yarn init -y\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"3",children:["\n",(0,s.jsx)(n.li,{children:"Install ESLint and TypeScript dependencies:"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Install ESLint, TypeScript, and any shared plugins or configurations that you want to use across the apps. We recommend the follow:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-plugin-react-native eslint-plugin-reactotron eslint-config-standard eslint-config-prettier --dev\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"4",children:["\n",(0,s.jsxs)(n.li,{children:["Create the ",(0,s.jsx)(n.code,{children:"tsconfig.json"})," file:"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"packages/eslint-config/tsconfig.json"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'// success-line-start\n{\n "compilerOptions": {\n "module": "commonjs",\n "target": "es6",\n "lib": ["es6", "dom"],\n "jsx": "react",\n "strict": true,\n "esModuleInterop": true,\n "skipLibCheck": true\n }\n }\n // success-line-end\n'})}),"\n",(0,s.jsxs)(n.ol,{start:"5",children:["\n",(0,s.jsx)(n.li,{children:"Create the shared ESLint configuration file:"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Create an ",(0,s.jsx)(n.code,{children:"index.ts"})," file in the root of your ",(0,s.jsx)(n.code,{children:"eslint-config"})," package."]}),"\n",(0,s.jsx)(n.p,{children:"For this guide we will reuse Ignite\u2019s boilerplate ESLint configuration and then replace the original configuration with it."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"packages/eslint-config/index.ts"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'module.exports = {\n root: true,\n parser: "@typescript-eslint/parser",\n extends: [\n "plugin:@typescript-eslint/recommended",\n "plugin:react/recommended",\n "plugin:react-native/all",\n "standard",\n "prettier",\n ],\n plugins: [\n "@typescript-eslint",\n "react",\n "react-native",\n "reactotron",\n ],\n parserOptions: {\n ecmaFeatures: {\n jsx: true,\n },\n },\n settings: {\n react: {\n pragma: "React",\n version: "detect",\n },\n },\n globals: {\n __DEV__: false,\n jasmine: false,\n beforeAll: false,\n afterAll: false,\n beforeEach: false,\n afterEach: false,\n test: false,\n expect: false,\n describe: false,\n jest: false,\n it: false,\n },\n rules: {\n "@typescript-eslint/ban-ts-ignore": 0,\n "@typescript-eslint/ban-ts-comment": 0,\n "@typescript-eslint/explicit-function-return-type": 0,\n "@typescript-eslint/explicit-member-accessibility": 0,\n "@typescript-eslint/explicit-module-boundary-types": 0,\n "@typescript-eslint/indent": 0,\n "@typescript-eslint/member-delimiter-style": 0,\n "@typescript-eslint/no-empty-interface": 0,\n "@typescript-eslint/no-explicit-any": 0,\n "@typescript-eslint/no-object-literal-type-assertion": 0,\n "@typescript-eslint/no-var-requires": 0,\n "@typescript-eslint/no-unused-vars": [\n "error",\n {\n argsIgnorePattern: "^_",\n varsIgnorePattern: "^_",\n },\n ],\n "comma-dangle": 0,\n "multiline-ternary": 0,\n "no-undef": 0,\n "no-unused-vars": 0,\n "no-use-before-define": 0,\n "no-global-assign": 0,\n "quotes": 0,\n "react-native/no-raw-text": 0,\n "react/no-unescaped-entities": 0,\n "react/prop-types": 0,\n "space-before-function-paren": 0,\n "reactotron/no-tron-in-production": "error",\n },\n}\n// success-line-end\n'})}),"\n",(0,s.jsxs)(n.p,{children:["This configuration (originally sourced from ",(0,s.jsx)(n.a,{href:"https://github.com/infinitered/ignite",children:"Ignite"}),") will provide a strong foundation for TypeScript, React and React Native projects. You can always refine the rules later to align with the specific requirements of your project."]}),"\n",(0,s.jsxs)(n.ol,{start:"5",children:["\n",(0,s.jsx)(n.li,{children:"Compile the TypeScript configuration:"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"npx tsc\n"})}),"\n",(0,s.jsxs)(n.p,{children:["This will generate a ",(0,s.jsx)(n.code,{children:"index.js"})," file from your ",(0,s.jsx)(n.code,{children:"index.ts"})," file."]}),"\n",(0,s.jsx)(n.h2,{id:"step-6-use-the-shared-eslint-configuration-in-the-mobile-app",children:"Step 6: Use the shared ESLint configuration in the mobile app"}),"\n",(0,s.jsx)(n.p,{children:"Now we'll use the utility we just made and add it to the React Native app. Let\u2019s get started!"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Navigate to the mobile app:"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"cd ..\ncd ..\ncd apps/mobile\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"2",children:["\n",(0,s.jsxs)(n.li,{children:["Add the ESLint shared package to the ",(0,s.jsx)(n.code,{children:"package.json"})," file:"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"apps/mobile/package.json"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'"eslint": "8.17.0",\n// success-line\n "eslint-config": "workspace:^",\n "eslint-config-prettier": "8.5.0",\n'})}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["This guide mainly focuses on a private monorepo, but let\u2019s also talk about publishing packages publicly. If your monorepo includes packages meant for public release, avoid using ",(0,s.jsx)(n.code,{children:"workspace:^"})," for dependencies. Instead, set specific package versions to make sure everything works as expected. To handle versioning and publishing for multiple packages, we recommend trying out ",(0,s.jsx)(n.a,{href:"https://github.com/changesets/changesets",children:"changesets"})," \u2014 it makes the process much easier!"]})}),"\n",(0,s.jsxs)(n.ol,{start:"3",children:["\n",(0,s.jsxs)(n.li,{children:["Replace the shared ESLint configuration in ",(0,s.jsx)(n.code,{children:"package.json"}),":"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"apps/mobile/package.json"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'// error-line-start\n"eslintConfig": {\n "root": true,\n "parser": "@typescript-eslint/parser",\n "extends": [\n "plugin:@typescript-eslint/recommended",\n "plugin:react/recommended",\n "plugin:react-native/all",\n "standard",\n "prettier"\n ],\n "plugins": [\n "@typescript-eslint",\n "react",\n "react-native",\n "reactotron"\n ],\n "parserOptions": {\n "ecmaFeatures": {\n "jsx": true\n }\n },\n "settings": {\n "react": {\n "pragma": "React",\n "version": "detect"\n }\n },\n "globals": {\n "__DEV__": false,\n "jasmine": false,\n "beforeAll": false,\n "afterAll": false,\n "beforeEach": false,\n "afterEach": false,\n "test": false,\n "expect": false,\n "describe": false,\n "jest": false,\n "it": false\n },\n "rules": {\n "@typescript-eslint/ban-ts-ignore": 0,\n "@typescript-eslint/ban-ts-comment": 0,\n "@typescript-eslint/explicit-function-return-type": 0,\n "@typescript-eslint/explicit-member-accessibility": 0,\n "@typescript-eslint/explicit-module-boundary-types": 0,\n "@typescript-eslint/indent": 0,\n "@typescript-eslint/member-delimiter-style": 0,\n "@typescript-eslint/no-empty-interface": 0,\n "@typescript-eslint/no-explicit-any": 0,\n "@typescript-eslint/no-object-literal-type-assertion": 0,\n "@typescript-eslint/no-var-requires": 0,\n "@typescript-eslint/no-unused-vars": [\n "error",\n {\n "argsIgnorePattern": "^_",\n "varsIgnorePattern": "^_"\n }\n ],\n "comma-dangle": 0,\n "multiline-ternary": 0,\n "no-undef": 0,\n "no-unused-vars": 0,\n "no-use-before-define": 0,\n "no-global-assign": 0,\n "quotes": 0,\n "react-native/no-raw-text": 0,\n "react/no-unescaped-entities": 0,\n "react/prop-types": 0,\n "space-before-function-paren": 0,\n "reactotron/no-tron-in-production": "error"\n }\n }\n// error-line-end\n// success-line-start\n"eslintConfig": {\n extends: ["@monorepo-example/eslint-config"],\n}\n// success-line-end\n'})}),"\n",(0,s.jsx)(n.admonition,{type:"warning",children:(0,s.jsxs)(n.p,{children:["In this guide, we use ",(0,s.jsx)(n.code,{children:"@monorepo-example"})," as the placeholder name for the monorepo. Be sure to replace it with your actual monorepo name if it\u2019s different."]})}),"\n",(0,s.jsx)(n.p,{children:"By completing this step, you now have an app (and maybe more in the future) that benefits from a shared ESLint configuration. Great work!"}),"\n",(0,s.jsx)(n.h2,{id:"step-7-create-a-shared-ui-components-package",children:"Step 7: Create a shared UI components package"}),"\n",(0,s.jsx)(n.p,{children:"Now that we are familiar with the creation of a shared package, let's create another one."}),"\n",(0,s.jsx)(n.p,{children:"As we mentioned earlier, a common need in projects is sharing UI components across multiple apps. In this step, we\u2019ll create a shared UI package featuring a Badge component. A Badge is a simple yet versatile element often used to show small pieces of information, like notifications, statuses, or labels."}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Navigate to the packages folder:"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"cd ..\ncd ..\ncd packages\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"2",children:["\n",(0,s.jsx)(n.li,{children:"Create the package directory:"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"mkdir ui-components\ncd ui-components\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"3",children:["\n",(0,s.jsx)(n.li,{children:"Initialize the package:"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Initialize the package with a ",(0,s.jsx)(n.code,{children:"package.json"})," file."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"yarn init -y\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"4",children:["\n",(0,s.jsx)(n.li,{children:"Install dependencies:"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Install any necessary dependencies, such as React, React Native, and TypeScript, which will be used across both platforms."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"yarn add react react-native typescript --peer\nyarn add @types/react @types/react-native --dev\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"4",children:["\n",(0,s.jsxs)(n.li,{children:["Create the ",(0,s.jsx)(n.code,{children:"tsconfig.json"})," file:"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"packages/ui-components/tsconfig.json"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'// success-line-start\n{\n "compilerOptions": {\n "target": "es5",\n "lib": ["dom", "es2017"],\n "module": "commonjs",\n "jsx": "react",\n "declaration": true,\n "outDir": "dist",\n "strict": true,\n "esModuleInterop": true,\n "skipLibCheck": true\n },\n "include": ["src"],\n "exclude": ["node_modules"]\n}\n // success-line-end\n'})}),"\n",(0,s.jsxs)(n.ol,{start:"5",children:["\n",(0,s.jsx)(n.li,{children:"Now let's create the badge component:"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Inside the ",(0,s.jsx)(n.code,{children:"packages/ui-components"})," directory, create a ",(0,s.jsx)(n.code,{children:"src"})," folder and add your Badge component."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"mkdir src\ntouch src/Badge.tsx\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"6",children:["\n",(0,s.jsx)(n.li,{children:"Build the badge component:"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"packages/ui-components/src/Badge.tsx"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'// success-line-start\nimport React, { FC } from "react"\nimport { View, Text, StyleSheet, ViewStyle, TextStyle } from "react-native"\n\ninterface BadgeProps {\n label: string\n color?: string\n backgroundColor?: string\n style?: ViewStyle\n textStyle?: TextStyle\n}\n\nexport const Badge: FC = ({ label, color = "white", backgroundColor = "red", style, textStyle }) => {\n return (\n \n {label}\n \n )\n}\n\nconst styles = StyleSheet.create({\n badge: {\n paddingHorizontal: 8,\n paddingVertical: 4,\n borderRadius: 12,\n alignSelf: "flex-start",\n } satisfies ViewStyle,\n text: {\n fontSize: 12,\n fontWeight: "bold",\n } satisfies TextStyle,\n})\n// success-line-end\n'})}),"\n",(0,s.jsxs)(n.p,{children:["A ",(0,s.jsx)(n.code,{children:"Badge"})," component, as defined above, is a simple UI element designed to display a label with customizable colors. This makes it versatile and useful in various parts of your app, like showing notification counts, statuses, or category labels."]}),"\n",(0,s.jsxs)(n.ol,{start:"7",children:["\n",(0,s.jsx)(n.li,{children:"Export the badge component:"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Ensure that your component is exported in the package's main entry file."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"packages/ui-components/src/index.ts"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-ts",children:'// success-line-start\nexport * from "./Badge"\n// success-line-end\n'})}),"\n",(0,s.jsxs)(n.ol,{start:"8",children:["\n",(0,s.jsx)(n.li,{children:"Compile the package:"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Compile your TypeScript code to ensure it's ready for consumption by other packages."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"npx tsc\n"})}),"\n",(0,s.jsx)(n.p,{children:"Awesome! We have now a second package within our monorepo and a UI component we can share across apps. Onward!"}),"\n",(0,s.jsx)(n.h2,{id:"step-8-use-the-shared-ui-package-in-the-mobile-app",children:"Step 8: Use the shared UI package in the mobile app"}),"\n",(0,s.jsx)(n.p,{children:"To finish integrating our shared UI package, we also need to include it in the mobile app."}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Navigate now to the mobile app:"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"cd ..\ncd ..\ncd apps/mobile\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"2",children:["\n",(0,s.jsxs)(n.li,{children:["Add the shared UI package to the ",(0,s.jsx)(n.code,{children:"package.json"})," file:"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"apps/mobile/package.json"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:' "react-native-screens": "3.31.1",\n // error-line\n "react-native-web": "~0.19.6"\n // success-line-start\n "react-native-web": "~0.19.6",\n "ui-components": "workspace:^"\n // success-line-end\n },\n'})}),"\n",(0,s.jsxs)(n.ol,{start:"3",children:["\n",(0,s.jsx)(n.li,{children:"Add the Badge component to the UI"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Now, let\u2019s add the ",(0,s.jsx)(n.code,{children:"Badge"})," component to the app! For this example, we\u2019ll place it on the login screen\u2014right below the heading and above the form fields\u2014to show the number of login attempts if they go over a certain limit."]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"apps/mobile/apps/screens/LoginScreen.tsx"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-tsx",children:'import { AppStackScreenProps } from "../navigators"\nimport { colors, spacing } from "../theme"\n// success-line\nimport { Badge } from "ui-components"\n\n...\n\n\n// success-line-start\n{attemptsCount > 0 && (\n 2 ? "red" : "blue"}\n />\n)}\n// success-line-end\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Great work! Now the mobile app is using the ",(0,s.jsx)(n.code,{children:"Badge"})," component from the shared UI library."]}),"\n",(0,s.jsx)(n.h2,{id:"step-9-run-mobile-app-to-make-sure-logic-was-added",children:"Step 9: Run mobile app to make sure logic was added"}),"\n",(0,s.jsx)(n.p,{children:"Alright, we\u2019re almost done! The final step is to make sure everything is set up correctly. Let\u2019s do this by running the mobile app."}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"Navigate to the root of the project:"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"cd ..\ncd ..\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"2",children:["\n",(0,s.jsx)(n.li,{children:"Make sure dependencies are installed:"}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"yarn\n"})}),"\n",(0,s.jsxs)(n.ol,{start:"3",children:["\n",(0,s.jsxs)(n.li,{children:["Run the React Native app (make sure you have your ",(0,s.jsx)(n.a,{href:"https://reactnative.dev/docs/set-up-your-environment",children:"environment setup"}),"):"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"For iOS:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"cd apps/mobile\nyarn ios\n"})}),"\n",(0,s.jsx)(n.p,{children:"For Android:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-shell",children:"cd apps/mobile\nyarn android\n"})}),"\n",(0,s.jsxs)(n.p,{children:["You should now see the login screen with a ",(0,s.jsx)(n.code,{children:"Badge"})," displayed between the heading and the form fields. Amazing! \ud83c\udf89"]}),"\n",(0,s.jsx)(n.h2,{id:"step-10-add-yarn-global-scripts-optional",children:"Step 10: Add Yarn global scripts (optional)"}),"\n",(0,s.jsx)(n.p,{children:"Just when we thought we were done! If you're still with us, here's an extra step that can make your workflow even smoother."}),"\n",(0,s.jsx)(n.p,{children:"One of the great features of Yarn Workspaces is the ability to define and run scripts globally across all packages in your monorepo. This means you can handle tasks like testing, building, or linting right from the root of your project\u2014no need to dive into individual packages."}),"\n",(0,s.jsx)(n.p,{children:"In this optional section, we\u2019ll show you how to set up and use global scripts with Yarn. To start, let's add a global script for the mobile app to run both iOS and Android projects."}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:["Add a global script to the mobile app ",(0,s.jsx)(n.code,{children:"package.json"})," file:"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.code,{children:"apps/mobile/package.json"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:' "scripts": {\n ...\n "serve:web": "npx server dist",\n // error-line\n "prebuild:clean": "npx expo prebuild --clean"\n // success-line-start\n "prebuild:clean": "npx expo prebuild --clean",\n "mobile:ios" : "yarn workspace mobile ios",\n "mobile:android" : "yarn workspace mobile android"\n // success-line-end\n },\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Even though this script is locally defined within the app's ",(0,s.jsx)(n.code,{children:"package.json"})," file, it will available everywhere within the monorepo by running ",(0,s.jsx)(n.code,{children:"yarn mobile:ios"})," or ",(0,s.jsx)(n.code,{children:"yarn mobile:android"}),". Very neat!"]}),"\n",(0,s.jsx)(n.admonition,{type:"info",children:(0,s.jsxs)(n.p,{children:["For more information on Yarn's global scripts, check ",(0,s.jsx)(n.a,{href:"https://yarnpkg.com/features/workspaces#global-scripts",children:"this link"}),"."]})}),"\n",(0,s.jsx)(n.h2,{id:"conclusion",children:"Conclusion"}),"\n",(0,s.jsxs)(n.p,{children:["\ud83c\udf89 Congratulations on reaching the end of this guide! You\u2019ve set up a powerful monorepo with shared utilities, learned how to integrate them into a React Native app created using ",(0,s.jsx)(n.a,{href:"https://github.com/infinitered/ignite",children:"Ignite"}),", and even explored optional enhancements to streamline your workflow."]}),"\n",(0,s.jsx)(n.p,{children:"We hope this guide has been helpful and gives you more confidence when working with a monorepo setup!"}),"\n",(0,s.jsx)(n.p,{children:"For more information, you can check the following resources:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"/docs/recipes/MonoreposOverview",children:"Choosing the right monorepo strategy for your project"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.a,{href:"https://docs.expo.dev/guides/monorepos/",children:"Expo: Work with monorepos"})}),"\n"]})]})}function p(e={}){const{wrapper:n}={...(0,i.M)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},2172:(e,n,t)=>{t.d(n,{I:()=>a,M:()=>o});var s=t(1504);const i={},r=s.createContext(i);function o(e){const n=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:o(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/main.0a826d26.js b/assets/js/main.0a826d26.js
new file mode 100644
index 00000000..d4b823e1
--- /dev/null
+++ b/assets/js/main.0a826d26.js
@@ -0,0 +1,2 @@
+/*! For license information please see main.0a826d26.js.LICENSE.txt */
+(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[1590],{5052:(e,n,t)=>{"use strict";t.d(n,{I:()=>o});var r=t(1504);function o(){return r.createElement("svg",{width:"20",height:"20",className:"DocSearch-Search-Icon",viewBox:"0 0 20 20"},r.createElement("path",{d:"M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z",stroke:"currentColor",fill:"none",fillRule:"evenodd",strokeLinecap:"round",strokeLinejoin:"round"}))}},628:(e,n,t)=>{"use strict";t.d(n,{c:()=>p});t(1504);var r=t(8852),o=t.n(r),a=t(4504);const s={"03224537":[()=>t.e(5284).then(t.bind(t,4024)),"@site/docs/recipes/DetoxIntro.md",4024],"04c3832a":[()=>t.e(7920).then(t.t.bind(t,780,19)),"~docs/default/tag-docs-tags-icons-a4b.json",780],"0825c398":[()=>t.e(5524).then(t.t.bind(t,5708,19)),"~docs/default/tag-docs-tags-backend-b17.json",5708],"08a54ed9":[()=>t.e(5156).then(t.t.bind(t,1128,19)),"~docs/default/tag-docs-tags-expo-updates-8e6.json",1128],"0e384e19":[()=>t.e(6328).then(t.bind(t,9404)),"@site/docs/intro.md",9404],"145425bc":[()=>t.e(16).then(t.bind(t,9856)),"@site/docs/recipes/AccessibilityFontSizes.md",9856],17896441:[()=>Promise.all([t.e(2176),t.e(7656),t.e(8908),t.e(6752)]).then(t.bind(t,8552)),"@theme/DocItem",8552],"18d888f3":[()=>t.e(5640).then(t.t.bind(t,9584,19)),"~docs/default/tag-docs-tags-state-management-fd9.json",9584],"199ff765":[()=>t.e(8368).then(t.t.bind(t,2520,19)),"~docs/default/tag-docs-tags-offline-support-d09.json",2520],"19d620af":[()=>t.e(3966).then(t.t.bind(t,5568,19)),"~docs/default/tag-docs-tags-ui-22c.json",5568],"1a4e3797":[()=>Promise.all([t.e(2176),t.e(9648)]).then(t.bind(t,3416)),"@theme/SearchPage",3416],"1ab29606":[()=>t.e(6928).then(t.t.bind(t,7704,19)),"~docs/default/tag-docs-tags-apisauce-8de.json",7704],"1c9ea255":[()=>t.e(8636).then(t.bind(t,620)),"@site/docs/recipes/TypeScriptBaseURL.md",620],"1df93b7f":[()=>Promise.all([t.e(2176),t.e(2096),t.e(8552)]).then(t.bind(t,9184)),"@site/src/pages/index.tsx",9184],"1fdec661":[()=>t.e(1904).then(t.t.bind(t,318,19)),"~docs/default/tag-docs-tags-cache-b57.json",318],"215698ba":[()=>Promise.all([t.e(2176),t.e(7656),t.e(492)]).then(t.bind(t,8392)),"@site/docs/recipes/Theming-StyledComponents.mdx",8392],"24a07a83":[()=>t.e(4424).then(t.t.bind(t,4136,19)),"~docs/default/tag-docs-tags-generator-e43.json",4136],"24c3776a":[()=>t.e(9828).then(t.bind(t,176)),"@site/docs/recipes/DistributingAuthTokenToAPI.md",176],"2fbc58fd":[()=>t.e(3132).then(t.bind(t,2736)),"@site/docs/recipes/MonoreposOverview.md",2736],"30b0babe":[()=>t.e(2816).then(t.t.bind(t,8464,19)),"~docs/default/tag-docs-tags-emotion-js-844.json",8464],"3192f89a":[()=>t.e(4992).then(t.t.bind(t,590,19)),"/home/runner/work/ignite-cookbook/ignite-cookbook/.docusaurus/docusaurus-plugin-content-pages/default/plugin-route-context-module-100.json",590],"31ac2bc7":[()=>t.e(9632).then(t.t.bind(t,9010,19)),"~docs/default/tag-docs-tags-cng-585.json",9010],"33f24359":[()=>t.e(222).then(t.t.bind(t,1592,19)),"~docs/default/tag-docs-tags-eas-update-bce.json",1592],"3663082a":[()=>t.e(5592).then(t.t.bind(t,6944,19)),"~docs/default/tag-docs-tags-data-synchronization-126.json",6944],"3720c009":[()=>Promise.all([t.e(2176),t.e(4492)]).then(t.bind(t,2808)),"@theme/DocTagsListPage",2808],"37e9da98":[()=>t.e(3800).then(t.t.bind(t,4496,19)),"~docs/default/tag-docs-tags-styled-components-6ef.json",4496],"37f4af6f":[()=>t.e(6922).then(t.t.bind(t,4816,19)),"~docs/default/tag-docs-tags-ignite-b0e.json",4816],"3bb7a4af":[()=>t.e(2008).then(t.t.bind(t,7264,19)),"~docs/default/tag-docs-tags-i-os-d44.json",7264],"4699e3bd":[()=>t.e(8440).then(t.t.bind(t,680,19)),"~docs/default/tag-docs-tags-vector-icons-80f.json",680],"47a03c7f":[()=>t.e(3336).then(t.t.bind(t,7536,19)),"~docs/default/tag-docs-tags-testing-194.json",7536],"47c232e1":[()=>t.e(784).then(t.t.bind(t,6930,19)),"~docs/default/tag-docs-tags-supabase-1d9.json",6930],"4890b90f":[()=>t.e(6728).then(t.bind(t,2292)),"@site/docs/communityRecipes/index.md",2292],"4a4fb967":[()=>t.e(6756).then(t.t.bind(t,9056,19)),"~docs/default/tag-docs-tags-accessibility-83c.json",9056],"4e09609f":[()=>t.e(3128).then(t.bind(t,8220)),"@site/docs/recipes/SampleYAMLCircleCI.md",8220],"51658ad1":[()=>t.e(9576).then(t.t.bind(t,5752,19)),"~docs/default/tag-docs-tags-intro-ce4.json",5752],51892623:[()=>t.e(2328).then(t.t.bind(t,5731,19)),"~docs/default/tag-docs-tags-i-18-n-fae.json",5731],"51e76fb5":[()=>t.e(5064).then(t.bind(t,4488)),"@site/docs/recipes/MaestroSetup.md",4488],"51ea30c5":[()=>t.e(2324).then(t.t.bind(t,162,19)),"~docs/default/tag-docs-tags-mob-x-748.json",162],"52d269c5":[()=>t.e(5292).then(t.bind(t,9220)),"@site/docs/recipes/Authentication.md",9220],"54a9c7e8":[()=>t.e(8576).then(t.bind(t,2716)),"@site/docs/recipes/PrepForEASBuild.md",2716],"55960ee5":[()=>t.e(4296).then(t.t.bind(t,2416,19)),"~docs/default/tags-list-current-prop-15a.json",2416],"569bff92":[()=>t.e(5320).then(t.t.bind(t,2968,19)),"~docs/default/tag-docs-tags-hardware-92c.json",2968],"5e95c892":[()=>t.e(4304).then(t.bind(t,3564)),"@theme/DocsRoot",3564],"5e9f5e1a":[()=>Promise.resolve().then(t.bind(t,7768)),"@generated/docusaurus.config",7768],"5fd7ef2e":[()=>t.e(656).then(t.bind(t,9044)),"@site/docs/recipes/LocalFirstDataWithPowerSync.md",9044],63181745:[()=>t.e(5548).then(t.bind(t,9108)),"@site/docs/recipes/UsingScreenReaders.md",9108],"6558e733":[()=>t.e(7338).then(t.t.bind(t,5440,19)),"~docs/default/tag-docs-tags-session-c44.json",5440],"657027a7":[()=>t.e(3180).then(t.bind(t,8232)),"@site/docs/recipes/MigratingToMMKV.md",8232],"6728e797":[()=>t.e(8616).then(t.t.bind(t,6824,19)),"~docs/default/tag-docs-tags-android-593.json",6824],"69dd30cd":[()=>t.e(4e3).then(t.t.bind(t,7672,19)),"~docs/default/tag-docs-tags-prettier-367.json",7672],"6c727604":[()=>t.e(7620).then(t.t.bind(t,7508,19)),"~docs/default/tag-docs-tags-login-6c7.json",7508],"6fd287af":[()=>t.e(4264).then(t.bind(t,9700)),"@site/docs/recipes/UpdatingIgnite.md",9700],"72dfd944":[()=>t.e(40).then(t.t.bind(t,8466,19)),"~docs/default/tag-docs-tags-text-field-8cc.json",8466],76759531:[()=>Promise.all([t.e(2176),t.e(7656),t.e(1016)]).then(t.bind(t,500)),"@site/docs/recipes/Theming-Emotion.mdx",500],"78a0b2f7":[()=>t.e(4224).then(t.bind(t,4068)),"@site/docs/recipes/SwitchBetweenExpoGoCNG.md",4068],"7acb6f50":[()=>t.e(56).then(t.t.bind(t,9200,19)),"~docs/default/tag-docs-tags-yarn-9bd.json",9200],"7b45617e":[()=>t.e(8436).then(t.bind(t,9368)),"@site/docs/recipes/GeneratorComponentTests.md",9368],"7f8cce85":[()=>t.e(4212).then(t.t.bind(t,2536,19)),"~docs/default/tag-docs-tags-power-sync-570.json",2536],82139467:[()=>t.e(7820).then(t.t.bind(t,5688,19)),"~docs/default/tag-docs-tags-react-native-vision-camera-af5.json",5688],"8b0d950b":[()=>t.e(3960).then(t.t.bind(t,4832,19)),"~docs/default/tag-docs-tags-dependencies-d4f.json",4832],"935f2afb":[()=>t.e(5696).then(t.t.bind(t,5988,19)),"~docs/default/version-current-metadata-prop-751.json",5988],"94ee064e":[()=>t.e(7300).then(t.t.bind(t,5683,19)),"~docs/default/tag-docs-tags-react-navigation-23c.json",5683],"954f316d":[()=>t.e(6044).then(t.t.bind(t,9272,19)),"~docs/default/tag-docs-tags-zustand-18e.json",9272],"985027d8":[()=>t.e(2160).then(t.t.bind(t,6843,19)),"~docs/default/tag-docs-tags-archive-6cf.json",6843],"99d16955":[()=>t.e(622).then(t.t.bind(t,5004,19)),"~docs/default/tag-docs-tags-eas-228.json",5004],"9b01ede9":[()=>t.e(3480).then(t.bind(t,2436)),"@site/docs/recipes/MigratingToI18Next.md",2436],"9b650fc1":[()=>t.e(9208).then(t.t.bind(t,4514,19)),"~docs/default/tag-docs-tags-colors-792.json",4514],"9dc0f37b":[()=>t.e(6704).then(t.t.bind(t,6154,19)),"~docs/default/tag-docs-tags-section-list-198.json",6154],"9ec24567":[()=>Promise.all([t.e(2176),t.e(7656),t.e(7136)]).then(t.bind(t,900)),"@site/docs/recipes/Theming-Unistyles.mdx",900],"9fc76e3d":[()=>t.e(900).then(t.bind(t,8468)),"@site/docs/recipes/EASUpdate.md",8468],a0d6a633:[()=>t.e(7732).then(t.t.bind(t,1284,19)),"~docs/default/tag-docs-tags-expo-dev-client-4bf.json",1284],a2f5d017:[()=>t.e(916).then(t.bind(t,3884)),"@site/docs/recipes/Redux.md",3884],a7bd4aaa:[()=>t.e(6500).then(t.bind(t,2e3)),"@theme/DocVersionRoot",2e3],a8646ade:[()=>t.e(3008).then(t.t.bind(t,1492,19)),"~docs/default/tag-docs-tags-mmkv-046.json",1492],a8f9d519:[()=>t.e(6216).then(t.t.bind(t,606,19)),"~docs/default/tag-docs-tags-scroll-to-8d4.json",606],a94703ab:[()=>Promise.all([t.e(2176),t.e(4666)]).then(t.bind(t,996)),"@theme/DocRoot",996],a951c726:[()=>t.e(8200).then(t.t.bind(t,7976,19)),"~docs/default/tag-docs-tags-ci-cd-ed9.json",7976],abaad534:[()=>t.e(2464).then(t.t.bind(t,4684,19)),"/home/runner/work/ignite-cookbook/ignite-cookbook/.docusaurus/docusaurus-theme-search-algolia/default/plugin-route-context-module-100.json",4684],abfb2977:[()=>t.e(2044).then(t.t.bind(t,6278,19)),"~docs/default/tag-docs-tags-database-1c7.json",6278],ad7b1610:[()=>t.e(3980).then(t.t.bind(t,6248,19)),"~docs/default/tag-docs-tags-community-0f9.json",6248],b16fadc1:[()=>t.e(1176).then(t.t.bind(t,9390,19)),"~docs/default/tag-docs-tags-monorepo-79d.json",9390],b33180cb:[()=>t.e(1528).then(t.bind(t,7160)),"@site/docs/archive/PristineExpoProject.md",7160],b3d1732c:[()=>t.e(95).then(t.t.bind(t,2464,19)),"~docs/default/tag-docs-tags-babel-ecc.json",2464],b3fb1bb5:[()=>t.e(4955).then(t.bind(t,3980)),"@site/docs/communityRecipes/CustomVectorIcons.md",3980],b6b5631c:[()=>t.e(9960).then(t.t.bind(t,9148,19)),"~docs/default/tag-docs-tags-theming-09d.json",9148],b747e1af:[()=>t.e(7556).then(t.t.bind(t,784,19)),"~docs/default/tag-docs-tags-signup-d75.json",784],b832b2ff:[()=>t.e(4822).then(t.t.bind(t,2962,19)),"~docs/default/tag-docs-tags-imports-63f.json",2962],b8c37621:[()=>t.e(2372).then(t.t.bind(t,4328,19)),"~docs/default/tag-docs-tags-guide-a32.json",4328],bbaf8084:[()=>t.e(4428).then(t.t.bind(t,5328,19)),"~docs/default/tag-docs-tags-darkmode-3d7.json",5328],bc1a59c9:[()=>t.e(5819).then(t.t.bind(t,5256,19)),"~docs/default/tag-docs-tags-reactotron-574.json",5256],c3e2f4d4:[()=>t.e(1264).then(t.bind(t,5800)),"@site/docs/recipes/UnrenderedItemInScrollView.md",5800],c47fa949:[()=>t.e(5032).then(t.bind(t,8332)),"@site/docs/recipes/ExpoRouter.md",8332],c5abe9fe:[()=>t.e(9607).then(t.t.bind(t,1344,19)),"~docs/default/tag-docs-tags-vision-camera-2ad.json",1344],c5ca3bf3:[()=>t.e(8872).then(t.t.bind(t,872,19)),"~docs/default/tag-docs-tags-expo-router-e61.json",872],c75b21fd:[()=>t.e(1412).then(t.bind(t,6188)),"@site/docs/recipes/RemoveMobxStateTree.md",6188],cc1d3934:[()=>t.e(8500).then(t.bind(t,5220)),"@site/docs/recipes/ReactNativeVisionCamera.md",5220],ccf3150e:[()=>t.e(7600).then(t.t.bind(t,3030,19)),"~docs/default/tag-docs-tags-ui-required-device-capabilities-7ff.json",3030],ce17b301:[()=>t.e(1388).then(t.t.bind(t,1540,19)),"~docs/default/tag-docs-tags-expo-4de.json",1540],cecd52bc:[()=>t.e(8).then(t.bind(t,6040)),"@site/docs/recipes/RequiringHardwareFeaturesWithExpo.md",6040],cf59a740:[()=>t.e(8780).then(t.t.bind(t,411,19)),"~docs/default/tag-docs-tags-react-native-b11.json",411],d01c4de2:[()=>t.e(7224).then(t.t.bind(t,488,19)),"~docs/default/tag-docs-tags-async-storage-da0.json",488],d0e08e4a:[()=>t.e(9016).then(t.bind(t,2980)),"@site/docs/recipes/PatchingBuildingAndroid.md",2980],d63d2b89:[()=>Promise.all([t.e(6432),t.e(5276)]).then(t.bind(t,6960)),"@site/docs/recipes/SelectFieldWithBottomSheet.mdx",6960],d699663a:[()=>t.e(1784).then(t.t.bind(t,5820,19)),"~docs/default/tag-docs-tags-uses-feature-818.json",5820],d6ab422f:[()=>t.e(8762).then(t.bind(t,7748)),"@site/docs/recipes/EnvironmentVariables.md",7748],d7af48b9:[()=>t.e(6264).then(t.bind(t,2508)),"@site/docs/recipes/EnforcingImportOrder.md",2508],da9277bc:[()=>t.e(5856).then(t.bind(t,1188)),"@site/docs/recipes/UpdatingDependencies.md",1188],dce6faa4:[()=>t.e(2596).then(t.t.bind(t,5892,19)),"~docs/default/tag-docs-tags-select-field-e86.json",5892],dd3340a6:[()=>t.e(7572).then(t.bind(t,8508)),"@site/docs/archive/index.md",8508],dec1aed8:[()=>t.e(9144).then(t.t.bind(t,4986,19)),"~docs/default/tag-docs-tags-maestro-a0f.json",4986],df203c0f:[()=>Promise.all([t.e(2176),t.e(3400)]).then(t.bind(t,8614)),"@theme/DocTagDocListPage",8614],e0854532:[()=>t.e(2288).then(t.t.bind(t,2456,19)),"~docs/default/tag-docs-tags-flat-list-bca.json",2456],e1b6b0a8:[()=>t.e(328).then(t.t.bind(t,2984,19)),"~docs/default/tag-docs-tags-debug-69b.json",2984],e2041b9b:[()=>t.e(7164).then(t.t.bind(t,9680,19)),"~docs/default/tag-docs-tags-redux-49b.json",9680],e2d058df:[()=>t.e(7706).then(t.t.bind(t,4196,19)),"~docs/default/tag-docs-tags-font-awesome-056.json",4196],e33e793e:[()=>t.e(5708).then(t.bind(t,3356)),"@site/docs/recipes/Zustand.md",3356],e7928ab4:[()=>t.e(6460).then(t.t.bind(t,2080,19)),"/home/runner/work/ignite-cookbook/ignite-cookbook/.docusaurus/docusaurus-plugin-content-docs/default/plugin-route-context-module-100.json",2080],e965bea7:[()=>t.e(5576).then(t.bind(t,4492)),"@site/docs/recipes/ApolloClientCache.md",4492],ea9dae2d:[()=>t.e(8568).then(t.bind(t,3860)),"@site/docs/recipes/SettingUpYarnMonorepo.md",3860],eba20459:[()=>t.e(2928).then(t.t.bind(t,5736,19)),"~docs/default/tag-docs-tags-apollo-client-0ba.json",5736],ecce3b64:[()=>t.e(3324).then(t.t.bind(t,4640,19)),"~docs/default/tag-docs-tags-authentication-9a3.json",4640],ee0b98b5:[()=>t.e(6568).then(t.t.bind(t,1962,19)),"~docs/default/tag-docs-tags-unistyles-958.json",1962],f50f3884:[()=>t.e(1236).then(t.t.bind(t,9720,19)),"~docs/default/tag-docs-tags-prebuild-64b.json",9720],f523b160:[()=>t.e(3504).then(t.bind(t,2232)),"@site/docs/recipes/CreatingGreateExperienceForScreenReaders.md",2232],fd1937a7:[()=>t.e(4116).then(t.t.bind(t,7472,19)),"~docs/default/tag-docs-tags-environment-variables-3be.json",7472],fe9b09bf:[()=>t.e(140).then(t.bind(t,1204)),"@site/docs/recipes/CircleCIRNSetup.md",1204],ff2c7cca:[()=>t.e(6252).then(t.t.bind(t,2248,19)),"~docs/default/tag-docs-tags-type-script-6e5.json",2248],ffe4833d:[()=>t.e(9380).then(t.t.bind(t,570,19)),"~docs/default/tag-docs-tags-custom-commands-621.json",570]};var i=t(7624);function l(e){let{error:n,retry:t,pastDelay:r}=e;return n?(0,i.jsxs)("div",{style:{textAlign:"center",color:"#fff",backgroundColor:"#fa383e",borderColor:"#fa383e",borderStyle:"solid",borderRadius:"0.25rem",borderWidth:"1px",boxSizing:"border-box",display:"block",padding:"1rem",flex:"0 0 50%",marginLeft:"25%",marginRight:"25%",marginTop:"5rem",maxWidth:"50%",width:"100%"},children:[(0,i.jsx)("p",{children:String(n)}),(0,i.jsx)("div",{children:(0,i.jsx)("button",{type:"button",onClick:t,children:"Retry"})})]}):r?(0,i.jsx)("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh"},children:(0,i.jsx)("svg",{id:"loader",style:{width:128,height:110,position:"absolute",top:"calc(100vh - 64%)"},viewBox:"0 0 45 45",xmlns:"http://www.w3.org/2000/svg",stroke:"#61dafb",children:(0,i.jsxs)("g",{fill:"none",fillRule:"evenodd",transform:"translate(1 1)",strokeWidth:"2",children:[(0,i.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,i.jsx)("animate",{attributeName:"r",begin:"1.5s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,i.jsx)("animate",{attributeName:"stroke-opacity",begin:"1.5s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,i.jsx)("animate",{attributeName:"stroke-width",begin:"1.5s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,i.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,i.jsx)("animate",{attributeName:"r",begin:"3s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,i.jsx)("animate",{attributeName:"stroke-opacity",begin:"3s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,i.jsx)("animate",{attributeName:"stroke-width",begin:"3s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,i.jsx)("circle",{cx:"22",cy:"22",r:"8",children:(0,i.jsx)("animate",{attributeName:"r",begin:"0s",dur:"1.5s",values:"6;1;2;3;4;5;6",calcMode:"linear",repeatCount:"indefinite"})})]})})}):null}var c=t(8120),u=t(5548);function d(e,n){if("*"===e)return o()({loading:l,loader:()=>t.e(4552).then(t.bind(t,4552)),modules:["@theme/NotFound"],webpack:()=>[4552],render(e,n){const t=e.default;return(0,i.jsx)(u.Y,{value:{plugin:{name:"native",id:"default"}},children:(0,i.jsx)(t,{...n})})}});const r=a[`${e}-${n}`],d={},p=[],f=[],m=(0,c.c)(r);return Object.entries(m).forEach((e=>{let[n,t]=e;const r=s[t];r&&(d[n]=r[0],p.push(r[1]),f.push(r[2]))})),o().Map({loading:l,loader:d,modules:p,webpack:()=>f,render(n,t){const o=JSON.parse(JSON.stringify(r));Object.entries(n).forEach((n=>{let[t,r]=n;const a=r.default;if(!a)throw new Error(`The page component at ${e} doesn't have a default export. This makes it impossible to render anything. Consider default-exporting a React component.`);"object"!=typeof a&&"function"!=typeof a||Object.keys(r).filter((e=>"default"!==e)).forEach((e=>{a[e]=r[e]}));let s=o;const i=t.split(".");i.slice(0,-1).forEach((e=>{s=s[e]})),s[i[i.length-1]]=a}));const a=o.__comp;delete o.__comp;const s=o.__context;return delete o.__context,(0,i.jsx)(u.Y,{value:s,children:(0,i.jsx)(a,{...o,...t})})}})}const p=[{path:"/search",component:d("/search","437"),exact:!0},{path:"/docs",component:d("/docs","10d"),routes:[{path:"/docs",component:d("/docs","9ea"),routes:[{path:"/docs/tags",component:d("/docs/tags","0cc"),exact:!0},{path:"/docs/tags/accessibility",component:d("/docs/tags/accessibility","8aa"),exact:!0},{path:"/docs/tags/android",component:d("/docs/tags/android","e3a"),exact:!0},{path:"/docs/tags/apisauce",component:d("/docs/tags/apisauce","143"),exact:!0},{path:"/docs/tags/apollo-client",component:d("/docs/tags/apollo-client","ee0"),exact:!0},{path:"/docs/tags/archive",component:d("/docs/tags/archive","51b"),exact:!0},{path:"/docs/tags/async-storage",component:d("/docs/tags/async-storage","afc"),exact:!0},{path:"/docs/tags/authentication",component:d("/docs/tags/authentication","587"),exact:!0},{path:"/docs/tags/babel",component:d("/docs/tags/babel","e90"),exact:!0},{path:"/docs/tags/backend",component:d("/docs/tags/backend","908"),exact:!0},{path:"/docs/tags/cache",component:d("/docs/tags/cache","283"),exact:!0},{path:"/docs/tags/ci-cd",component:d("/docs/tags/ci-cd","56c"),exact:!0},{path:"/docs/tags/cng",component:d("/docs/tags/cng","581"),exact:!0},{path:"/docs/tags/colors",component:d("/docs/tags/colors","9a5"),exact:!0},{path:"/docs/tags/community",component:d("/docs/tags/community","de4"),exact:!0},{path:"/docs/tags/custom-commands",component:d("/docs/tags/custom-commands","269"),exact:!0},{path:"/docs/tags/darkmode",component:d("/docs/tags/darkmode","2b8"),exact:!0},{path:"/docs/tags/data-synchronization",component:d("/docs/tags/data-synchronization","453"),exact:!0},{path:"/docs/tags/database",component:d("/docs/tags/database","7e4"),exact:!0},{path:"/docs/tags/debug",component:d("/docs/tags/debug","888"),exact:!0},{path:"/docs/tags/dependencies",component:d("/docs/tags/dependencies","c34"),exact:!0},{path:"/docs/tags/eas",component:d("/docs/tags/eas","17a"),exact:!0},{path:"/docs/tags/eas-update",component:d("/docs/tags/eas-update","632"),exact:!0},{path:"/docs/tags/emotion-js",component:d("/docs/tags/emotion-js","a0a"),exact:!0},{path:"/docs/tags/environment-variables",component:d("/docs/tags/environment-variables","0f3"),exact:!0},{path:"/docs/tags/expo",component:d("/docs/tags/expo","a41"),exact:!0},{path:"/docs/tags/expo-dev-client",component:d("/docs/tags/expo-dev-client","0df"),exact:!0},{path:"/docs/tags/expo-router",component:d("/docs/tags/expo-router","257"),exact:!0},{path:"/docs/tags/expo-updates",component:d("/docs/tags/expo-updates","807"),exact:!0},{path:"/docs/tags/flat-list",component:d("/docs/tags/flat-list","5a7"),exact:!0},{path:"/docs/tags/font-awesome",component:d("/docs/tags/font-awesome","c23"),exact:!0},{path:"/docs/tags/generator",component:d("/docs/tags/generator","80e"),exact:!0},{path:"/docs/tags/guide",component:d("/docs/tags/guide","cea"),exact:!0},{path:"/docs/tags/hardware",component:d("/docs/tags/hardware","303"),exact:!0},{path:"/docs/tags/i-18-n",component:d("/docs/tags/i-18-n","d93"),exact:!0},{path:"/docs/tags/i-os",component:d("/docs/tags/i-os","96e"),exact:!0},{path:"/docs/tags/icons",component:d("/docs/tags/icons","3e5"),exact:!0},{path:"/docs/tags/ignite",component:d("/docs/tags/ignite","850"),exact:!0},{path:"/docs/tags/imports",component:d("/docs/tags/imports","887"),exact:!0},{path:"/docs/tags/intro",component:d("/docs/tags/intro","b42"),exact:!0},{path:"/docs/tags/login",component:d("/docs/tags/login","a24"),exact:!0},{path:"/docs/tags/maestro",component:d("/docs/tags/maestro","034"),exact:!0},{path:"/docs/tags/mmkv",component:d("/docs/tags/mmkv","f7e"),exact:!0},{path:"/docs/tags/mob-x",component:d("/docs/tags/mob-x","a01"),exact:!0},{path:"/docs/tags/monorepo",component:d("/docs/tags/monorepo","ba7"),exact:!0},{path:"/docs/tags/offline-support",component:d("/docs/tags/offline-support","6ec"),exact:!0},{path:"/docs/tags/power-sync",component:d("/docs/tags/power-sync","dca"),exact:!0},{path:"/docs/tags/prebuild",component:d("/docs/tags/prebuild","83d"),exact:!0},{path:"/docs/tags/prettier",component:d("/docs/tags/prettier","db7"),exact:!0},{path:"/docs/tags/react-native",component:d("/docs/tags/react-native","873"),exact:!0},{path:"/docs/tags/react-native-vision-camera",component:d("/docs/tags/react-native-vision-camera","ea0"),exact:!0},{path:"/docs/tags/react-navigation",component:d("/docs/tags/react-navigation","0ad"),exact:!0},{path:"/docs/tags/reactotron",component:d("/docs/tags/reactotron","cf3"),exact:!0},{path:"/docs/tags/redux",component:d("/docs/tags/redux","c5e"),exact:!0},{path:"/docs/tags/scroll-to",component:d("/docs/tags/scroll-to","f32"),exact:!0},{path:"/docs/tags/section-list",component:d("/docs/tags/section-list","8de"),exact:!0},{path:"/docs/tags/select-field",component:d("/docs/tags/select-field","5f4"),exact:!0},{path:"/docs/tags/session",component:d("/docs/tags/session","2f1"),exact:!0},{path:"/docs/tags/signup",component:d("/docs/tags/signup","c30"),exact:!0},{path:"/docs/tags/state-management",component:d("/docs/tags/state-management","181"),exact:!0},{path:"/docs/tags/styled-components",component:d("/docs/tags/styled-components","e53"),exact:!0},{path:"/docs/tags/supabase",component:d("/docs/tags/supabase","e10"),exact:!0},{path:"/docs/tags/testing",component:d("/docs/tags/testing","b53"),exact:!0},{path:"/docs/tags/text-field",component:d("/docs/tags/text-field","782"),exact:!0},{path:"/docs/tags/theming",component:d("/docs/tags/theming","bbf"),exact:!0},{path:"/docs/tags/type-script",component:d("/docs/tags/type-script","825"),exact:!0},{path:"/docs/tags/ui",component:d("/docs/tags/ui","020"),exact:!0},{path:"/docs/tags/ui-required-device-capabilities",component:d("/docs/tags/ui-required-device-capabilities","73a"),exact:!0},{path:"/docs/tags/unistyles",component:d("/docs/tags/unistyles","8aa"),exact:!0},{path:"/docs/tags/uses-feature",component:d("/docs/tags/uses-feature","f1a"),exact:!0},{path:"/docs/tags/vector-icons",component:d("/docs/tags/vector-icons","968"),exact:!0},{path:"/docs/tags/vision-camera",component:d("/docs/tags/vision-camera","e9b"),exact:!0},{path:"/docs/tags/yarn",component:d("/docs/tags/yarn","e4e"),exact:!0},{path:"/docs/tags/zustand",component:d("/docs/tags/zustand","24d"),exact:!0},{path:"/docs",component:d("/docs","395"),routes:[{path:"/docs/archive/",component:d("/docs/archive/","ab8"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/archive/PristineExpoProject",component:d("/docs/archive/PristineExpoProject","afa"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/communityRecipes/",component:d("/docs/communityRecipes/","fc5"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/communityRecipes/CustomVectorIcons",component:d("/docs/communityRecipes/CustomVectorIcons","b15"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/intro",component:d("/docs/intro","194"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/AccessibilityFontSizes",component:d("/docs/recipes/AccessibilityFontSizes","872"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/ApolloClientCache",component:d("/docs/recipes/ApolloClientCache","f38"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/Authentication",component:d("/docs/recipes/Authentication","41e"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/CircleCIRNSetup",component:d("/docs/recipes/CircleCIRNSetup","ce2"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/CreatingGreateExperienceForScreenReaders",component:d("/docs/recipes/CreatingGreateExperienceForScreenReaders","752"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/DetoxIntro",component:d("/docs/recipes/DetoxIntro","700"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/DistributingAuthTokenToAPI",component:d("/docs/recipes/DistributingAuthTokenToAPI","298"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/EASUpdate",component:d("/docs/recipes/EASUpdate","938"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/EnforcingImportOrder",component:d("/docs/recipes/EnforcingImportOrder","713"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/EnvironmentVariables",component:d("/docs/recipes/EnvironmentVariables","92b"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/ExpoRouter",component:d("/docs/recipes/ExpoRouter","0b5"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/GeneratorComponentTests",component:d("/docs/recipes/GeneratorComponentTests","e55"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/LocalFirstDataWithPowerSync",component:d("/docs/recipes/LocalFirstDataWithPowerSync","9a4"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/MaestroSetup",component:d("/docs/recipes/MaestroSetup","237"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/MigratingToI18Next",component:d("/docs/recipes/MigratingToI18Next","de3"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/MigratingToMMKV",component:d("/docs/recipes/MigratingToMMKV","9d0"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/MonoreposOverview",component:d("/docs/recipes/MonoreposOverview","092"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/PatchingBuildingAndroid",component:d("/docs/recipes/PatchingBuildingAndroid","ac8"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/PrepForEASBuild",component:d("/docs/recipes/PrepForEASBuild","64a"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/ReactNativeVisionCamera",component:d("/docs/recipes/ReactNativeVisionCamera","e2e"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/Redux",component:d("/docs/recipes/Redux","0f8"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/RemoveMobxStateTree",component:d("/docs/recipes/RemoveMobxStateTree","50f"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/RequiringHardwareFeaturesWithExpo",component:d("/docs/recipes/RequiringHardwareFeaturesWithExpo","2fd"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/SampleYAMLCircleCI",component:d("/docs/recipes/SampleYAMLCircleCI","30a"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/SelectFieldWithBottomSheet",component:d("/docs/recipes/SelectFieldWithBottomSheet","ca3"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/SettingUpYarnMonorepo",component:d("/docs/recipes/SettingUpYarnMonorepo","85d"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/SwitchBetweenExpoGoCNG",component:d("/docs/recipes/SwitchBetweenExpoGoCNG","695"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/Theming-Emotion",component:d("/docs/recipes/Theming-Emotion","715"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/Theming-StyledComponents",component:d("/docs/recipes/Theming-StyledComponents","069"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/Theming-Unistyles",component:d("/docs/recipes/Theming-Unistyles","905"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/TypeScriptBaseURL",component:d("/docs/recipes/TypeScriptBaseURL","986"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/UnrenderedItemInScrollView",component:d("/docs/recipes/UnrenderedItemInScrollView","c15"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/UpdatingDependencies",component:d("/docs/recipes/UpdatingDependencies","21c"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/UpdatingIgnite",component:d("/docs/recipes/UpdatingIgnite","f0c"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/UsingScreenReaders",component:d("/docs/recipes/UsingScreenReaders","324"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/Zustand",component:d("/docs/recipes/Zustand","0e1"),exact:!0,sidebar:"mainSidebar"}]}]}]},{path:"/",component:d("/","703"),exact:!0},{path:"*",component:d("*")}]},240:(e,n,t)=>{"use strict";t.d(n,{e:()=>a,g:()=>s});var r=t(1504),o=t(7624);const a=r.createContext(!1);function s(e){let{children:n}=e;const[t,s]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{s(!0)}),[]),(0,o.jsx)(a.Provider,{value:t,children:n})}},8808:(e,n,t)=>{"use strict";var r=t(1504),o=t(8352),a=t(440),s=t(2160),i=t(8684);const l=[t(7483),t(1462),t(5396),t(1976),t(9115)];var c=t(628),u=t(5592),d=t(5464),p=t(7624);function f(e){let{children:n}=e;return(0,p.jsx)(p.Fragment,{children:n})}var m=t(6952),g=t(8264),h=t(964),y=t(1824),b=t(5008),v=t(1616),S=t(204),w=t(4456),x=t(5684),k=t(8712);function T(){const{i18n:{currentLocale:e,defaultLocale:n,localeConfigs:t}}=(0,g.c)(),r=(0,v.D)(),o=t[e].htmlLang,a=e=>e.replace("-","_");return(0,p.jsxs)(m.c,{children:[Object.entries(t).map((e=>{let[n,{htmlLang:t}]=e;return(0,p.jsx)("link",{rel:"alternate",href:r.createUrl({locale:n,fullyQualified:!0}),hrefLang:t},n)})),(0,p.jsx)("link",{rel:"alternate",href:r.createUrl({locale:n,fullyQualified:!0}),hrefLang:"x-default"}),(0,p.jsx)("meta",{property:"og:locale",content:a(o)}),Object.values(t).filter((e=>o!==e.htmlLang)).map((e=>(0,p.jsx)("meta",{property:"og:locale:alternate",content:a(e.htmlLang)},`meta-og-${e.htmlLang}`)))]})}function E(e){let{permalink:n}=e;const{siteConfig:{url:t}}=(0,g.c)(),r=function(){const{siteConfig:{url:e,baseUrl:n,trailingSlash:t}}=(0,g.c)(),{pathname:r}=(0,u.IT)();return e+(0,x.applyTrailingSlash)((0,h.c)(r),{trailingSlash:t,baseUrl:n})}(),o=n?`${t}${n}`:r;return(0,p.jsxs)(m.c,{children:[(0,p.jsx)("meta",{property:"og:url",content:o}),(0,p.jsx)("link",{rel:"canonical",href:o})]})}function C(){const{i18n:{currentLocale:e}}=(0,g.c)(),{metadata:n,image:t}=(0,y.y)();return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(m.c,{children:[(0,p.jsx)("meta",{name:"twitter:card",content:"summary_large_image"}),(0,p.jsx)("body",{className:S.m})]}),t&&(0,p.jsx)(b.U7,{image:t}),(0,p.jsx)(E,{}),(0,p.jsx)(T,{}),(0,p.jsx)(k.c,{tag:w.e6,locale:e}),(0,p.jsx)(m.c,{children:n.map(((e,n)=>(0,p.jsx)("meta",{...e},n)))})]})}const A=new Map;function _(e){if(A.has(e.pathname))return{...e,pathname:A.get(e.pathname)};if((0,d.C)(c.c,e.pathname).some((e=>{let{route:n}=e;return!0===n.exact})))return A.set(e.pathname,e.pathname),e;const n=e.pathname.trim().replace(/(?:\/index)?\.html$/,"")||"/";return A.set(e.pathname,n),{...e,pathname:n}}var I=t(240),P=t(136),R=t(5288);function L(e){for(var n=arguments.length,t=new Array(n>1?n-1:0),r=1;r{const r=n.default?.[e]??n[e];return r?.(...t)}));return()=>o.forEach((e=>e?.()))}const O=function(e){let{children:n,location:t,previousLocation:r}=e;return(0,R.c)((()=>{r!==t&&(!function(e){let{location:n,previousLocation:t}=e;if(!t)return;const r=n.pathname===t.pathname,o=n.hash===t.hash,a=n.search===t.search;if(r&&o&&!a)return;const{hash:s}=n;if(s){const e=decodeURIComponent(s.substring(1)),n=document.getElementById(e);n?.scrollIntoView()}else window.scrollTo(0,0)}({location:t,previousLocation:r}),L("onRouteDidUpdate",{previousLocation:r,location:t}))}),[r,t]),n};function N(e){const n=Array.from(new Set([e,decodeURI(e)])).map((e=>(0,d.C)(c.c,e))).flat();return Promise.all(n.map((e=>e.route.component.preload?.())))}class F extends r.Component{previousLocation;routeUpdateCleanupCb;constructor(e){super(e),this.previousLocation=null,this.routeUpdateCleanupCb=i.c.canUseDOM?L("onRouteUpdate",{previousLocation:null,location:this.props.location}):()=>{},this.state={nextRouteHasLoaded:!0}}shouldComponentUpdate(e,n){if(e.location===this.props.location)return n.nextRouteHasLoaded;const t=e.location;return this.previousLocation=this.props.location,this.setState({nextRouteHasLoaded:!1}),this.routeUpdateCleanupCb=L("onRouteUpdate",{previousLocation:this.previousLocation,location:t}),N(t.pathname).then((()=>{this.routeUpdateCleanupCb(),this.setState({nextRouteHasLoaded:!0})})).catch((e=>{console.warn(e),window.location.reload()})),!1}render(){const{children:e,location:n}=this.props;return(0,p.jsx)(O,{previousLocation:this.previousLocation,location:n,children:(0,p.jsx)(u.kX,{location:n,render:()=>e})})}}const j=F,M="__docusaurus-base-url-issue-banner-container",B="__docusaurus-base-url-issue-banner",D="__docusaurus-base-url-issue-banner-suggestion-container";function $(e){return`\ndocument.addEventListener('DOMContentLoaded', function maybeInsertBanner() {\n var shouldInsert = typeof window['docusaurus'] === 'undefined';\n shouldInsert && insertBanner();\n});\n\nfunction insertBanner() {\n var bannerContainer = document.createElement('div');\n bannerContainer.id = '${M}';\n var bannerHtml = ${JSON.stringify(function(e){return`\n\n
Your Docusaurus site did not load properly.
\n
A very common reason is a wrong site baseUrl configuration.
\n
Current configured baseUrl = ${e} ${"/"===e?" (default value)":""}
\n
We suggest trying baseUrl =
\n
\n`}(e)).replace(/{if("undefined"==typeof document)return void t();const r=document.createElement("link");r.setAttribute("rel","prefetch"),r.setAttribute("href",e),r.onload=()=>n(),r.onerror=()=>t();const o=document.getElementsByTagName("head")[0]??document.getElementsByName("script")[0]?.parentNode;o?.appendChild(r)}))}:function(e){return new Promise(((n,t)=>{const r=new XMLHttpRequest;r.open("GET",e,!0),r.withCredentials=!0,r.onload=()=>{200===r.status?n():t()},r.send(null)}))};var X=t(8120);const Q=new Set,Z=new Set,J=()=>navigator.connection?.effectiveType.includes("2g")||navigator.connection?.saveData,ee={prefetch(e){if(!(e=>!J()&&!Z.has(e)&&!Q.has(e))(e))return!1;Q.add(e);const n=(0,d.C)(c.c,e).flatMap((e=>{return n=e.route.path,Object.entries(K).filter((e=>{let[t]=e;return t.replace(/-[^-]+$/,"")===n})).flatMap((e=>{let[,n]=e;return Object.values((0,X.c)(n))}));var n}));return Promise.all(n.map((e=>{const n=t.gca(e);return n&&!n.includes("undefined")?Y(n).catch((()=>{})):Promise.resolve()})))},preload:e=>!!(e=>!J()&&!Z.has(e))(e)&&(Z.add(e),N(e))},ne=Object.freeze(ee),te=Boolean(!0);if(i.c.canUseDOM){window.docusaurus=ne;const e=document.getElementById("__docusaurus"),n=(0,p.jsx)(s.EN,{children:(0,p.jsx)(a.kn,{children:(0,p.jsx)(q,{})})}),t=(e,n)=>{console.error("Docusaurus React Root onRecoverableError:",e,n)},i=()=>{if(te)r.startTransition((()=>{o.hydrateRoot(e,n,{onRecoverableError:t})}));else{const a=o.createRoot(e,{onRecoverableError:t});r.startTransition((()=>{a.render(n)}))}};N(window.location.pathname).then(i)}},136:(e,n,t)=>{"use strict";t.d(n,{e:()=>d,y:()=>p});var r=t(1504),o=t(7768);const a=JSON.parse('{"docusaurus-plugin-google-gtag":{"default":{"trackingID":["G-1NP64B0XVM"],"anonymizeIP":true,"id":"default"}},"example-code-snippets":{"default":{"snippets":[{"author":"Mark Rickert","content":"import * as React from \\"react\\";\\nimport { View, TextProps, PixelRatio, AppState } from \\"react-native\\";\\nimport { MaterialTopTabNavigationOptions } from \\"@react-navigation/material-top-tabs\\";\\nimport { StackNavigationOptions } from \\"@react-navigation/stack\\";\\nimport { BottomTabNavigationOptions } from \\"@react-navigation/bottom-tabs\\";\\nimport { DrawerNavigationOptions } from \\"@react-navigation/drawer\\";\\nimport { Text } from \\"./Text\\";\\n\\n// These constants determine how much bigger the font size should get based on the user\'s\\n// accessibility settings. Even if they turn the dial all the way to 11, we will only ever\\n// scale the fonts by these factors. This is to prevent the font size from getting too large\\n// and completely breaking the layout.\\nconst MAX_FONT_SCALE = 1.2;\\nconst MIN_FONT_SCALE = 0.8;\\n\\n// Returns fontScaling props for Text and TextInput components\\n// Usage:\\n// const fontProps = useFontScaling();\\n// return Text Here;\\nexport const useFontScaling = (): Partial => {\\n // You probably want to get this value from your user\'s preferences\\n const [allowFontScaling,] = React.useState(true);\\n\\n const fontScaling: Partial = React.useMemo(() => {\\n return {\\n minimumFontScale: allowFontScaling ? MIN_FONT_SCALE : 1, // This prevents the font from getting too small.\\n maxFontSizeMultiplier: allowFontScaling ? MAX_FONT_SCALE : 1, // This prevents the font from getting too big.\\n allowFontScaling, // This allows the font to be scaled or not.\\n };\\n }, [allowFontScaling]);\\n\\n return fontScaling;\\n};\\n\\n// Returns fontScaling props for Navigator components\\nexport const useNavigatorFontScalingScreenOptions =\\n (): Partial => {\\n // You probably want to get this value from your user\'s preferences\\n const [allowFontScaling,] = React.useState(true);\\n\\n const fontScaling: Partial = React.useMemo(() => {\\n return {\\n headerBackAllowFontScaling: allowFontScaling,\\n headerTitleAllowFontScaling: allowFontScaling,\\n };\\n }, [allowFontScaling]);\\n\\n return fontScaling;\\n };\\n\\n// Returns fontScaling props for Top Tab Navigator components\\nexport const useTopTabNavigatorFontScalingScreenOptions =\\n (): Partial => {\\n // You probably want to get this value from your user\'s preferences\\n const [allowFontScaling,] = React.useState(true);\\n\\n const fontScaling: Partial =\\n React.useMemo(() => {\\n return {\\n tabBarAllowFontScaling: allowFontScaling,\\n };\\n }, [allowFontScaling]);\\n\\n return fontScaling;\\n };\\n\\n// Returns fontScaling props for Tab Navigator components\\nexport const useTabNavigatorFontScalingScreenOptions =\\n (): Partial => {\\n // You probably want to get this value from your user\'s preferences\\n const [allowFontScaling,] = React.useState(true);\\n\\n const fontScaling: Partial =\\n React.useMemo(() => {\\n return {\\n tabBarAllowFontScaling: fontScaling,\\n headerTitleAllowFontScaling: fontScaling,\\n };\\n }, [allowFontScaling]);\\n\\n return fontScaling;\\n };\\n\\n// Returns fontScaling props for Tab Navigator components\\nexport const useDrawerNavigatorFontScalingScreenOptions =\\n (): Partial => {\\n const [allowFontScaling,] = React.useState(true);\\n\\n const fontScaling: Partial = React.useMemo(() => {\\n return {\\n drawerAllowFontScaling: allowFontScaling,\\n headerTitleAllowFontScaling: allowFontScaling,\\n };\\n }, [allowFontScaling]);\\n\\n return fontScaling;\\n };\\n\\n// Use this handy __DEV__ mode only component to figure out what the font size is actually doing.\\nexport const DevFontSize = () => {\\n const [allowFontScaling,] = React.useState(true);\\n const [appStateVisible, setAppStateVisible] = React.useState(\\n AppState.currentState\\n );\\n\\n React.useEffect(() => {\\n const subscription = AppState.addEventListener(\\"change\\", (nextAppState) => {\\n setAppStateVisible(nextAppState);\\n });\\n\\n return () => subscription.remove();\\n }, []);\\n\\n // This memo has to listen to appStateVisible even though it\'s not a direct dependency\\n // so that we can reload the font size when the app switches back from user settings.\\n const fontSize = React.useMemo(() => {\\n if (allowFontScaling) {\\n return Math.min(\\n Math.max(PixelRatio.getFontScale(), MIN_FONT_SCALE),\\n MAX_FONT_SCALE\\n );\\n } else {\\n return 1.0;\\n }\\n }, [allowFontScaling, appStateVisible]); // eslint-disable-line react-hooks/exhaustive-deps\\n\\n return __DEV__ ? (\\n \\n \\n User Font Setting: {Math.trunc(PixelRatio.getFontScale() * 100) / 100}\\n \\n \\n Currently limiting ratio to: {Math.trunc(fontSize * 100) / 100}\\n \\n \\n ) : null;\\n};\\n\\n","lastUpdated":"6 months ago","title":"Accessiblity Font Sizes","publish_date":"2022-10-09","doc_name":"AccessibilityFontSizes.md"},{"author":"Frank Calise","content":"npx ignite-cli@latest new ignite-apollo-cmds --yes\\ncd ignite-apollo-cmds\\nnpx expo install @apollo/client graphql\\nmkdir app/stores/apollo\\ntouch app/stores/apollo/index.tsx\\n\\nimport { ApolloClient, InMemoryCache } from \\"@apollo/client\\";\\n\\nconst cache = new InMemoryCache();\\n\\nexport const client = new ApolloClient({\\n uri: \\"https://api.graphql.guide/graphql\\",\\n cache,\\n defaultOptions: {\\n watchQuery: { fetchPolicy: \\"cache-and-network\\" },\\n },\\n});\\n\\n// success-line-start\\nimport { ApolloProvider } from \\"@apollo/client\\";\\nimport { client as apolloClient } from \\"app/stores/apollo\\";\\n// success-line-end\\n\\n// ...\\n\\nreturn (\\n // success-line\\n \\n \\n \\n \\n \\n \\n \\n \\n // success-line\\n \\n);\\n\\nreactotron.onCustomCommand({\\n title: \\"Extract Apollo Client Cache\\",\\n description: \\"Gets the updated InMemory cache from Apollo Client\\",\\n command: \\"extractApolloCache\\",\\n handler: () => {\\n Reactotron.display({\\n name: \\"Apollo Cache\\",\\n preview: \\"Cache Snapshot\\",\\n value: apolloClient.cache.extract(),\\n });\\n },\\n});\\n\\n{\\n \\"parent\\": {\\n \\"child\\": {\\n \\"someProp\\": 5\\n }\\n }\\n}\\n\\nfunction getNestedCacheValue(keyPath: string): any {\\n // Extract the entire cache\\n const cache: NormalizedCacheObject = client.cache.extract();\\n\\n // Define a regular expression to match keys and array accessors\\n const pathSegmentRegex = /[^.[\\\\]]+|\\\\[\\\\d+\\\\]/g;\\n\\n // Extract path segments, including array indices\\n const pathSegments = keyPath.match(pathSegmentRegex) || [];\\n\\n // Navigate through the path segments to get to the desired value\\n const value = pathSegments.reduce((acc, segment) => {\\n // Check if the segment is an array accessor, e.g., [1]\\n if (segment.startsWith(\\"[\\") && segment.endsWith(\\"]\\")) {\\n // Extract the index from the segment and convert it to a number\\n const index = parseInt(segment.slice(1, -1), 10);\\n return acc ? acc[index] : undefined;\\n }\\n // Handle normal object property access\\n return acc ? acc[segment] : undefined;\\n }, cache);\\n\\n return value ?? null; // Return null if the value is undefined at any point\\n}\\n\\nreactotron.onCustomCommand({\\n title: \\"Extract Apollo Client Cache by Key\\",\\n description: \\"Retrieves a specific key from the Apollo Client cache\\",\\n command: \\"extractApolloCacheByKey\\",\\n args: [{ name: \\"key\\", type: ArgType.String }],\\n handler: (args) => {\\n const { key } = args ?? {};\\n if (key) {\\n const findValue = getNestedCacheValue(key);\\n if (findValue) {\\n Reactotron.display({\\n name: \\"Apollo Cache\\",\\n preview: `Cache Value for Key: ${key}`,\\n value: findValue,\\n });\\n } else {\\n Reactotron.display({\\n name: \\"Apollo Cache\\",\\n preview: `Value not available for key: ${key}`,\\n });\\n }\\n } else {\\n Reactotron.log(\\"Could not extract cache value. No key provided.\\");\\n }\\n },\\n});\\n\\n","lastUpdated":"6 months ago","title":"Extracting Apollo Client\'s Cache in Reactotron","publish_date":"2024-03-26","doc_name":"ApolloClientCache.md"},{"author":"Nick Morgan (@morganick)","content":"bunx ignite-cli@latest new AuthRecipe --workflow=cng --remove-demo --git --install-deps --packager=bun\\n\\ncd AuthRecipe\\nbun run ios\\n\\nbunx ignite-cli@latest generate screen SignIn\\n\\nimport React, { FC, useState } from \\"react\\"\\nimport { observer } from \\"mobx-react-lite\\"\\nimport {\\n Image,\\n ImageStyle,\\n Pressable,\\n TextStyle,\\n View,\\n ViewStyle,\\n} from \\"react-native\\"\\nimport { AppStackScreenProps } from \\"app/navigators\\"\\nimport { Button, Screen, Text, TextField } from \\"app/components\\"\\nimport { useSafeAreaInsetsStyle } from \\"app/utils/useSafeAreaInsetsStyle\\"\\nimport { colors, spacing } from \\"app/theme\\"\\n\\nconst logo = require(\\"../../assets/images/logo.png\\")\\n\\ninterface SignInScreenProps extends AppStackScreenProps<\\"SignIn\\"> {}\\n\\nexport const SignInScreen: FC = observer(\\n function SignInScreen() {\\n const $bottomContainerInsets = useSafeAreaInsetsStyle([\\"bottom\\"])\\n const [email, setEmail] = useState(\\"\\")\\n const [password, setPassword] = useState(\\"\\")\\n\\n const onSignIn = () => {\\n // Sign In Flow\\n console.log(\\"Sign In Flow\\", { email, password })\\n }\\n\\n const onSignUp = () => {\\n // Sign Up Flow\\n console.log(\\"Sign Up Flow\\")\\n }\\n\\n const onForgotPassword = () => {\\n // Forgot Password Flow\\n console.log(\\"Forgot Password Flow\\")\\n }\\n\\n return (\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n Forgot Password?\\n \\n - or -\\n \\n \\n \\n \\n \\n \\n )\\n }\\n)\\n\\nconst $root: ViewStyle = {\\n minHeight: \\"100%\\",\\n backgroundColor: colors.palette.neutral100,\\n}\\n\\nconst $container: ViewStyle = {\\n backgroundColor: colors.background,\\n}\\n\\nconst $topContainer: ViewStyle = {\\n height: 200,\\n justifyContent: \\"center\\",\\n alignItems: \\"center\\",\\n}\\n\\nconst $bottomContainer: ViewStyle = {\\n backgroundColor: colors.palette.neutral100,\\n paddingBottom: spacing.xl,\\n paddingHorizontal: spacing.lg,\\n}\\n\\nconst $cap: ViewStyle = {\\n backgroundColor: colors.palette.neutral100,\\n borderTopLeftRadius: 16,\\n borderTopRightRadius: 16,\\n height: spacing.xl,\\n position: \\"absolute\\",\\n top: -spacing.xl,\\n left: 0,\\n right: 0,\\n}\\n\\nconst $textField: ViewStyle = {\\n marginBottom: spacing.md,\\n}\\n\\nconst $forgotPassword: ViewStyle = {\\n marginVertical: spacing.md,\\n}\\n\\nconst $buttonDivider: TextStyle = {\\n textAlign: \\"center\\",\\n marginVertical: spacing.md,\\n}\\n\\nconst $logo: ImageStyle = {\\n height: 88,\\n width: \\"100%\\",\\n marginBottom: spacing.xxl,\\n}\\n\\nconst AppStack = observer(function AppStack() {\\n // success-line\\n const isAuthenticated = false\\n return (\\n \\n // success-line-start\\n {isAuthenticated ? (\\n <>\\n {/** \ud83d\udd25 Your screens go here */}\\n \\n {/* IGNITE_GENERATOR_ANCHOR_APP_STACK_SCREENS */}\\n >\\n ) : (\\n \\n )}\\n // success-line-end\\n \\n )\\n})\\n\\nEXPO_PUBLIC_SUPABASE_URL=\\"https://.supabase.co\\"\\nEXPO_PUBLIC_SUPABASE_ANON_KEY=\\"\\"\\n\\n.env\\n\\nbunx eas secret:push --scope project --env-file .env\\n\\nexport interface ConfigBaseProps {\\n persistNavigation: \\"always\\" | \\"dev\\" | \\"prod\\" | \\"never\\"\\n catchErrors: \\"always\\" | \\"dev\\" | \\"prod\\" | \\"never\\"\\n exitRoutes: string[]\\n // success-line-start\\n supabaseUrl: string\\n supabaseAnonKey: string\\n // success-line-end\\n}\\n\\nexport type PersistNavigationConfig = ConfigBaseProps[\\"persistNavigation\\"]\\n\\nconst BaseConfig: ConfigBaseProps = {\\n // This feature is particularly useful in development mode, but\\n // can be used in production as well if you prefer.\\n persistNavigation: \\"dev\\",\\n\\n /**\\n * Only enable if we\'re catching errors in the right environment\\n */\\n catchErrors: \\"always\\",\\n\\n /**\\n * This is a list of all the route names that will exit the app if the back button\\n * is pressed while in that screen. Only affects Android.\\n */\\n exitRoutes: [\\"Welcome\\"],\\n // success-line-start\\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\\n supabaseUrl: process.env.EXPO_PUBLIC_SUPABASE_URL!,\\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\\n supabaseAnonKey: process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,\\n // success-line-end\\n}\\n\\nexport default BaseConfig\\n\\nbunx expo install @supabase/supabase-js react-native-mmkv\\n\\nbun ios\\n# or\\nbun android\\n\\nimport { MMKV } from \\"react-native-mmkv\\"\\n\\nconst storage = new MMKV({\\n id: \\"session\\",\\n})\\n\\n// TODO: Remove this workaround for encryption: https://github.com/mrousavy/react-native-mmkv/issues/665\\nstorage.set(\\"workaround\\", true)\\n\\n/**\\n * A simple wrapper around MMKV that provides a base API\\n * that matches AsyncStorage for use with Supabase.\\n */\\n\\n/**\\n * Get an item from storage by key\\n *\\n * @param {string} key of the item to fetch\\n * @returns {Promise} value for the key as a string or null if not found\\n */\\nexport async function getItem(key: string): Promise {\\n try {\\n return storage.getString(key) ?? null\\n } catch {\\n console.warn(`Failed to get key \\"${key}\\" from secure storage`)\\n return null\\n }\\n}\\n\\n/**\\n * Sets an item in storage by key\\n *\\n * @param {string} key of the item to store\\n * @param {string} value of the item to store\\n */\\nexport async function setItem(key: string, value: string): Promise {\\n try {\\n storage.set(key, value)\\n } catch {\\n console.warn(`Failed to set key \\"${key}\\" in secure storage`)\\n }\\n}\\n\\n/**\\n * Removes a single item from storage by key\\n *\\n * @param {string} key of the item to remove\\n */\\nexport async function removeItem(key: string): Promise {\\n try {\\n storage.delete(key)\\n } catch {\\n console.warn(`Failed to remove key \\"${key}\\" from secure storage`)\\n }\\n}\\n\\nbunx expo install expo-secure-store expo-crypto\\n\\n...\\n \\"plugins\\": [\\n \\"expo-localization\\",\\n // success-line\\n \\"expo-secure-store\\",\\n [\\n \\"expo-build-properties\\",\\n {\\n \\"ios\\": {\\n \\"newArchEnabled\\": false,\\n \\"flipper\\": false\\n },\\n \\"android\\": {\\n \\"newArchEnabled\\": false\\n }\\n }\\n ],\\n \\"expo-font\\"\\n ],\\n...\\n\\nbun ios\\n# or\\nbun android\\n\\nimport { MMKV } from \\"react-native-mmkv\\"\\n// success-line-start\\nimport * as SecureStore from \\"expo-secure-store\\"\\nimport * as Crypto from \\"expo-crypto\\"\\n\\nconst fetchOrGenerateEncryptionKey = (): string => {\\n const encryptionKey = SecureStore.getItem(\\"session-encryption-key\\")\\n\\n if (encryptionKey) {\\n return encryptionKey\\n } else {\\n const uuid = Crypto.randomUUID()\\n SecureStore.setItem(\\"session-encryption-key\\", uuid)\\n return uuid\\n }\\n}\\n// success-line-end\\n\\nconst storage = new MMKV({\\n id: \\"session\\",\\n // success-line\\n encryptionKey: fetchOrGenerateEncryptionKey(),\\n})\\n\\n...\\n\\nimport Config from \\"app/config\\"\\nimport { createClient } from \\"@supabase/supabase-js\\"\\nimport * as SessionStorage from \\"app/utils/storage/SessionStorage\\"\\nimport { AppState } from \\"react-native\\"\\n\\nexport const supabase = createClient(\\n Config.supabaseUrl,\\n Config.supabaseAnonKey,\\n {\\n auth: {\\n storage: SessionStorage,\\n autoRefreshToken: true,\\n detectSessionInUrl: false,\\n },\\n }\\n)\\n\\nexport { type Session, type AuthError } from \\"@supabase/supabase-js\\"\\n\\n/**\\n * Tells Supabase to autorefresh the session while the application\\n * is in the foreground. (Docs: https://supabase.com/docs/reference/javascript/auth-startautorefresh)\\n */\\nAppState.addEventListener(\\"change\\", (nextAppState) => {\\n if (nextAppState === \\"active\\") {\\n supabase.auth.startAutoRefresh()\\n } else {\\n supabase.auth.stopAutoRefresh()\\n }\\n})\\n\\nimport React, {\\n createContext,\\n PropsWithChildren,\\n useCallback,\\n useContext,\\n useState,\\n} from \\"react\\"\\nimport { Session, supabase } from \\"./supabase\\"\\nimport { AuthResponse, AuthTokenResponsePassword } from \\"@supabase/supabase-js\\"\\n\\ntype AuthState = {\\n isAuthenticated: boolean\\n token?: Session[\\"access_token\\"]\\n}\\n\\ntype SignInProps = {\\n email: string\\n password: string\\n}\\n\\ntype SignUpProps = {\\n email: string\\n password: string\\n}\\n\\ntype AuthContextType = {\\n signIn: (props: SignInProps) => Promise\\n signUp: (props: SignUpProps) => Promise\\n} & AuthState\\n\\nconst AuthContext = createContext({\\n isAuthenticated: false,\\n token: undefined,\\n signIn: () => new Promise(() => ({})),\\n signUp: () => new Promise(() => ({})),\\n})\\n\\nexport function useAuth() {\\n const value = useContext(AuthContext)\\n\\n if (process.env.NODE_ENV !== \\"production\\") {\\n if (!value) {\\n throw new Error(\\"useAuth must be used within an AuthProvider\\")\\n }\\n }\\n\\n return value\\n}\\n\\nexport const AuthProvider = ({ children }: PropsWithChildren) => {\\n const [token, setToken] = useState(undefined)\\n\\n const signIn = useCallback(\\n async ({ email, password }: SignInProps) => {\\n const result = await supabase.auth.signInWithPassword({\\n email,\\n password,\\n })\\n\\n if (result.data?.session?.access_token) {\\n setToken(result.data.session.access_token)\\n }\\n\\n return result\\n },\\n [supabase]\\n )\\n\\n const signUp = useCallback(\\n async ({ email, password }: SignUpProps) => {\\n const result = await supabase.auth.signUp({\\n email,\\n password,\\n })\\n\\n if (result.data?.session?.access_token) {\\n setToken(result.data.session.access_token)\\n }\\n\\n return result\\n },\\n [supabase]\\n )\\n\\n return (\\n \\n {children}\\n \\n )\\n}\\n\\n...\\nimport { ViewStyle } from \\"react-native\\"\\n// success-line\\nimport { AuthProvider } from \\"./services/auth/useAuth\\"\\n\\n...\\n\\n return (\\n // success-line\\n \\n \\n \\n \\n \\n \\n \\n \\n // success-line\\n \\n )\\n}\\n\\n...\\n\\nimport { colors } from \\"app/theme\\"\\n// success-line\\nimport { useAuth } from \\"app/services/auth/useAuth\\"\\n\\n...\\n\\nconst AppStack = observer(function AppStack() {\\n // error-line\\n const isAuthenticated = false\\n // success-line\\n const { isAuthenticated } = useAuth()\\n return (\\n \\n {isAuthenticated ? (\\n <>\\n {/** \ud83d\udd25 Your screens go here */}\\n \\n {/* IGNITE_GENERATOR_ANCHOR_APP_STACK_SCREENS */}\\n >\\n ) : (\\n \\n )}\\n \\n )\\n})\\n\\n...\\n\\n...\\nimport { colors, spacing } from \\"app/theme\\"\\n// success-line\\nimport { useAuth } from \\"app/services/auth/useAuth\\"\\n\\n...\\nexport const SignInScreen: FC = observer(function SignInScreen() {\\n const $bottomContainerInsets = useSafeAreaInsetsStyle([\\"bottom\\"])\\n // success-line\\n const { signIn, signUp } = useAuth()\\n const [email, setEmail] = useState(\\"\\")\\n const [password, setPassword] = useState(\\"\\")\\n\\n const passwordInput = React.useRef(null)\\n\\n const onSignIn = () => {\\n // error-line-start\\n // Sign In Flow\\n console.log(\\"Sign In Flow\\", { email, password })\\n // error-line-end\\n // success-line\\n signIn({ email, password })\\n }\\n\\n const onSignUp = () => {\\n // error-line-start\\n // Sign Up Flow\\n console.log(\\"Sign Up Flow\\")\\n // error-line-end\\n // success-line\\n signUp({ email, password })\\n }\\n\\n...\\n\\n...\\ntype AuthContextType = {\\n signIn: (props: SignInProps) => Promise\\n signUp: (props: SignUpProps) => Promise\\n // success-line\\n signOut: () => void\\n} & AuthState\\n\\nconst AuthContext = createContext({\\n isAuthenticated: false,\\n token: undefined,\\n signIn: () => new Promise(() => ({})),\\n signUp: () => new Promise(() => ({})),\\n // success-line\\n signOut: () => undefined,\\n})\\n...\\nexport const AuthProvider = ({ children }: PropsWithChildren) => {\\n...\\n // success-line-start\\n const signOut = useCallback(async () => {\\n await supabase.auth.signOut()\\n setToken(undefined)\\n }, [supabase])\\n // success-line-end\\n\\n return (\\n \\n {children}\\n \\n )\\n}\\n\\nimport { observer } from \\"mobx-react-lite\\"\\nimport React, { FC } from \\"react\\"\\nimport { Image, ImageStyle, TextStyle, View, ViewStyle } from \\"react-native\\"\\n// error-line\\nimport { Text } from \\"app/components\\"\\n// success-line\\nimport { Button, Text } from \\"app/components\\"\\nimport { isRTL } from \\"../i18n\\"\\nimport { AppStackScreenProps } from \\"../navigators\\"\\nimport { colors, spacing } from \\"../theme\\"\\nimport { useSafeAreaInsetsStyle } from \\"../utils/useSafeAreaInsetsStyle\\"\\n// success-line\\nimport { useAuth } from \\"app/services/auth/useAuth\\"\\n\\nconst welcomeLogo = require(\\"../../assets/images/logo.png\\")\\nconst welcomeFace = require(\\"../../assets/images/welcome-face.png\\")\\n\\ninterface WelcomeScreenProps extends AppStackScreenProps<\\"Welcome\\"> {}\\n\\nexport const WelcomeScreen: FC = observer(\\n function WelcomeScreen() {\\n const $bottomContainerInsets = useSafeAreaInsetsStyle([\\"bottom\\"])\\n const { signOut } = useAuth()\\n\\n return (\\n \\n \\n \\n \\n \\n \\n \\n \\n // error-line\\n \\n // success-line\\n \\n \\n \\n )\\n }\\n)\\n\\n...\\nconst AppStack = observer(function AppStack() {\\n const { isAuthenticated } = useAuth()\\n return (\\n \\n {isAuthenticated ? (\\n <>\\n {/** \ud83d\udd25 Your screens go here */}\\n \\n {/* IGNITE_GENERATOR_ANCHOR_APP_STACK_SCREENS */}\\n >\\n ) : (\\n \\n )}\\n \\n )\\n})\\n...\\n\\n...\\n// error-line\\nimport React, { createContext, PropsWithChildren, useCallback, useContext, useState } from \\"react\\"\\n// success-line\\nimport React, { createContext, PropsWithChildren, useCallback, useContext, useEffect, useState } from \\"react\\"\\n...\\nexport const AuthProvider = ({ children }: PropsWithChildren) => {\\n const [token, setToken] = useState(undefined)\\n\\n // success-line-start\\n useEffect(() => {\\n const {\\n data: { subscription },\\n } = supabase.auth.onAuthStateChange((event, session) => {\\n switch (event) {\\n case \\"SIGNED_OUT\\":\\n setToken(undefined)\\n break\\n case \\"INITIAL_SESSION\\":\\n case \\"SIGNED_IN\\":\\n case \\"TOKEN_REFRESHED\\":\\n setToken(session?.access_token)\\n break\\n default:\\n // no-op\\n }\\n })\\n\\n return () => {\\n subscription.unsubscribe()\\n }\\n }, [supabase])\\n // success-line-end\\n...\\n\\n...\\nexport const SignInScreen: FC = observer(function SignInScreen() {\\n const $bottomContainerInsets = useSafeAreaInsetsStyle([\\"bottom\\"])\\n const { signIn, signUp } = useAuth()\\n const [email, setEmail] = useState(\\"\\")\\n const [password, setPassword] = useState(\\"\\")\\n // success-line\\n const [isSigningIn, setIsSigningIn] = useState(false)\\n\\n // error-line-start\\n const onSignIn = () => {\\n signIn({ email, password })\\n // error-line-end\\n // success-line-start\\n const onSignIn = async () => {\\n try {\\n setIsSigningIn(true)\\n await signIn({ email, password })\\n } finally {\\n setIsSigningIn(false)\\n }\\n // success-line-end\\n }\\n ...\\n // error-line\\n \\n // success-line-start\\n \\n // success-line-end\\n ...\\n\\n\\n...\\nexport const SignInScreen: FC = observer(function SignInScreen() {\\n const $bottomContainerInsets = useSafeAreaInsetsStyle([\\"bottom\\"])\\n const { signIn, signUp } = useAuth()\\n const [email, setEmail] = useState(\\"\\")\\n const [password, setPassword] = useState(\\"\\")\\n const [isSigningIn, setIsSigningIn] = useState(false)\\n // success-line\\n const [isSigningUp, setIsSigningUp] = useState(false)\\n\\n const onSignIn = async () => {\\n try {\\n setIsSigningIn(true)\\n await signIn({ email, password })\\n } finally {\\n setIsSigningIn(false)\\n }\\n }\\n\\n // error-line-start\\n const onSignUp = () => {\\n signUp({ email, password })\\n // error-line-end\\n // success-line-start\\n const onSignUp = async () => {\\n try {\\n setIsSigningUp(true)\\n await signUp({ email, password })\\n } finally {\\n setIsSigningUp(false)\\n }\\n // success-line-end\\n }\\n ...\\n // error-line\\n \\n // success-line-start\\n \\n // success-line-end\\n ...\\n\\n\\n...\\nexport const SignInScreen: FC = observer(function SignInScreen() {\\n const $bottomContainerInsets = useSafeAreaInsetsStyle([\\"bottom\\"])\\n const { signIn, signUp } = useAuth()\\n const [email, setEmail] = useState(\\"\\")\\n const [password, setPassword] = useState(\\"\\")\\n const [isSigningIn, setIsSigningIn] = useState(false)\\n const [isSigningUp, setIsSigningUp] = useState(false)\\n // success-line\\n const isLoading = isSigningIn || isSigningUp\\n...\\n \\n \\n \\n \\n \\n // success-line\\n \\n // success-line\\n \\n Forgot Password?\\n \\n - or -\\n // success-line\\n \\n \\n \\n\\n...\\nexport const SignInScreen: FC = observer(function SignInScreen() {\\n const $bottomContainerInsets = useSafeAreaInsetsStyle([\\"bottom\\"])\\n const { signIn, signUp } = useAuth()\\n const [email, setEmail] = useState(\\"\\")\\n const [password, setPassword] = useState(\\"\\")\\n // success-line\\n const [error, setError] = useState(undefined)\\n...\\n const onSignIn = async () => {\\n try {\\n setIsSigningIn(true)\\n // success-line\\n setError(undefined)\\n\\n // error-line\\n await signIn({ email, password })\\n // success-line-start\\n const { error } = await signIn({ email, password })\\n if (error) {\\n setError(error.message)\\n }\\n // success-line-end\\n } finally {\\n setIsSigningIn(false)\\n }\\n }\\n\\n const onSignUp = async () => {\\n try {\\n setIsSigningUp(true)\\n // success-line\\n setError(undefined)\\n\\n // error-line\\n await signUp({ email, password })\\n // success-line-start\\n const { error } = await signUp({ email, password })\\n if (error) {\\n setError(error.message)\\n }\\n // success-line-end\\n } finally {\\n setIsSigningUp(false)\\n }\\n }\\n ...\\n return (\\n \\n \\n \\n \\n \\n \\n // success-line\\n {error && {error}}\\n \\n...\\n\\nconst $logo: ImageStyle = {\\n height: 88,\\n width: \\"100%\\",\\n marginBottom: spacing.xxl,\\n}\\n\\n// success-line-start\\nconst $errorText: TextStyle = {\\n color: colors.error,\\n}\\n// success-line-end\\n\\n\\n...\\nexport const SignInScreen: FC = observer(function SignInScreen() {\\n const $bottomContainerInsets = useSafeAreaInsetsStyle([\\"bottom\\"])\\n const { signIn, signUp } = useAuth()\\n const [email, setEmail] = useState(\\"\\")\\n const [password, setPassword] = useState(\\"\\")\\n // success-line\\n const [validationErrors, setValidationErrors] = useState\\n }\\n onPress={() => openLinkInBrowser(\\"https://reactnativenewsletter.com/\\")}\\n />\\n \\n \\n \\n }\\n onPress={() => openLinkInBrowser(\\"https://rn.live/\\")}\\n />\\n \\n \\n \\n }\\n onPress={() => openLinkInBrowser(\\"https://cr.infinite.red/\\")}\\n />\\n \\n \\n openLinkInBrowser(\\"https://infinite.red/contact\\")}\\n />\\n \\n );\\n}\\n\\nconst $container: ViewStyle = {\\n paddingTop: spacing.lg + spacing.xl,\\n paddingHorizontal: spacing.lg,\\n};\\n\\nconst $title: TextStyle = {\\n marginBottom: spacing.sm,\\n};\\n\\nconst $tagline: TextStyle = {\\n marginBottom: spacing.xxl,\\n};\\n\\nconst $description: TextStyle = {\\n marginBottom: spacing.lg,\\n};\\n\\nconst $sectionTitle: TextStyle = {\\n marginTop: spacing.xxl,\\n};\\n\\nconst $logoContainer: ViewStyle = {\\n marginEnd: spacing.md,\\n flexDirection: \\"row\\",\\n flexWrap: \\"wrap\\",\\n alignContent: \\"center\\",\\n};\\n\\nconst $logo: ImageStyle = {\\n height: 38,\\n width: 38,\\n};\\n\\nimport { observer } from \\"mobx-react-lite\\";\\nimport React, { ComponentType, useEffect, useMemo } from \\"react\\";\\nimport {\\n AccessibilityProps,\\n ActivityIndicator,\\n Image,\\n ImageSourcePropType,\\n ImageStyle,\\n Platform,\\n StyleSheet,\\n TextStyle,\\n View,\\n ViewStyle,\\n} from \\"react-native\\";\\nimport { type ContentStyle } from \\"@shopify/flash-list\\";\\nimport Animated, {\\n Extrapolate,\\n interpolate,\\n useAnimatedStyle,\\n useSharedValue,\\n withSpring,\\n} from \\"react-native-reanimated\\";\\nimport {\\n Button,\\n ButtonAccessoryProps,\\n Card,\\n EmptyState,\\n Icon,\\n ListView,\\n Screen,\\n Text,\\n Toggle,\\n} from \\"src/components\\";\\nimport { isRTL, translate } from \\"src/i18n\\";\\nimport { useStores } from \\"src/models\\";\\nimport { Episode } from \\"src/models/Episode\\";\\nimport { colors, spacing } from \\"src/theme\\";\\nimport { delay } from \\"src/utils/delay\\";\\nimport { openLinkInBrowser } from \\"src/utils/openLinkInBrowser\\";\\n\\nconst ICON_SIZE = 14;\\n\\nconst rnrImage1 = require(\\"assets/images/demo/rnr-image-1.png\\");\\nconst rnrImage2 = require(\\"assets/images/demo/rnr-image-2.png\\");\\nconst rnrImage3 = require(\\"assets/images/demo/rnr-image-3.png\\");\\nconst rnrImages = [rnrImage1, rnrImage2, rnrImage3];\\n\\nexport default observer(function DemoPodcastListScreen(_props) {\\n const { episodeStore } = useStores();\\n\\n const [refreshing, setRefreshing] = React.useState(false);\\n const [isLoading, setIsLoading] = React.useState(false);\\n\\n // initially, kick off a background refresh without the refreshing UI\\n useEffect(() => {\\n (async function load() {\\n setIsLoading(true);\\n await episodeStore.fetchEpisodes();\\n setIsLoading(false);\\n })();\\n }, [episodeStore]);\\n\\n // simulate a longer refresh, if the refresh is too fast for UX\\n async function manualRefresh() {\\n setRefreshing(true);\\n await Promise.all([episodeStore.fetchEpisodes(), delay(750)]);\\n setRefreshing(false);\\n }\\n\\n return (\\n \\n \\n contentContainerStyle={$listContentContainer}\\n data={episodeStore.episodesForList.slice()}\\n extraData={episodeStore.favorites.length + episodeStore.episodes.length}\\n refreshing={refreshing}\\n estimatedItemSize={177}\\n onRefresh={manualRefresh}\\n ListEmptyComponent={\\n isLoading ? (\\n \\n ) : (\\n \\n )\\n }\\n ListHeaderComponent={\\n \\n \\n {(episodeStore.favoritesOnly ||\\n episodeStore.episodesForList.length > 0) && (\\n \\n \\n episodeStore.setProp(\\n \\"favoritesOnly\\",\\n !episodeStore.favoritesOnly\\n )\\n }\\n variant=\\"switch\\"\\n labelTx=\\"demoPodcastListScreen.onlyFavorites\\"\\n labelPosition=\\"left\\"\\n labelStyle={$labelStyle}\\n accessibilityLabel={translate(\\n \\"demoPodcastListScreen.accessibility.switch\\"\\n )}\\n />\\n \\n )}\\n \\n }\\n renderItem={({ item }) => (\\n episodeStore.toggleFavorite(item)}\\n />\\n )}\\n />\\n \\n );\\n});\\n\\nconst EpisodeCard = observer(function EpisodeCard({\\n episode,\\n isFavorite,\\n onPressFavorite,\\n}: {\\n episode: Episode;\\n onPressFavorite: () => void;\\n isFavorite: boolean;\\n}) {\\n const liked = useSharedValue(isFavorite ? 1 : 0);\\n\\n const imageUri = useMemo(() => {\\n return rnrImages[Math.floor(Math.random() * rnrImages.length)];\\n }, []);\\n\\n // Grey heart\\n const animatedLikeButtonStyles = useAnimatedStyle(() => {\\n return {\\n transform: [\\n {\\n scale: interpolate(liked.value, [0, 1], [1, 0], Extrapolate.EXTEND),\\n },\\n ],\\n opacity: interpolate(liked.value, [0, 1], [1, 0], Extrapolate.CLAMP),\\n };\\n });\\n\\n // Pink heart\\n const animatedUnlikeButtonStyles = useAnimatedStyle(() => {\\n return {\\n transform: [\\n {\\n scale: liked.value,\\n },\\n ],\\n opacity: liked.value,\\n };\\n });\\n\\n /**\\n * Android has a \\"longpress\\" accessibility action. iOS does not, so we just have to use a hint.\\n * @see https://reactnative.dev/docs/accessibility#accessibilityactions\\n */\\n const accessibilityHintProps = useMemo(\\n () =>\\n Platform.select({\\n ios: {\\n accessibilityLabel: episode.title,\\n accessibilityHint: translate(\\n \\"demoPodcastListScreen.accessibility.cardHint\\",\\n {\\n action: isFavorite ? \\"unfavorite\\" : \\"favorite\\",\\n }\\n ),\\n },\\n android: {\\n accessibilityLabel: episode.title,\\n accessibilityActions: [\\n {\\n name: \\"longpress\\",\\n label: translate(\\n \\"demoPodcastListScreen.accessibility.favoriteAction\\"\\n ),\\n },\\n ],\\n onAccessibilityAction: ({ nativeEvent }) => {\\n if (nativeEvent.actionName === \\"longpress\\") {\\n handlePressFavorite();\\n }\\n },\\n },\\n }),\\n [episode, isFavorite]\\n );\\n\\n const handlePressFavorite = () => {\\n onPressFavorite();\\n liked.value = withSpring(liked.value ? 0 : 1);\\n };\\n\\n const handlePressCard = () => {\\n openLinkInBrowser(episode.enclosure.link);\\n };\\n\\n const ButtonLeftAccessory: ComponentType = useMemo(\\n () =>\\n function ButtonLeftAccessory() {\\n return (\\n \\n \\n \\n \\n \\n \\n \\n \\n );\\n },\\n []\\n );\\n\\n return (\\n \\n \\n {episode.datePublished.textLabel}\\n \\n \\n {episode.duration.textLabel}\\n \\n \\n }\\n content={`${episode.parsedTitleAndSubtitle.title} - ${episode.parsedTitleAndSubtitle.subtitle}`}\\n {...accessibilityHintProps}\\n RightComponent={}\\n FooterComponent={\\n \\n }\\n />\\n );\\n});\\n\\nconst $screenContentContainer: ViewStyle = {\\n flex: 1,\\n};\\n\\nconst $listContentContainer: ContentStyle = {\\n paddingHorizontal: spacing.lg,\\n paddingTop: spacing.lg + spacing.xl,\\n paddingBottom: spacing.lg,\\n};\\n\\nconst $heading: ViewStyle = {\\n marginBottom: spacing.md,\\n};\\n\\nconst $item: ViewStyle = {\\n padding: spacing.md,\\n marginTop: spacing.md,\\n minHeight: 120,\\n};\\n\\nconst $itemThumbnail: ImageStyle = {\\n marginTop: spacing.sm,\\n borderRadius: 50,\\n alignSelf: \\"flex-start\\",\\n};\\n\\nconst $toggle: ViewStyle = {\\n marginTop: spacing.md,\\n};\\n\\nconst $labelStyle: TextStyle = {\\n textAlign: \\"left\\",\\n};\\n\\nconst $iconContainer: ViewStyle = {\\n height: ICON_SIZE,\\n width: ICON_SIZE,\\n flexDirection: \\"row\\",\\n marginEnd: spacing.sm,\\n};\\n\\nconst $metadata: TextStyle = {\\n color: colors.textDim,\\n marginTop: spacing.xs,\\n flexDirection: \\"row\\",\\n};\\n\\nconst $metadataText: TextStyle = {\\n color: colors.textDim,\\n marginEnd: spacing.md,\\n marginBottom: spacing.xs,\\n};\\n\\nconst $favoriteButton: ViewStyle = {\\n borderRadius: 17,\\n marginTop: spacing.md,\\n justifyContent: \\"flex-start\\",\\n backgroundColor: colors.palette.neutral300,\\n borderColor: colors.palette.neutral300,\\n paddingHorizontal: spacing.md,\\n paddingTop: spacing.xxxs,\\n paddingBottom: 0,\\n minHeight: 32,\\n alignSelf: \\"flex-start\\",\\n};\\n\\nconst $unFavoriteButton: ViewStyle = {\\n borderColor: colors.palette.primary100,\\n backgroundColor: colors.palette.primary100,\\n};\\n\\nconst $emptyState: ViewStyle = {\\n marginTop: spacing.xxl,\\n};\\n\\nconst $emptyStateImage: ImageStyle = {\\n transform: [{ scaleX: isRTL ? -1 : 1 }],\\n};\\n\\nimport React from \\"react\\";\\nimport * as Application from \\"expo-application\\";\\nimport { Linking, Platform, TextStyle, View, ViewStyle } from \\"react-native\\";\\nimport { Button, ListItem, Screen, Text } from \\"src/components\\";\\nimport { colors, spacing } from \\"src/theme\\";\\nimport { isRTL } from \\"src/i18n\\";\\nimport { useStores } from \\"src/models\\";\\n\\nfunction openLinkInBrowser(url: string) {\\n Linking.canOpenURL(url).then((canOpen) => canOpen && Linking.openURL(url));\\n}\\n\\nexport default function DemoDebugScreen() {\\n const {\\n authenticationStore: { logout },\\n } = useStores();\\n\\n const usingHermes =\\n typeof HermesInternal === \\"object\\" && HermesInternal !== null;\\n // @ts-expect-error\\n const usingFabric = global.nativeFabricUIManager != null;\\n\\n const demoReactotron = React.useMemo(\\n () => async () => {\\n if (__DEV__) {\\n console.tron.display({\\n name: \\"DISPLAY\\",\\n value: {\\n appId: Application.applicationId,\\n appName: Application.applicationName,\\n appVersion: Application.nativeApplicationVersion,\\n appBuildVersion: Application.nativeBuildVersion,\\n hermesEnabled: usingHermes,\\n },\\n important: true,\\n });\\n }\\n },\\n []\\n );\\n\\n return (\\n \\n \\n openLinkInBrowser(\\"https://github.com/infinitered/ignite/issues\\")\\n }\\n />\\n \\n \\n \\n App Id\\n {Application.applicationId}\\n \\n }\\n />\\n \\n App Name\\n {Application.applicationName}\\n \\n }\\n />\\n \\n App Version\\n {Application.nativeApplicationVersion}\\n \\n }\\n />\\n \\n App Build Version\\n {Application.nativeBuildVersion}\\n \\n }\\n />\\n \\n Hermes Enabled\\n {String(usingHermes)}\\n \\n }\\n />\\n \\n Fabric Enabled\\n {String(usingFabric)}\\n \\n }\\n />\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n );\\n}\\n\\nconst $container: ViewStyle = {\\n paddingTop: spacing.lg + spacing.xl,\\n paddingBottom: spacing.xxl,\\n paddingHorizontal: spacing.lg,\\n};\\n\\nconst $title: TextStyle = {\\n marginBottom: spacing.xxl,\\n};\\n\\nconst $reportBugsLink: TextStyle = {\\n color: colors.tint,\\n marginBottom: spacing.lg,\\n alignSelf: isRTL ? \\"flex-start\\" : \\"flex-end\\",\\n};\\n\\nconst $item: ViewStyle = {\\n marginBottom: spacing.md,\\n};\\n\\nconst $itemsContainer: ViewStyle = {\\n marginBottom: spacing.xl,\\n};\\n\\nconst $button: ViewStyle = {\\n marginBottom: spacing.xs,\\n};\\n\\nconst $buttonContainer: ViewStyle = {\\n marginBottom: spacing.md,\\n};\\n\\nconst $hint: TextStyle = {\\n color: colors.palette.neutral600,\\n fontSize: 12,\\n lineHeight: 15,\\n paddingBottom: spacing.lg,\\n};\\n\\nmv src/screens/DemoShowroomScreen src/components/Showroom\\nrm src/components/Showroom/DemoShowroomScreen.tsx\\n\\n> import { ReactElement } from \\"react\\";\\n>\\n> export interface Demo {\\n> name: string;\\n> description: string;\\n> data: ReactElement[];\\n> }\\n\\nconst WebListItem: FC = ({ item, sectionIndex }) => {\\n const sectionSlug = item.name.toLowerCase();\\n\\n return (\\n \\n \\n {item.name}\\n \\n {item.useCases.map((u) => {\\n const itemSlug = slugify(u);\\n\\n return (\\n \\n {u}\\n \\n );\\n })}\\n \\n );\\n};\\n\\nconst NativeListItem: FC = ({\\n item,\\n sectionIndex,\\n handleScroll,\\n}) => (\\n \\n handleScroll?.(sectionIndex)}\\n preset=\\"bold\\"\\n style={$menuContainer}\\n >\\n {item.name}\\n \\n {item.useCases.map((u, index) => (\\n handleScroll?.(sectionIndex, index + 1)}\\n text={u}\\n rightIcon={isRTL ? \\"caretLeft\\" : \\"caretRight\\"}\\n />\\n ))}\\n \\n);\\n\\nconst ShowroomListItem = Platform.select({\\n web: WebListItem,\\n default: NativeListItem,\\n});\\n\\nconst ShowroomListItem: FC = ({ item, sectionIndex }) => {\\n const sectionSlug = item.name.toLowerCase();\\n\\n return (\\n \\n \\n {item.name}\\n \\n {item.useCases.map((u) => {\\n const itemSlug = slugify(u);\\n return (\\n \\n \\n \\n );\\n })}\\n \\n );\\n};\\n\\nimport React, { FC, useEffect, useRef, useState } from \\"react\\";\\nimport {\\n Image,\\n ImageStyle,\\n SectionList,\\n TextStyle,\\n View,\\n ViewStyle,\\n} from \\"react-native\\";\\nimport { Drawer } from \\"react-native-drawer-layout\\";\\nimport { type ContentStyle } from \\"@shopify/flash-list\\";\\nimport { ListItem, ListView, ListViewRef, Screen, Text } from \\"src/components\\";\\nimport { isRTL } from \\"src/i18n\\";\\nimport { colors, spacing } from \\"src/theme\\";\\nimport { useSafeAreaInsetsStyle } from \\"src/utils/useSafeAreaInsetsStyle\\";\\nimport * as Demos from \\"src/components/Showroom/demos\\";\\nimport { DrawerIconButton } from \\"src/components/Showroom/DrawerIconButton\\";\\nimport { Link, useLocalSearchParams } from \\"expo-router\\";\\n\\nconst logo = require(\\"assets/images/logo.png\\");\\n\\ninterface DemoListItem {\\n item: { name: string; useCases: string[] };\\n sectionIndex: number;\\n onPress?: () => void;\\n}\\n\\nconst slugify = (str: string) =>\\n str\\n .toLowerCase()\\n .trim()\\n .replace(/[^\\\\w\\\\s-]/g, \\"\\")\\n .replace(/[\\\\s_-]+/g, \\"-\\")\\n .replace(/^-+|-+$/g, \\"\\");\\n\\nconst ShowroomListItem: FC = ({\\n item,\\n sectionIndex,\\n onPress,\\n}) => {\\n const sectionSlug = item.name.toLowerCase();\\n\\n return (\\n \\n \\n {item.name}\\n \\n {item.useCases.map((u) => {\\n const itemSlug = slugify(u);\\n return (\\n \\n \\n \\n );\\n })}\\n \\n );\\n};\\n\\nexport default function DemoShowroomScreen() {\\n const [open, setOpen] = useState(false);\\n const timeout = useRef>();\\n const listRef = useRef(null);\\n const menuRef = useRef>(null);\\n\\n const params = useLocalSearchParams<{\\n sectionSlug?: string;\\n itemSlug?: string;\\n }>();\\n\\n // handle scroll when section/item params change\\n React.useEffect(() => {\\n if (Object.keys(params).length > 0) {\\n const demoValues = Object.values(Demos);\\n const findSectionIndex = demoValues.findIndex(\\n (x) => x.name.toLowerCase() === params.sectionSlug\\n );\\n let findItemIndex = 0;\\n if (params.itemSlug) {\\n try {\\n findItemIndex =\\n demoValues[findSectionIndex].data.findIndex(\\n (u) => slugify(u.props.name) === params.itemSlug\\n ) + 1;\\n } catch (err) {\\n console.error(err);\\n }\\n }\\n handleScroll(findSectionIndex, findItemIndex);\\n }\\n }, [params]);\\n\\n const toggleDrawer = () => {\\n if (!open) {\\n setOpen(true);\\n } else {\\n setOpen(false);\\n }\\n };\\n\\n const handleScroll = (sectionIndex: number, itemIndex = 0) => {\\n listRef.current?.scrollToLocation({\\n animated: true,\\n itemIndex,\\n sectionIndex,\\n });\\n };\\n\\n const scrollToIndexFailed = (info: {\\n index: number;\\n highestMeasuredFrameIndex: number;\\n averageItemLength: number;\\n }) => {\\n listRef.current?.getScrollResponder()?.scrollToEnd();\\n timeout.current = setTimeout(\\n () =>\\n listRef.current?.scrollToLocation({\\n animated: true,\\n itemIndex: info.index,\\n sectionIndex: 0,\\n }),\\n 50\\n );\\n };\\n\\n useEffect(() => {\\n return () => timeout.current && clearTimeout(timeout.current);\\n }, []);\\n\\n const $drawerInsets = useSafeAreaInsetsStyle([\\"top\\"]);\\n\\n return (\\n setOpen(true)}\\n onClose={() => setOpen(false)}\\n drawerType={\\"slide\\"}\\n drawerPosition={isRTL ? \\"right\\" : \\"left\\"}\\n renderDrawerContent={() => (\\n \\n \\n \\n \\n\\n \\n ref={menuRef}\\n contentContainerStyle={$listContentContainer}\\n estimatedItemSize={250}\\n data={Object.values(Demos).map((d) => ({\\n name: d.name,\\n useCases: d.data.map((u) => u.props.name as string),\\n }))}\\n keyExtractor={(item) => item.name}\\n renderItem={({ item, index: sectionIndex, onPress }) => (\\n \\n )}\\n />\\n \\n )}\\n >\\n \\n \\n\\n item}\\n renderSectionFooter={() => }\\n ListHeaderComponent={\\n \\n \\n \\n }\\n onScrollToIndexFailed={scrollToIndexFailed}\\n renderSectionHeader={({ section }) => {\\n return (\\n \\n \\n {section.name}\\n \\n {section.description}\\n \\n );\\n }}\\n />\\n \\n \\n );\\n}\\n\\nconst $screenContainer: ViewStyle = {\\n flex: 1,\\n};\\n\\nconst $drawer: ViewStyle = {\\n backgroundColor: colors.background,\\n flex: 1,\\n};\\n\\nconst $listContentContainer: ContentStyle = {\\n paddingHorizontal: spacing.lg,\\n};\\n\\nconst $sectionListContentContainer: ViewStyle = {\\n paddingHorizontal: spacing.lg,\\n};\\n\\nconst $heading: ViewStyle = {\\n marginBottom: spacing.xxxl,\\n};\\n\\nconst $logoImage: ImageStyle = {\\n height: 42,\\n width: 77,\\n};\\n\\nconst $logoContainer: ViewStyle = {\\n alignSelf: \\"flex-start\\",\\n justifyContent: \\"center\\",\\n height: 56,\\n paddingHorizontal: spacing.lg,\\n};\\n\\nconst $demoItemName: TextStyle = {\\n fontSize: 24,\\n marginBottom: spacing.md,\\n};\\n\\nconst $demoItemDescription: TextStyle = {\\n marginBottom: spacing.xxl,\\n};\\n\\nconst $demoUseCasesSpacer: ViewStyle = {\\n paddingBottom: spacing.xxl,\\n};\\n\\nnpx uri-scheme open exp://localhost:8081/--/showroom --ios\\n\\nrm src/app.tsx\\nrm -rf src/screens\\nrm -rf src/navigators\\nrm -rf ignite/templates/screen\\n\\n// error-line\\nimport {\\n goBack,\\n resetRoot,\\n navigate,\\n} from \\"src/navigators/navigationUtilities\\";\\n// success-line\\nimport { router } from \\"expo-router\\";\\n// ...\\n// error-line-start\\nreactotron.onCustomCommand({\\n title: \\"Reset Navigation State\\",\\n description: \\"Resets the navigation state\\",\\n command: \\"resetNavigation\\",\\n handler: () => {\\n Reactotron.log(\\"resetting navigation state\\");\\n resetRoot({ index: 0, routes: [] });\\n },\\n});\\n// error-line-end\\n\\nreactotron.onCustomCommand<[{ name: \\"route\\"; type: ArgType.String }]>({\\n command: \\"navigateTo\\",\\n handler: (args) => {\\n const { route } = args ?? {};\\n if (route) {\\n Reactotron.log(`Navigating to: ${route}`);\\n // error-line\\n navigate(route as any); // this should be tied to the navigator, but since this is for debugging, we can navigate to illegal routes\\n // success-line-start\\n // @ts-ignore - bypass Expo Router Typed Routes\\n router.push(route);\\n // success-line-end\\n } else {\\n Reactotron.log(\\"Could not navigate. No route provided.\\");\\n }\\n },\\n title: \\"Navigate To Screen\\",\\n description: \\"Navigates to a screen by name.\\",\\n args: [{ name: \\"route\\", type: ArgType.String }],\\n});\\n\\nreactotron.onCustomCommand({\\n title: \\"Go Back\\",\\n description: \\"Goes back\\",\\n command: \\"goBack\\",\\n handler: () => {\\n Reactotron.log(\\"Going back\\");\\n // error-line\\n goBack();\\n // success-line\\n router.back();\\n },\\n});\\n\\nERROR Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?\\n\\n/**\\n * A styled row component that can be used in FlatList, SectionList, or by itself.\\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/components/ListItem/}\\n * @param {ListItemProps} props - The props for the `ListItem` component.\\n * @returns {JSX.Element} The rendered `ListItem` component.\\n */\\n// error-line\\n export function ListItem(props: ListItemProps) {\\n// success-line-start\\n export const ListItem = React.forwardRef(function ListItem(\\n props: ListItemProps,\\n ref,\\n) {\\n// success-line-end\\n const {\\n bottomSeparator,\\n children,\\n height = 56,\\n LeftComponent,\\n leftIcon,\\n leftIconColor,\\n RightComponent,\\n rightIcon,\\n rightIconColor,\\n style,\\n text,\\n TextProps,\\n topSeparator,\\n tx,\\n txOptions,\\n textStyle: $textStyleOverride,\\n containerStyle: $containerStyleOverride,\\n ...TouchableOpacityProps\\n } = props\\n\\n const $textStyles = [$textStyle, $textStyleOverride, TextProps?.style]\\n\\n const $containerStyles = [\\n topSeparator && $separatorTop,\\n bottomSeparator && $separatorBottom,\\n $containerStyleOverride,\\n ]\\n\\n const $touchableStyles = [$touchableStyle, { minHeight: height }, style]\\n\\n return (\\n // error-line\\n \\n // success-line\\n \\n \\n \\n\\n \\n {children}\\n \\n\\n \\n \\n \\n )\\n //error-line\\n}\\n //success-line\\n})\\n\\n\\n","lastUpdated":"5 months ago","title":"Expo Router","publish_date":"2024-01-25","doc_name":"ExpoRouter.md"},{"author":"Joshua Yoes","content":"---\\ndestinationDir: app/components/specs\\n---\\n// https://reactnativetesting.io/component/testing/\\n\\nimport React from \\"react\\"\\nimport { fireEvent, render, screen } from \\"@testing-library/react-native\\"\\nimport { <%= props.pascalCaseName %> } from \\"../<%= props.pascalCaseName %>\\"\\n\\ndescribe(\\"<%= props.pascalCaseName %>\\", () => {\\n it(\\"renders\\", () => {\\n render(<<%= props.pascalCaseName %> />)\\n expect(screen.getByText(\\"Hello\\")).toBeTruthy()\\n })\\n})\\n\\n","lastUpdated":"5 months ago","title":"Generator for Component Tests","publish_date":"2022-10-10","doc_name":"GeneratorComponentTests.md"},{"author":"Trevor Coleman","content":" npx ignite-cli@latest new PowerSyncIgnite --remove-demo --workflow=cng --yes\\n\\nnpx expo install \\\\\\n @powersync/react-native \\\\\\n @journeyapps/react-native-quick-sqlite \\n\\nnpx expo install @supabase/supabase-js\\n\\nnpx expo install @react-native-async-storage/async-storage\\n\\n// `metro.config.js`:\\n// Learn more https://docs.expo.io/guides/customizing-metro\\nconst { getDefaultConfig } = require(\\"expo/metro-config\\")\\n\\n/** @type {import(\'expo/metro-config\').MetroConfig} */\\nconst config = getDefaultConfig(__dirname)\\n\\nconfig.transformer.getTransformOptions = async () => ({\\n transform: {\\n // Inline requires are very useful for deferring loading of large dependencies/components.\\n // For example, we use it in app.tsx to conditionally load Reactotron.\\n // However, this comes with some gotchas.\\n // Read more here: https://reactnative.dev/docs/optimizing-javascript-loading\\n // And here: https://github.com/expo/expo/issues/27279#issuecomment-1971610698\\n inlineRequires: {\\n blockList: {\\n [require.resolve(\\"@powersync/react-native\\")]: true,\\n\\n // require() calls anywhere else will be inlined, unless they\\n // match any entry nonInlinedRequires.\\n },\\n },\\n },\\n})\\n\\n// This helps support certain popular third-party libraries\\n// such as Firebase that use the extension cjs.\\nconfig.resolver.sourceExts.push(\\"cjs\\")\\n\\nmodule.exports = config\\n\\n\\n// `app/config/config.base.ts`:\\n\\n// update the interface to include the new properties\\nexport interface ConfigBaseProps {\\n // Existing config properties\\n\\n // success-line\\n supabaseUrl: string\\n // success-line\\n supabaseAnonKey: string\\n}\\n\\n// Add the new properties to the config object\\nconst BaseConfig: ConfigBaseProps = {\\n // Existing config values\\n // success-line\\n supabaseUrl: \'<>\',\\n // success-line\\n supabaseAnonKey: \'<>\',\\n}\\n\\nexport default BaseConfig;\\n\\n\\n// app/services/database/supabase.ts\\nimport AsyncStorage from \'@react-native-async-storage/async-storage\'\\nimport { createClient } from \\"@supabase/supabase-js\\"\\nimport Config from \'../../config\'\\n\\nexport const supabase = createClient(Config.supabaseUrl, Config.supabaseAnonKey, {\\n auth: {\\n persistSession: true, storage: AsyncStorage,\\n },\\n})\\n\\n// app/services/database/use-auth.tsx\\nimport { User } from \\"@supabase/supabase-js\\"\\nimport { supabase } from \\"app/services/database/supabase\\"\\nimport React, { createContext, PropsWithChildren, useCallback, useContext, useMemo, useState } from \\"react\\"\\n\\ntype AuthContextType = {\\n signIn: (email: string, password: string) => void\\n signUp: (email: string, password: string) => void\\n signOut: () => Promise\\n signedIn: boolean\\n loading: boolean\\n error: string\\n user: User | null\\n}\\n\\n// We initialize the context with null to ensure that it is not used outside of the provider\\nconst AuthContext = createContext(null)\\n\\n/**\\n * AuthProvider manages the authentication state and provides the necessary methods to sign in, sign up and sign out.\\n */\\nexport const AuthProvider = ({ children }: PropsWithChildren) => {\\n const [signedIn, setSignedIn] = useState(false)\\n const [loading, setLoading] = useState(false)\\n const [error, setError] = useState(\\"\\")\\n const [user, setUser] = useState(null)\\n\\n\\n // Sign in with provided email and password\\n const signIn = useCallback(async (email: string, password: string) => {\\n setLoading(true)\\n setError(\\"\\")\\n setUser(null)\\n \\n try {\\n // get the session and user from supabase\\n const { \\n data: {session, user}, \\n error \\n } = await supabase.auth.signInWithPassword({ email, password })\\n \\n // if we have a session and user, sign them in\\n if (session && user) {\\n setSignedIn(true)\\n setUser(user)\\n // otherwise sign them out and set an error\\n } else {\\n throw new Error(error?.message);\\n setSignedIn(false)\\n setUser(null)\\n }\\n } catch (error: any) {\\n setError(error?.message ?? \\"Unknown error\\")\\n setSignedIn(false)\\n setUser(null)\\n } finally {\\n setLoading(false)\\n }\\n }, [\\n setSignedIn, setLoading, setError, setUser, supabase\\n ])\\n\\n // Create a new account with provided email and password\\n const signUp = useCallback(async (email: string, password: string) => {\\n setLoading(true)\\n setError(\\"\\")\\n setUser(null)\\n try {\\n const { data, error } = await supabase.auth.signUp({ email, password })\\n if (error) {\\n setSignedIn(false)\\n setError(error.message)\\n } else if (data.session) {\\n await supabase.auth.setSession(data.session)\\n setSignedIn(true)\\n setUser(data.user)\\n }\\n } catch (error: any) {\\n setUser(null)\\n setSignedIn(false)\\n setError(error?.message ?? \\"Unknown error\\")\\n } finally {\\n setLoading(false)\\n }\\n }, [\\n setSignedIn, setLoading, setError, setUser, supabase\\n ])\\n\\n // Sign out the current user\\n const signOut = useCallback(async () => {\\n setLoading(true)\\n await supabase.auth.signOut()\\n setError(\\"\\")\\n setSignedIn(false)\\n setLoading(false)\\n setUser(null)\\n }, [\\n setSignedIn, setLoading, setError, setUser, supabase\\n ])\\n\\n // Always memoize context values as they can cause unnecessary re-renders if they aren\'t stable!\\n const value = useMemo(() => ({\\n signIn, signOut, signUp, signedIn, loading, error, user\\n }), [\\n signIn, signOut, signUp, signedIn, loading, error, user\\n ])\\n return { children }\\n}\\n\\nexport const useAuth = () => {\\n const context = useContext(AuthContext)\\n\\n // It\'s a good idea to throw an error if the context is null, as it means the hook is being used outside of the provider\\n if (context === null) {\\n throw new Error(\'useAuthContext must be used within a AuthProvider\')\\n }\\n return context\\n}\\n\\n\\n// app/app.tsx\\n// ...other imports\\n// success-line\\nimport { AuthProvider } from \\"app/services/database/use-auth\\"\\n\\n// ...\\nfunction App(props: AppProps) {\\n // ...\\n return (\\n // success-line\\n \\n \\n {/* ... */ }\\n \\n // success-line\\n \\n )\\n}\\n\\n\\n\\nnpx ignite-cli generate screen Auth\\n\\n// app/screens/AuthScreen.tsx\\nimport { AppStackScreenProps } from \\"app/navigators\\"\\nimport { Button, Screen, Text, TextField } from \\"app/components\\"\\nimport { useAuth } from \\"app/services/database/use-auth\\"\\nimport React, { useEffect, useState } from \\"react\\"\\nimport { ActivityIndicator, Modal, TextStyle, View, ViewStyle } from \\"react-native\\"\\nimport { colors, spacing } from \\"../theme\\"\\n\\ninterface AuthScreenProps extends AppStackScreenProps<\\"Auth\\"> {}\\n\\nexport const AuthScreen: React.FC = ({ navigation }) => {\\n const { signUp, signIn, loading, error, user } = useAuth()\\n const [email, setEmail] = useState(\\"\\")\\n const [password, setPassword] = useState(\\"\\")\\n\\n const handleSignIn = async () => {\\n signIn(email, password)\\n }\\n\\n const handleSignUp = async () => {\\n signUp(email, password)\\n }\\n\\n useEffect(() => {\\n if (user) {\\n navigation.navigate(\\"Welcome\\")\\n }\\n }, [user])\\n\\n return (\\n \\n PowerSync + Supabase\\n Sign in or Create Account\\n \\n \\n\\n \\n \\n\\n \\n \\n { error ? : null }\\n \\n \\n \\n \\n \\n \\n )\\n}\\n\\nconst $container: ViewStyle = {\\n backgroundColor: colors.background,\\n flex: 1,\\n justifyContent: \\"center\\",\\n paddingHorizontal: spacing.lg,\\n}\\n\\nconst $inputContainer: TextStyle = {\\n marginTop: spacing.md,\\n}\\n\\nconst $inputWrapper: TextStyle = {\\n backgroundColor: colors.palette.neutral100,\\n}\\nconst $modalBackground: ViewStyle = {\\n alignItems: \\"center\\",\\n backgroundColor: \\"#00000040\\",\\n flex: 1,\\n flexDirection: \\"column\\",\\n justifyContent: \\"space-around\\",\\n}\\n\\nconst $error: TextStyle = {\\n color: colors.error,\\n marginVertical: spacing.md,\\n textAlign: \\"center\\",\\n width: \\"100%\\",\\n fontSize: 20,\\n}\\n\\nconst $buttonContainer: ViewStyle = {\\n display: \\"flex\\",\\n flexDirection: \\"column\\",\\n justifyContent: \\"space-between\\",\\n marginVertical: spacing.md,\\n}\\n\\nconst $button: ViewStyle = {\\n marginTop: spacing.xs,\\n}\\n\\nnpx ignite-cli generate component SignOutButton\\n\\n// app/components/SignOutButton.tsx\\n\\nimport { Button } from \\"app/components/Button\\"\\nimport { useAuth } from \\"app/services/database/use-auth\\"\\nimport * as React from \\"react\\"\\nimport { StyleProp, View, ViewStyle } from \\"react-native\\"\\nimport { observer } from \\"mobx-react-lite\\"\\nimport { spacing } from \\"app/theme\\"\\n\\nexport interface SignOutButtonProps {\\n /**\\n * An optional style override useful for padding & margin.\\n */\\n style?: StyleProp\\n}\\n\\n/**\\n * Describe your component here\\n */\\nexport const SignOutButton = observer(function SignOutButton(props: SignOutButtonProps) {\\n const { style } = props\\n const $styles = [$container, style]\\n\\n const { signOut } = useAuth()\\n\\n const handleSignOut = async () => {\\n await signOut()\\n }\\n\\n return (\\n \\n \\n \\n )\\n})\\n\\nconst $container: ViewStyle = {\\n padding: spacing.md,\\n}\\n\\n// app/screens/WelcomeScreen.tsx\\nimport { SignOutButton } from \\"app/components\\"\\nimport { observer } from \\"mobx-react-lite\\"\\nimport React, { FC } from \\"react\\"\\nimport { ViewStyle } from \\"react-native\\"\\nimport { SafeAreaView } from \\"react-native-safe-area-context\\"\\nimport { colors } from \\"../theme\\"\\n\\ninterface WelcomeScreenProps\\n extends NativeStackScreenProps {}\\n\\nexport const WelcomeScreen: FC = observer(function WelcomeScreen() {\\n return (\\n \\n \\n \\n )\\n})\\n\\nconst $container: ViewStyle = {\\n flex: 1,\\n backgroundColor: colors.palette.neutral300,\\n display: \\"flex\\",\\n justifyContent: \\"flex-start\\",\\n height: \\"100%\\",\\n flexDirection: \\"column\\",\\n}\\n\\n// app/navigators/AppNavigator.tsx\\n\\n// success-line\\nimport { useAuth } from \\"app/services/database/use-auth\\"\\n\\n//...\\n\\nconst AppStack = observer(function AppStack() {\\n // Fetch the user from the auth context\\n // success-line\\n // success-line\\n const { signedIn } = useAuth()\\n return (\\n \\n {/**\\n * by wrapping the Welcome screen in a conditional, we ensure that\\n * the user can only access it if they are signed in\\n */ }\\n // success-line\\n { signedIn\\n // success-line\\n ? \\n // success-line\\n : null\\n // success-line\\n }\\n \\n {/* IGNITE_GENERATOR_ANCHOR_APP_STACK_SCREENS */ }\\n \\n )\\n})\\n\\n// ...\\n\\n// app/config/config.base.ts:\\n\\n// update the interface to include the new properties\\nexport interface ConfigBaseProps {\\n // Existing config properties\\n supabaseUrl: string\\n supabaseAnonKey: string\\n // success-line\\n powersyncUrl: string\\n}\\n\\n// Add the new properties to the config object\\nconst BaseConfig: ConfigBaseProps = {\\n // Existing config values\\n supabaseUrl: \'<>\',\\n supabaseAnonKey: \'<>\',\\n // success-line\\n powersyncUrl: \'<>\',\\n}\\n\\n// app/services/database/schema.ts\\nimport { column, Schema, TableV2 } from \'@powersync/react-native\';\\n\\nexport const LISTS_TABLE = \'lists\';\\nexport const TODOS_TABLE = \'todos\';\\n\\nconst todos = new TableV2(\\n {\\n list_id: column.text,\\n created_at: column.text,\\n completed_at: column.text,\\n description: column.text,\\n created_by: column.text,\\n completed_by: column.text,\\n completed: column.integer\\n },\\n { indexes: { list: [\'list_id\'] } }\\n);\\n\\nconst lists = new TableV2({\\n created_at: column.text,\\n name: column.text,\\n owner_id: column.text\\n});\\n\\nexport const AppSchema = new Schema({\\n todos,\\n lists\\n});\\n\\nexport type Database = (typeof AppSchema)[\'types\'];\\nexport type TodoRecord = Database[\'todos\'];\\n// OR:\\n// export type Todo = RowType;\\n\\nexport type ListRecord = Database[\'lists\'];\\n\\n\\n// from @journeyapps/powersync-sdk-common \\nexport interface PowerSyncBackendConnector {\\n /** Allows the PowerSync client to retrieve an authentication token from your backend\\n * which is used to authenticate against the PowerSync service.\\n *\\n * This should always fetch a fresh set of credentials - don\'t use cached\\n * values.\\n *\\n * Return null if the user is not signed in. Throw an error if credentials\\n * cannot be fetched due to a network error or other temporary error.\\n *\\n * This token is kept for the duration of a sync connection.\\n */\\n fetchCredentials: () => Promise\\n\\n /** Upload local changes to the app backend.\\n *\\n * Use {@link AbstractPowerSyncDatabase.getCrudBatch} to get a batch of changes to upload.\\n *\\n * Any thrown errors will result in a retry after the configured wait period (default: 5 seconds).\\n */\\n uploadData: (database: AbstractPowerSyncDatabase) => Promise\\n}\\n\\n// app/services/database/supabase.ts\\nimport {\\n AbstractPowerSyncDatabase,\\n CrudEntry,\\n PowerSyncBackendConnector,\\n UpdateType,\\n PowerSyncCredentials\\n} from \\"@powersync/react-native\\"\\nimport AsyncStorage from \'@react-native-async-storage/async-storage\'\\nimport { createClient } from \\"@supabase/supabase-js\\"\\nimport Config from \\"../../config\\"\\n\\nexport const supabase = createClient(Config.supabaseUrl, Config.supabaseAnonKey, {\\n auth: {\\n persistSession: true, storage: AsyncStorage,\\n },\\n})\\n\\n\\n// This function fetches the session token from Supabase, it should return null if the user is not signed in, and the session token if they are.\\nasync function fetchCredentials(): Promise {\\n const { data: { session }, error } = await supabase.auth.getSession()\\n\\n if (error) {\\n throw new Error(`Could not fetch Supabase credentials: ${ error }`)\\n }\\n\\n if (!session) {\\n return null\\n }\\n\\n return {\\n endpoint: Config.powersyncUrl,\\n token: session.access_token ?? \\"\\",\\n expiresAt: session.expires_at\\n ? new Date(session.expires_at * 1000)\\n : undefined\\n }\\n}\\n\\n\\n// Regexes for response codes indicating unrecoverable errors.\\nconst FATAL_RESPONSE_CODES = [\\n /^22...$/, // Data Exception\\n /^23...$/, // Integrity Constraint Violation\\n /^42501$/, // INSUFFICIENT PRIVILEGE\\n]\\n\\n// PowerSync will call this function to upload data to the backend\\nconst uploadData: (database: AbstractPowerSyncDatabase) => Promise = async (database) => {\\n const transaction = await database.getNextCrudTransaction()\\n\\n if (!transaction) {\\n return\\n }\\n\\n\\n let lastOp: CrudEntry | null = null\\n try {\\n // Note: If transactional consistency is important, use database functions\\n // or edge functions to process the entire transaction in a single call.\\n for (const op of transaction.crud) {\\n lastOp = op\\n const table = supabase.from(op.table)\\n let result: any = null\\n switch (op.op) {\\n case UpdateType.PUT:\\n // eslint-disable-next-line no-case-declarations\\n const record = { ...op.opData, id: op.id }\\n result = await table.upsert(record)\\n break\\n case UpdateType.PATCH:\\n result = await table.update(op.opData).eq(\'id\', op.id)\\n break\\n case UpdateType.DELETE:\\n result = await table.delete().eq(\'id\', op.id)\\n break\\n }\\n\\n if (result?.error) {\\n throw new Error(`Could not ${ op.op } data to Supabase error: ${ JSON.stringify(result) }`)\\n }\\n }\\n\\n await transaction.complete()\\n } catch (ex: any) {\\n console.debug(ex)\\n if (typeof ex.code === \'string\' && FATAL_RESPONSE_CODES.some((regex) => regex.test(ex.code))) {\\n /**\\n * Instead of blocking the queue with these errors,\\n * discard the (rest of the) transaction.\\n *\\n * Note that these errors typically indicate a bug in the application.\\n * If protecting against data loss is important, save the failing records\\n * elsewhere instead of discarding, and/or notify the user.\\n */\\n console.error(`Data upload error - discarding ${ lastOp }`, ex)\\n await transaction.complete()\\n } else {\\n // Error may be retryable - e.g. network error or temporary server error.\\n // Throwing an error here causes this call to be retried after a delay.\\n throw ex\\n }\\n }\\n}\\n\\nexport const supabaseConnector: PowerSyncBackendConnector = {\\n fetchCredentials, uploadData,\\n}\\n\\n// app/services/database/database.tsx\\nimport { SupabaseClient } from \\"@supabase/supabase-js\\"\\nimport { useAuth } from \\"./use-auth\\"\\nimport React, { PropsWithChildren, useEffect } from \\"react\\"\\nimport {\\n AbstractPowerSyncDatabase,\\n PowerSyncContext,\\n PowerSyncDatabase,\\n} from \\"@powersync/react-native\\"\\nimport { supabase, supabaseConnector } from \\"./supabase\\" // Adjust the path as needed\\nimport { AppSchema } from \\"./schema\\" // Adjust the path as needed\\n\\nexport class Database {\\n // We expose the PowerSync and Supabase instances for easy access elsewhere in the app\\n powersync: AbstractPowerSyncDatabase\\n supabase: SupabaseClient = supabase\\n\\n /**\\n * Initialize the Database class with a new PowerSync instance\\n */\\n constructor() {\\n this.powersync = new PowerSyncDatabase({\\n database: {\\n dbFilename: \\"sqlite.db\\"\\n },\\n schema: AppSchema\\n })\\n }\\n\\n /**\\n * Initialize the PowerSync instance and connect it to the Supabase backend.\\n * This will call `fetchCredentials` on the Supabase connector to get the session token.\\n * So if your database requires authentication, the user will need to be signed in before this is\\n * called.\\n */\\n async init() {\\n await this.powersync.init()\\n await this.powersync.connect(supabaseConnector)\\n }\\n\\n async disconnect() {\\n await this.powersync.disconnectAndClear()\\n }\\n}\\n\\nconst database = new Database()\\n\\n// A context to provide our singleton to the rest of the app\\nconst DatabaseContext = React.createContext(null)\\n\\nexport const useDatabase = () => {\\n const context: Database | null = React.useContext(DatabaseContext)\\n if (!context) {\\n throw new Error(\\"useDatabase must be used within a DatabaseProvider\\")\\n }\\n\\n return context\\n}\\n\\n// Finally, we create a provider component that initializes the database and provides it to the app\\nexport function DatabaseProvider({ children }: PropsWithChildren) {\\n const { user } = useAuth()\\n useEffect(() => {\\n if (user) {\\n database.init().catch(console.error)\\n }\\n }, [database, user])\\n return (\\n \\n \\n { children }\\n \\n \\n )\\n}\\n\\n// app/app.tsx\\n\\n\\n//... other imports\\n// success-line\\n// Import the provider\\n// success-line\\nimport { DatabaseProvider } from \\"app/services/database/database\\"\\n\\n// ...\\n\\nfunction App(props: AppProps) {\\n // ...\\n return (\\n \\n // success-line\\n {/* Add the Database Provider inside the AuthProvider */ }\\n // success-line\\n \\n \\n // ...\\n \\n // success-line\\n \\n \\n )\\n}\\n\\nexport default App\\n\\nconst $container: ViewStyle = {\\n flex: 1,\\n}\\n\\n\\nconst { data: lists } = useQuery(`\\n SELECT ${ LISTS_TABLE }.*,\\n COUNT(${ TODOS_TABLE }.id) AS total_tasks,\\n SUM(CASE WHEN ${ TODOS_TABLE }.completed = true THEN 1 ELSE 0 END) AS completed_tasks\\n FROM ${ LISTS_TABLE }\\n LEFT JOIN ${ TODOS_TABLE } ON ${ LISTS_TABLE }.id = ${ TODOS_TABLE }.list_id\\n GROUP BY ${ LISTS_TABLE }.id;\\n `);\\n\\nconst deleteList = useCallback(async (id: string) => {\\n console.log(\'Deleting list\', id)\\n return powersync.execute(`DELETE FROM ${ LIST_TABLE } WHERE id = ?`, [id])\\n}, [powersync])\\n\\n// app/components/SignOutButton.tsx\\n\\n//...other imports\\nimport { useDatabase } from \\"app/services/database/database\\"\\n\\n// ...\\nexport const SignOutButton = observer(function SignOutButton(props: SignOutButtonProps) {\\n // ...\\n\\n const { signOut } = useAuth()\\n // success-line\\n const { powersync } = useDatabase()\\n\\n // success-line\\n const handleSignOut = async () => { // make this async\\n // success-line\\n await powersync.disconnectAndClear()\\n await signOut()\\n }\\n\\n return (\\n \\n \\n \\n )\\n})\\n\\n\\n// app/services/database/use-lists.ts\\nimport { useQuery } from \\"@powersync/react-native\\"\\nimport { useAuth } from \\"app/services/database/use-auth\\"\\nimport { useCallback } from \\"react\\"\\nimport { useDatabase } from \\"app/services/database/database\\"\\nimport { LISTS_TABLE, ListRecord, TODOS_TABLE } from \\"app/services/database/schema\\"\\n\\n// Extend the base type with the calculated fields from our query \\nexport type ListItemRecord = ListRecord & { total_tasks: number; completed_tasks: number }\\n\\nexport const useLists = () => {\\n // Get the current user from the auth context \\n const { user } = useAuth()\\n // Get the database instance from the context\\n const { powersync } = useDatabase()\\n\\n // List fetching logic here. You can modify it as per your needs.\\n const { data: lists } = useQuery(`\\n SELECT ${ LISTS_TABLE }.*,\\n COUNT(${ TODOS_TABLE }.id) AS total_tasks,\\n SUM(CASE WHEN ${ TODOS_TABLE }.completed = true THEN 1 ELSE 0 END) as completed_tasks\\n FROM ${ LISTS_TABLE }\\n LEFT JOIN ${ TODOS_TABLE } ON ${ LISTS_TABLE }.id = ${ TODOS_TABLE }.list_id\\n GROUP BY ${ LISTS_TABLE }.id\\n `)\\n\\n\\n const createList = useCallback(async (name: string) => {\\n\\n if (!user) {throw new Error(\\"Can\'t add list -- user is undefined\\")}\\n\\n return powersync.execute(\\n `\\n INSERT INTO ${ LISTS_TABLE }\\n (id, name, created_at, owner_id)\\n VALUES (uuid(), ?, ?, ?)`,\\n [name, new Date().toISOString(), user?.id],\\n )\\n }, [user, powersync])\\n\\n const deleteList = useCallback(async (id: string) => {\\n console.log(\'Deleting list\', id)\\n return powersync.execute(`DELETE\\n FROM ${ LISTS_TABLE }\\n WHERE id = ?`, [id])\\n }, [powersync])\\n\\n return { lists, createList, deleteList }\\n}\\n\\n\\nnpx ignite-cli generate component AddList\\n\\nnpx ignite-cli generate component Lists\\n\\n// app/screens/WelcomeScreen.tsx\\nimport { NativeStackScreenProps } from \\"@react-navigation/native-stack\\"\\nimport { Lists, SignOutButton } from \\"app/components\\"\\nimport { observer } from \\"mobx-react-lite\\"\\nimport React, { FC } from \\"react\\"\\nimport { ViewStyle } from \\"react-native\\"\\nimport { SafeAreaView } from \\"react-native-safe-area-context\\"\\nimport { SignedInNavigatorParamList } from \\"../navigators\\"\\nimport { colors } from \\"../theme\\"\\n\\ninterface WelcomeScreenProps\\n extends NativeStackScreenProps {}\\n\\nexport const WelcomeScreen: FC = observer(function WelcomeScreen() {\\n return (\\n \\n // success-line\\n \\n \\n \\n )\\n})\\n\\nconst $container: ViewStyle = {\\n flex: 1,\\n backgroundColor: colors.palette.neutral300,\\n display: \\"flex\\",\\n justifyContent: \\"flex-start\\",\\n height: \\"100%\\",\\n flexDirection: \\"column\\",\\n}\\n\\n\\n\\n// app/components/Lists.tsx\\nimport { NavigationProp, useNavigation } from \\"@react-navigation/native\\"\\nimport { AddList, Icon, ListItem, Text } from \\"app/components\\"\\nimport { AppStackParamList } from \\"app/navigators\\"\\nimport { ListItemRecord, useLists } from \\"app/services/database/use-lists\\"\\nimport React, { useCallback } from \\"react\\"\\nimport { FlatList, TextStyle, View, ViewStyle } from \\"react-native\\"\\nimport { colors, spacing } from \\"../theme\\"\\n\\nexport function Lists() {\\n \\n // use our hook to fetch the lists\\n const { lists, deleteList } = useLists()\\n const navigation = useNavigation>()\\n\\n // This function tells FlatList how to render each item\\n const renderItem = useCallback(({ item }: { item: ListItemRecord }) => {\\n return (\\n {\\n // Eventually this si where we\'ll navigate to the todo, but for now we\'ll just log the list name\\n console.log(\'Pressed: \', item.name)\\n } }\\n text={ `${ item.name }` }\\n RightComponent={\\n \\n {/* Let users delete lists */}\\n deleteList(item.id) }/>\\n \\n }\\n />\\n )\\n }, [])\\n\\n return (\\n \\n Lists\\n \\n \\n \\n \\n Your Lists\\n item.id }\\n ItemSeparatorComponent={ () => }\\n // show a message if the list is empty\\n ListEmptyComponent={ No lists found }\\n />\\n \\n \\n )\\n}\\n\\n// STYLES\\nconst $separator: ViewStyle = { height: 1, backgroundColor: colors.border }\\nconst $emptyList: TextStyle = {\\n textAlign: \\"center\\",\\n color: colors.textDim,\\n opacity: 0.5,\\n padding: spacing.lg,\\n}\\nconst $card: ViewStyle = {\\n shadowColor: colors.palette.neutral800,\\n shadowOffset: { width: 0, height: 1 },\\n shadowRadius: 2,\\n shadowOpacity: 0.35,\\n borderRadius: 8,\\n}\\nconst $listContainer: ViewStyle = {\\n backgroundColor: colors.palette.neutral100,\\n paddingHorizontal: spacing.md,\\n height: \\"100%\\",\\n borderColor: colors.border,\\n borderWidth: 1,\\n}\\nconst $list: ViewStyle = {\\n flex: 1,\\n marginVertical: spacing.md,\\n backgroundColor: colors.palette.neutral200,\\n padding: spacing.md,\\n}\\nconst $container: ViewStyle = {\\n flex: 1,\\n display: \\"flex\\",\\n flexGrow: 1,\\n padding: spacing.md,\\n}\\nconst $listItemText: TextStyle = {\\n height: 44,\\n width: 44,\\n}\\nconst $deleteListIcon: ViewStyle = {\\n display: \\"flex\\",\\n justifyContent: \\"center\\",\\n alignItems: \\"center\\",\\n height: 44,\\n marginVertical: spacing.xxs,\\n}\\n\\n// app/components/AddList.tsx\\nimport { Button, Text, TextField } from \\"app/components\\"\\nimport { useLists } from \\"app/services/database/use-lists\\"\\nimport { colors, spacing } from \\"app/theme\\"\\nimport { observer } from \\"mobx-react-lite\\"\\nimport React from \\"react\\"\\nimport { Keyboard, TextStyle, View, ViewStyle } from \\"react-native\\"\\n\\n/**\\n * Display a form to add a new list\\n */\\nexport const AddList = observer(function AddList() {\\n const [newListName, setNewListName] = React.useState(\\"\\")\\n const [error, setError] = React.useState(null)\\n\\n // we use the function from our hook to create a new list\\n const { createList } = useLists()\\n\\n const handleAddList = React.useCallback(async () => {\\n if (!newListName) {\\n Keyboard.dismiss()\\n return\\n }\\n try {\\n await createList(newListName)\\n setNewListName(\\"\\")\\n } catch (e: any) {\\n setError(`Failed to create list: ${ e?.message ?? \\"unknown error\\" }`)\\n } finally {\\n Keyboard.dismiss()\\n }\\n }, [createList, newListName])\\n\\n return (\\n \\n Add a List\\n \\n \\n \\n \\n { error && { error } }\\n \\n )\\n})\\n\\nconst $container: ViewStyle = {\\n padding: spacing.md,\\n backgroundColor: colors.palette.neutral200,\\n}\\n\\nconst $form: ViewStyle = {\\n display: \\"flex\\",\\n flexDirection: \\"row\\",\\n alignItems: \\"center\\",\\n}\\n\\nconst $textField: ViewStyle = {\\n flex: 1,\\n}\\n\\nconst $textInput: ViewStyle = {\\n backgroundColor: colors.palette.neutral100,\\n}\\n\\nconst $button: ViewStyle = {\\n marginHorizontal: spacing.xs,\\n padding: 0,\\n paddingHorizontal: spacing.xs,\\n paddingVertical: 0,\\n minHeight: 44,\\n}\\n\\nconst $error: TextStyle = {\\n color: colors.error,\\n marginTop: spacing.sm,\\n}\\n\\n\\n\\nnpx ignite-cli generate screen TodoList\\n\\n // app/navigators/AppNavigator.tsx\\n export type AppStackParamList = {\\n Welcome: undefined\\n Auth: undefined\\n // success-line\\n TodoList: { listId: string } // add this line\\n // IGNITE_GENERATOR_ANCHOR_APP_STACK_PARAM_LIST\\n }\\n\\n // ...\\n \\n const AppStack = observer(function AppStack() {\\n // Fetch the user from the auth context\\n const { signedIn } = useAuth()\\n return (\\n \\n \\n // success-line\\n { signedIn ? (\\n // success-line\\n <>\\n // success-line\\n \\n // success-line\\n \\n // success-line\\n >\\n // success-line\\n ) : null }\\n {/* IGNITE_GENERATOR_ANCHOR_APP_STACK_SCREENS */ }\\n \\n )\\n })\\n \\n export const AppNavigator = observer(function AppNavigator(props: NavigationProps) {\\n // ... \\n })\\n\\n // app/screens/TodoListScreen.tsx\\n // ...\\n \\n export const TodoListScreen: FC = function TodoListScreen({\\n navigation,\\n // success-line\\n // We get the listId from the route params\\n // success-line\\n route: { params: {listId} }\\n }) {\\n return (\\n \\n // success-line\\n navigation.goBack() }>\\n \\n \\n // success-line\\n \\n \\n )\\n }\\n \\n const $root: ViewStyle = {\\n flex: 1,\\n }\\n \\n const $backButton: ViewStyle = {\\n height: 44,\\n }\\n \\n \\n\\n// app/components/Lists.tsx\\n\\n// ... other imports\\nimport { NavigationProp, useNavigation } from \\"@react-navigation/native\\"\\nimport { AppStackParamList } from \\"app/navigators\\"\\n\\nexport function Lists() {\\n\\n const { lists, deleteList } = useLists()\\n // We use the root param list, because this component might be reusing in other screens/navigators\\n // success-line\\n const navigation = useNavigation>()\\n\\n const renderItem = useCallback(({ item }: { item: ListItemRecord }) => {\\n return {\\n // success-line\\n navigation.navigate(\\"TodoList\\", { listId: item.id })\\n } }\\n />\\n }, [])\\n\\n return (\\n //... component body\\n )\\n}\\n\\n// app/services/database/use-list.ts\\n\\nimport { useQuery } from \\"@powersync/react-native\\"\\nimport { useDatabase } from \\"app/services/database/database\\"\\nimport { LISTS_TABLE, ListRecord, TODOS_TABLE, TodoRecord } from \\"app/services/database/schema\\"\\nimport { useAuth } from \\"app/services/database/use-auth\\"\\nimport { useCallback } from \\"react\\"\\n\\n\\nexport function useList(listId: string) {\\n const { user } = useAuth()\\n const { powersync } = useDatabase()\\n\\n\\n const { data: listRecords } = useQuery(`\\n SELECT *\\n FROM ${ LISTS_TABLE }\\n WHERE id = ?\\n `, [listId])\\n\\n // we only expect one list record\\n const list = listRecords[0]\\n\\n\\n const { data: todos } = useQuery(`\\n SELECT *\\n FROM ${ TODOS_TABtLE }\\n WHERE list_id = ?\\n `, [listId])\\n\\n\\n const addTodo = useCallback(async (description: string): Promise<{ error: string | null }> => {\\n if (!user) {\\n throw new Error(\\"Can\'t add todo -- user is undefined\\")\\n }\\n try {\\n await powersync.execute(\\n `INSERT INTO ${ TODOS_TABLE }\\n (id, description, created_at, list_id, created_by, completed)\\n VALUES (uuid(), ?, ?, ?, ?, ?)`,\\n [description, new Date().toISOString(), listId, user?.id, 0],\\n )\\n\\n return { error: null }\\n } catch (error: any) {\\n return { error: `Error adding todo: ${ error?.message }` }\\n }\\n }, [user, powersync, listId])\\n\\n const removeTodo = useCallback(async (id: string): Promise<{ error: string | null }> => {\\n try {\\n await powersync.execute(`DELETE\\n FROM ${ TODOS_TABLE }\\n WHERE id = ?`, [id])\\n return { error: null }\\n } catch (error: any) {\\n console.error(\\"Error removing todo\\", error)\\n return { error: `Error removing todo: ${ error?.message }` }\\n }\\n\\n }, [\\n powersync,\\n ])\\n\\n const setTodoCompleted = useCallback(async (id: string, completed: boolean): Promise<{ error: string | null }> => {\\n\\n const completedAt = completed ? new Date().toISOString() : null\\n const completedBy = completed ? user?.id : null\\n\\n try {\\n await powersync.execute(`\\n UPDATE ${ TODOS_TABLE }\\n SET completed = ?, completed_at = ?, completed_by = ?\\n WHERE id = ?\\n `, [completed, completedAt, completedBy, id])\\n\\n return { error: null }\\n\\n } catch (error: any) {\\n console.error(\'Error toggling todo\', error)\\n return { error: `Error toggling todo: ${ error?.message }` }\\n }\\n }, [powersync])\\n\\n\\n return { list, todos, addTodo, removeTodo, setTodoCompleted }\\n\\n}\\n\\n\\n// app/screens/TodoListScreen.tsx\\nimport { Button, Icon, ListItem, Screen, Text, TextField } from \\"app/components\\"\\nimport { SignedInNavigatorScreenProps } from \\"app/navigators\\"\\nimport { TodoRecord } from \\"app/services/database/schema\\"\\nimport { useList } from \\"app/services/database/use-list\\"\\nimport { colors, spacing } from \\"app/theme\\"\\nimport React, { FC, useCallback } from \\"react\\"\\nimport { FlatList, Pressable, TextStyle, View, ViewStyle } from \\"react-native\\"\\nimport { SafeAreaView } from \\"react-native-safe-area-context\\"\\n\\ninterface TodoListScreenProps extends SignedInNavigatorScreenProps<\\"TodoList\\"> {}\\n\\nexport const TodoListScreen: FC = function TodoListScreen({\\n navigation,\\n route: { params: { listId } },\\n}) {\\n\\n // We use the hook to get the list and todos for the list\\n const { list, todos, addTodo, removeTodo, setTodoCompleted } = useList(listId)\\n\\n // State for managing the new todo input and errors\\n const [newTodo, setNewTodo] = React.useState(\\"\\")\\n const [error, setError] = React.useState(null)\\n\\n // We wrap the addTodo from the hook with a bit of error handling\\n const handleAddTodo = useCallback(async () => {\\n const { error } = await addTodo(newTodo)\\n if (error) {\\n setError(error)\\n return\\n }\\n setNewTodo(\\"\\")\\n }, [newTodo])\\n\\n // And do the same for removeTodo\\n const handleRemoveTodo = useCallback(async (id: string) => {\\n const { error } = await removeTodo(id)\\n if (error) {\\n setError(error)\\n }\\n }, [removeTodo, setError])\\n\\n // We\'ll use the ListItem component to display each todo, as we did with the lists\\n const renderItem = useCallback(({ item }: { item: TodoRecord }) => {\\n return \\n handleRemoveTodo(item.id) }/>\\n ) }\\n onPress={ () => setTodoCompleted(item.id, !item.completed) }\\n />\\n }, [\\n handleRemoveTodo,\\n ])\\n\\n return (\\n \\n \\n navigation.goBack() }>\\n \\n \\n \\n \\n \\n Add a list\\n \\n \\n \\n \\n { error && { error } }\\n \\n \\n }\\n ListEmptyComponent={ List is Empty }\\n />\\n \\n \\n )\\n}\\n\\nconst $root: ViewStyle = {\\n flex: 1,\\n}\\nconst $listItemContainer: ViewStyle = {\\n alignItems: \\"center\\",\\n}\\n\\nconst $strikeThrough: TextStyle = { textDecorationLine: \\"line-through\\" }\\n\\nconst $form: ViewStyle = {\\n display: \\"flex\\",\\n flexDirection: \\"row\\",\\n alignItems: \\"center\\",\\n}\\n\\nconst $separator: ViewStyle = { height: 1, backgroundColor: colors.border }\\n\\nconst $emptyList: TextStyle = {\\n color: colors.textDim,\\n opacity: 0.5,\\n padding: spacing.lg,\\n fontSize: 24,\\n}\\n\\nconst $textField: ViewStyle = {\\n flex: 1,\\n}\\n\\nconst $textInput: ViewStyle = {\\n backgroundColor: colors.palette.neutral100,\\n}\\n\\n\\nconst $button: ViewStyle = {\\n marginHorizontal: spacing.xs,\\n padding: 0,\\n paddingHorizontal: spacing.xs,\\n paddingVertical: 0,\\n}\\n\\nconst $addTodoContainer: ViewStyle = {\\n padding: spacing.md,\\n backgroundColor: colors.palette.neutral300,\\n}\\nconst $header: ViewStyle = {\\n display: \\"flex\\",\\n flexDirection: \\"row\\",\\n alignItems: \\"center\\",\\n backgroundColor: colors.palette.secondary200,\\n paddingBottom: spacing.md,\\n}\\n\\nconst $listName: TextStyle = {\\n marginLeft: spacing.sm,\\n flex: 1,\\n}\\n\\nconst $error: TextStyle = {\\n color: colors.error,\\n marginTop: spacing.sm,\\n}\\n\\nconst $container: ViewStyle = {\\n padding: spacing.md,\\n}\\n\\nconst $listItemText: TextStyle = {\\n height: 44,\\n verticalAlign: \\"middle\\"\\n}\\n\\nconst $deleteIcon: ViewStyle = {\\n display: \\"flex\\",\\n justifyContent: \\"center\\",\\n alignItems: \\"center\\",\\n height: 44,\\n marginVertical: spacing.xxs,\\n}\\n\\n\\n","lastUpdated":"3 months ago","title":"PowerSync and Supabase for Local-First Data Management","publish_date":"2024-03-22","doc_name":"LocalFirstDataWithPowerSync.md"},{"author":"Dan Edwards","content":"curl -Ls \\"https://get.maestro.mobile.dev\\" | bash\\n\\nbrew tap facebook/fb\\nbrew install idb-companion\\n\\n#flow: Login\\n#intent:\\n# Open up our app and use the default credentials to login\\n# and navigate to the demo screen\\n\\nappId: com.maestroapp # the app id of the app we want to test\\n# You can find the appId of an Ignite app in the `app.json` file\\n# as the \\"package\\" under the \\"android\\" section and \\"bundleIdentifier\\" under the \\"ios\\" section\\n---\\n- clearState # clears the state of our app (navigation and authentication)\\n- launchApp # launches the app\\n- assertVisible: \\"Sign In\\"\\n- tapOn:\\n text: \\"Tap to sign in!\\"\\n- assertVisible: \\"Your app, almost ready for launch!\\"\\n- tapOn:\\n text: \\"Let\'s go!\\"\\n- assertVisible: \\"Components to jump start your project!\\"\\n\\ncd .maestro\\nmaestro test Login.yaml\\n\\n \u2551 > Flow\\n Running on iPhone 11 - iOS 16.2 - 5A269AA1-2704-429B-BF30-D6965060E03E\\n \u2551 \u2705 Clear state of com.maestroapp\\n \u2551 \u2705 Launch app \\"com.maestroapp\\"\\n \u2551 \u2705 Assert that \\"Sign In\\" is visible\\n \u2551 \u2705 Tap on \\"Tap to sign in!\\"\\n \u2551 \u2705 Assert that \\"Your app, almost ready for launch!\\" is visible\\n \u2551 \u2705 Tap on \\"Let\'s go!\\"\\n \u2551 \u2705 Assert that \\"Components to jump start your project!\\" is visible\\n\\n# flow: run the login flow and then navigate to the demo podcast list screen, favorite a podcast, and then switch the list to only be favorites.\\n\\nappId: com.maestroapp\\nenv:\\n TITLE: \\"RNR 257 - META RESPONDS! How can we improve React Native, part 2\\"\\n FAVORITES_TEXT: \\"Switch on to only show favorites\\"\\n\\n---\\n- runFlow: Login.yaml\\n- tapOn: \\"Podcast, tab, 3 of 4\\"\\n- assertVisible: \\"React Native Radio episodes\\"\\n- tapOn:\\n text: ${FAVORITES_TEXT}\\n- assertVisible: \\"This looks a bit empty\\"\\n- tapOn:\\n text: ${FAVORITES_TEXT}\\n- scrollUntilVisible:\\n element:\\n text: ${TITLE}\\n direction: DOWN\\n timeout: 50000\\n speed: 40\\n visibilityPercentage: 100\\n- longPressOn: ${TITLE}\\n- scrollUntilVisible:\\n element:\\n text: ${FAVORITES_TEXT}\\n direction: UP\\n timeout: 50000\\n speed: 40\\n visibilityPercentage: 100\\n- tapOn:\\n text: ${FAVORITES_TEXT}\\n- assertVisible: ${TITLE}\\n\\n","lastUpdated":"10 months ago","title":"Maestro Setup","publish_date":"2023-02-01","doc_name":"MaestroSetup.md"},{"author":"Felipe Pe\xf1a","content":"yarn remove i18n-js @types/i18n-js@types/i18n-js\\n\\nyarn add react-i18next i18next\\n\\n\\n// error-line\\nimport \\"./i18n\\"\\n// success-line\\nimport { initI18n } from \\"./i18n\\"\\n\\n// ...extra file logic\\n\\n// success-line-start\\nconst [isI18nInitialized, setIsI18nInitialized] = useState(false);\\n\\nuseEffect(() => {\\n initI18n().then(() => setIsI18nInitialized(true));\\n}, []);\\n// success-line-end\\n\\n// error-line-start\\nif (!rehydrated || !isNavigationStateRestored || (!areFontsLoaded && !fontLoadError)) {\\n// error-line-end\\n// success-line-start\\nif (!rehydrated || !isNavigationStateRestored || !isI18nInitialized || (!areFontsLoaded && !fontLoadError)) {\\n// success-line-end\\n return null\\n}\\n\\n// error-line\\nimport { I18n } from \\"i18n-js\\"\\n\\n\\n// success-line-start\\nimport * as i18next from \\"i18next\\"\\nimport { initReactI18next } from \\"react-i18next\\"\\nimport en from \\"./en\\"\\nimport ar from \\"./ar\\"\\nimport ko from \\"./ko\\"\\nimport es from \\"./es\\"\\nimport fr from \\"./fr\\"\\nimport ja from \\"./ja\\"\\nimport hi from \\"./hi\\"\\n// success-line-end\\n\\n// ...extra file logic\\n\\n\\n// error-line-start\\nexport const i18n = new I18n(\\n { ar, en, \\"en-US\\": en, ko, fr, ja, hi },\\n { locale: fallbackLocale, defaultLocale: fallbackLocale, enableFallback: true },\\n)\\n// error-line-end\\n// success-line-start\\nconst resources = { ar, en, ko, es, fr, ja, hi }\\n\\nconst pickSupportedLocale: () => Localization.Locale | undefined = () => {\\n return systemLocales.find((locale) => systemTagMatchesSupportedTags(locale.languageTag))\\n}\\n\\nconst locale = pickSupportedLocale()\\n\\nexport const initI18n = async () => {\\n await i18n.use(initReactI18next).init({\\n resources,\\n lng: locale?.languageTag ?? fallbackLocale,\\n fallbackLng: fallbackLocale,\\n interpolation: { escapeValue: false },\\n });\\n\\n const locale = pickSupportedLocale();\\n if (locale?.textDirection === \'rtl\') {\\n I18nManager.allowRTL(true);\\n isRTL = true;\\n } else {\\n I18nManager.allowRTL(false);\\n isRTL = false;\\n }\\n\\n return i18n;\\n};\\n// success-line-end\\n\\nyarn add intl-pluralrules\\n\\n// success-line\\nimport \'intl-pluralrules\';\\n\\n// error-line\\nimport { TranslateOptions } from \\"i18n-js\\"\\n// success-line-start\\nimport { TOptions } from \\"i18next\\"\\nimport { TxKeyPath } from \\"./i18n\\"\\n// success-line-end\\n\\n// error-line-start\\nexport function translate(key: TxKeyPath, options?: TranslateOptions): string {\\n return i18n.t(key, options)\\n// error-line-end\\n// success-line-start\\nexport function translate(key: TxKeyPath, options?: TOptions) {\\n return i18n.isInitialized ? i18n.t(key, options) : key;\\n// success-line-end\\n}\\n\\ntranslate(\\"common.ok\\")\\n\\ntranslate(\\"common:ok\\")\\n\\n// error-line-start\\nuseHeader(\\n {\\n rightTx: \\"common.logOut\\",\\n onRightPress: logout,\\n },\\n [logout],\\n)\\n// error-line-end\\n// success-line-start\\nuseHeader(\\n {\\n rightTx: \\"common:logOut\\",\\n onRightPress: logout,\\n },\\n [logout],\\n)\\n// success-line-end\\n\\n// error-line-start\\nreturn (\\n \\n \\n \\n \\n \\n \\n \\n\\n \\n \\n\\n \\n \\n \\n)\\n})\\n// error-line-end\\n// success-line-start\\nreturn (\\n \\n \\n \\n \\n \\n \\n \\n\\n \\n \\n\\n \\n \\n \\n)\\n})\\n// success-line-end\\n\\n// error-line-start\\ntype RecursiveKeyOf = {\\n [TKey in keyof TObj & (string | number)]: RecursiveKeyOfHandleValue\\n}[keyof TObj & (string | number)]\\n\\ntype RecursiveKeyOfInner = {\\n [TKey in keyof TObj & (string | number)]: RecursiveKeyOfHandleValue<\\n TObj[TKey],\\n `[\'${TKey}\']` | `.${TKey}`\\n >\\n}[keyof TObj & (string | number)]\\n\\ntype RecursiveKeyOfHandleValue = TValue extends any[]\\n ? Text\\n : TValue extends object\\n ? Text | `${Text}${RecursiveKeyOfInner}`\\n : Text\\n// error-line-end\\n// success-line-start\\ntype RecursiveKeyOf = {\\n [TKey in keyof TObj & (string | number)]: RecursiveKeyOfHandleValue\\n}[keyof TObj & (string | number)]\\n\\ntype RecursiveKeyOfInner = {\\n [TKey in keyof TObj & (string | number)]: RecursiveKeyOfHandleValue\\n}[keyof TObj & (string | number)]\\n\\ntype RecursiveKeyOfHandleValue<\\n TValue,\\n Text extends string,\\n IsFirstLevel extends boolean,\\n> = TValue extends any[]\\n ? Text\\n : TValue extends object\\n ? IsFirstLevel extends true\\n ? Text | `${Text}:${RecursiveKeyOfInner}`\\n : Text | `${Text}.${RecursiveKeyOfInner}`\\n : Text\\n// success-line-end\\n\\n// error-line\\nimport { i18n } from \\"app/i18n\\"\\n// success-line\\nimport i18next from \\"i18next\\"\\n\\n// ...extra file logic\\n\\n// error-line\\nconst locale = i18n.locale.split(\\"-\\")[0]\\n// success-line\\nconst locale = i18next.language.split(\\"-\\")[0]\\n\\n","lastUpdated":"7 weeks ago","title":"Migrating from i18n-js to react-i18next","publish_date":"2024-09-25","doc_name":"MigratingToI18Next.md"},{"author":"Frank Calise, Mark Rickert","content":"npx ignite-cli new PizzaApp --workflow=cng --yes\\ncd PizzaApp\\n\\nyarn remove @react-native-async-storage/async-storage\\nyarn add react-native-mmkv\\nyarn prebuild\\n\\nimport { MMKV } from \\"react-native-mmkv\\";\\n\\nexport const storage = new MMKV();\\n\\n/**\\n * Loads a string from storage.\\n *\\n * @param key The key to fetch.\\n */\\nexport function loadString(key: string): string | undefined {\\n try {\\n return storage.getString(key)\\n } catch {\\n // not sure why this would fail... even reading the RN docs I\'m unclear\\n return undefined\\n }\\n}\\n\\n/**\\n * Saves a string to storage.\\n *\\n * @param key The key to fetch.\\n * @param value The value to store.\\n */\\nexport function saveString(key: string, value: string): boolean {\\n try {\\n storage.set(key, value)\\n return true\\n } catch {\\n return false\\n }\\n}\\n\\n/**\\n * Loads something from storage and runs it thru JSON.parse.\\n *\\n * @param key The key to fetch.\\n */\\nexport function load(key: string): unknown | undefined {\\n try {\\n const almostThere = loadString(key)\\n if (almostThere) {\\n try {\\n return JSON.parse(almostThere)\\n } catch {\\n return almostThere // Return the string if it\'s not a valid JSON\\n }\\n }\\n return undefined\\n } catch {\\n return undefined\\n }\\n}\\n\\n/**\\n * Saves an object to storage.\\n *\\n * @param key The key to fetch.\\n * @param value The value to store.\\n */\\nexport function save(key: string, value: unknown): boolean {\\n try {\\n saveString(key, JSON.stringify(value))\\n return true\\n } catch {\\n return false\\n }\\n}\\n\\n/**\\n * Removes something from storage.\\n *\\n * @param key The key to kill.\\n */\\nexport function remove(key: string): void {\\n try {\\n storage.delete(key)\\n } catch {}\\n}\\n\\n/**\\n * Burn it all to the ground.\\n */\\nexport function clear(): void {\\n try {\\n storage.clearAll()\\n } catch {}\\n}\\n\\nimport {\\n storage,\\n load,\\n loadString,\\n save,\\n saveString,\\n clear,\\n remove,\\n} from \\"./storage\\";\\n\\nconst VALUE_OBJECT = { x: 1 };\\nconst VALUE_STRING = JSON.stringify(VALUE_OBJECT);\\n\\ndescribe(\\"MMKV Storage\\", () => {\\n beforeEach(() => {\\n storage.clearAll();\\n storage.set(\\"string\\", \\"string\\");\\n storage.set(\\"object\\", JSON.stringify(VALUE_OBJECT));\\n });\\n\\n it(\\"should be defined\\", () => {\\n expect(storage).toBeDefined();\\n });\\n\\n it(\\"should have default keys\\", () => {\\n expect(storage.getAllKeys()).toEqual([\\"string\\", \\"object\\"]);\\n });\\n\\n it(\\"should load data\\", () => {\\n expect(load(\\"object\\")).toEqual(VALUE_OBJECT);\\n expect(loadString(\\"object\\")).toEqual(VALUE_STRING);\\n\\n expect(load(\\"string\\")).toEqual(\\"string\\");\\n expect(loadString(\\"string\\")).toEqual(\\"string\\");\\n });\\n\\n it(\\"should save strings\\", () => {\\n saveString(\\"string\\", \\"new string\\");\\n expect(loadString(\\"string\\")).toEqual(\\"new string\\");\\n });\\n\\n it(\\"should save objects\\", () => {\\n save(\\"object\\", { y: 2 });\\n expect(load(\\"object\\")).toEqual({ y: 2 });\\n save(\\"object\\", { z: 3, also: true });\\n expect(load(\\"object\\")).toEqual({ z: 3, also: true });\\n });\\n\\n it(\\"should save strings and objects\\", () => {\\n saveString(\\"object\\", \\"new string\\");\\n expect(loadString(\\"object\\")).toEqual(\\"new string\\");\\n });\\n\\n it(\\"should remove data\\", () => {\\n remove(\\"object\\");\\n expect(load(\\"object\\")).toBeUndefined();\\n expect(storage.getAllKeys()).toEqual([\\"string\\"]);\\n\\n remove(\\"string\\");\\n expect(load(\\"string\\")).toBeUndefined();\\n expect(storage.getAllKeys()).toEqual([]);\\n });\\n\\n it(\\"should clear all data\\", () => {\\n expect(storage.getAllKeys()).toEqual([\\"string\\", \\"object\\"]);\\n clear();\\n expect(storage.getAllKeys()).toEqual([]);\\n });\\n});\\n\\n","lastUpdated":"4 months ago","title":"Migrating to MMKV","publish_date":"2022-12-28","doc_name":"MigratingToMMKV.md"},{"author":"Yulian Glukhenko","content":"sdk.dir=/Users/path/to/sdk\\nndk.dir=/Users/path/to/ndk\\n\\nsdk.dir=/Users/~Usernamehere~/Library/Android/sdk\\nndk.dir=/Users/~Usernamehere~/Library/Android/sdk/ndk/21.4.7075529\\n\\narch -x86_64 ./gradlew :ReactAndroid:installArchives --no-daemon\\n\\n","lastUpdated":"10 months ago","title":"Patching/Building Android .aar From Source","publish_date":"2022-10-09","doc_name":"PatchingBuildingAndroid.md"},{"author":"Frank Calise","content":"npx ignite-cli@latest new PizzaApp --workflow=prebuild --yes\\n\\nnpm install -g eas-cli\\n\\nbrew install cocoapods fastlane\\n\\nyarn add expo-dev-client\\n\\neas init\\n\\nWarning: Your project uses dynamic app configuration, and the EAS project ID can\'t automatically be added to it.\\nhttps://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs\\n\\nTo complete the setup process, set \\"extra.eas.projectId\\" in your app.config.ts or app.json:\\n\\n{\\n \\"expo\\": {\\n \\"extra\\": {\\n \\"eas\\": {\\n \\"projectId\\": \\"...id here...\\"\\n }\\n }\\n }\\n}\\n\\neas build:configure\\n\\n{\\n \\"cli\\": {\\n \\"version\\": \\">= 0.60.0\\"\\n },\\n \\"build\\": {\\n \\"development\\": {\\n \\"developmentClient\\": true,\\n \\"distribution\\": \\"internal\\"\\n },\\n \\"preview\\": {\\n \\"developmentClient\\": true,\\n \\"ios\\": {\\n \\"simulator\\": true\\n }\\n },\\n \\"production\\": {}\\n },\\n \\"submit\\": {\\n \\"production\\": {}\\n }\\n}\\n\\neas build --profile preview\\n\\neas build --profile preview --local\\n\\nEAS_LOCAL_BUILD_ARTIFACTS_DIR=build eas build --profile preview --local\\n\\n--\\"start\\": \\"expo start\\"\\n++\\"start\\": \\"expo start --dev-client\\"\\n\\n","lastUpdated":"10 months ago","title":"Prepping Ignite for EAS Build","publish_date":"2023-12-04","doc_name":"PrepForEASBuild.md"},{"author":"Frank Calise","content":"npx ignite-cli@latest new PizzaApp --remove-demo --workflow=cng --yes\\ncd PizzaApp\\n\\nnpx expo install react-native-vision-camera\\n\\n\\"plugins\\": [\\n \\"expo-localization\\",\\n [\\n \\"expo-build-properties\\",\\n {\\n \\"ios\\": {\\n \\"newArchEnabled\\": false\\n },\\n \\"android\\": {\\n \\"newArchEnabled\\": false\\n }\\n }\\n ],\\n [\\n \\"react-native-vision-camera\\",\\n {\\n \\"cameraPermissionText\\": \\"$(PRODUCT_NAME) needs access to your Camera.\\",\\n \\"enableCodeScanner\\": true\\n }\\n ]\\n],\\n\\nnpx expo prebuild\\nyarn android\\n\\nimport { observer } from \\"mobx-react-lite\\";\\nimport React, { FC } from \\"react\\";\\nimport { AppStackScreenProps } from \\"../navigators\\";\\nimport { Camera, CameraPermissionStatus } from \\"react-native-vision-camera\\";\\nimport { Linking, View, ViewStyle } from \\"react-native\\";\\nimport { Button, Screen, Text } from \\"app/components\\";\\n\\ninterface WelcomeScreenProps extends AppStackScreenProps<\\"Welcome\\"> {}\\n\\nexport const WelcomeScreen: FC = observer(\\n function WelcomeScreen(_props) {\\n const [cameraPermission, setCameraPermission] =\\n React.useState();\\n\\n React.useEffect(() => {\\n Camera.getCameraPermissionStatus().then(setCameraPermission);\\n }, []);\\n\\n const promptForCameraPermissions = React.useCallback(async () => {\\n const permission = await Camera.requestCameraPermission();\\n Camera.getCameraPermissionStatus().then(setCameraPermission);\\n\\n if (permission === \\"denied\\") await Linking.openSettings();\\n }, [cameraPermission]);\\n\\n if (cameraPermission == null) {\\n // still loading\\n return null;\\n }\\n\\n return (\\n \\n \\n \\n Camera Permission:{\\" \\"}\\n {cameraPermission === null ? \\"Loading...\\" : cameraPermission}\\n \\n {cameraPermission !== \\"granted\\" && (\\n \\n )}\\n \\n \\n );\\n }\\n);\\n\\nconst $container: ViewStyle = {\\n flex: 1,\\n padding: 20,\\n justifyContent: \\"space-evenly\\",\\n};\\n\\nnpx ignite-cli@next g model CodeStore\\nnpx ignite-cli@next g screen Codes\\n\\nimport { Instance, SnapshotIn, SnapshotOut, types } from \\"mobx-state-tree\\";\\nimport { withSetPropAction } from \\"./helpers/withSetPropAction\\";\\n\\n/**\\n * Model description here for TypeScript hints.\\n */\\nexport const CodeStoreModel = types\\n .model(\\"CodeStore\\")\\n .props({\\n codes: types.array(types.string),\\n })\\n .actions(withSetPropAction)\\n .actions((self) => ({\\n addCode(code: string) {\\n self.codes.push(code);\\n },\\n }));\\n\\nexport interface CodeStore extends Instance {}\\nexport interface CodeStoreSnapshotOut\\n extends SnapshotOut {}\\nexport interface CodeStoreSnapshotIn\\n extends SnapshotIn {}\\nexport const createCodeStoreDefaultModel = () =>\\n types.optional(CodeStoreModel, {});\\n\\nimport React, { FC } from \\"react\\";\\nimport { observer } from \\"mobx-react-lite\\";\\nimport { View, ViewStyle } from \\"react-native\\";\\nimport { AppStackScreenProps } from \\"app/navigators\\";\\nimport { Button, Screen, Text } from \\"app/components\\";\\nimport { useNavigation } from \\"@react-navigation/native\\";\\nimport { useStores } from \\"app/models\\";\\nimport { spacing } from \\"app/theme\\";\\n\\ninterface CodesScreenProps extends AppStackScreenProps<\\"Codes\\"> {}\\n\\nexport const CodesScreen: FC = observer(\\n function CodesScreen() {\\n // Pull in one of our MST stores\\n const { codeStore } = useStores();\\n\\n // Pull in navigation via hook\\n const navigation = useNavigation();\\n return (\\n \\n \\n \\n\\n {codeStore.codes.map((code, index) => (\\n \\n ))}\\n \\n\\n \\n );\\n }\\n);\\n\\nconst $root: ViewStyle = {\\n flex: 1,\\n};\\n\\nconst $container: ViewStyle = {\\n flex: 1,\\n justifyContent: \\"space-between\\",\\n paddingHorizontal: spacing.md,\\n};\\n\\nimport { observer } from \\"mobx-react-lite\\";\\nimport React, { FC } from \\"react\\";\\nimport { AppStackScreenProps } from \\"../navigators\\";\\n// success-line-start\\nimport {\\n Camera,\\n CameraPermissionStatus,\\n useCameraDevice,\\n useCodeScanner,\\n} from \\"react-native-vision-camera\\";\\nimport {\\n Alert,\\n Linking,\\n StyleSheet,\\n TouchableOpacity,\\n View,\\n ViewStyle,\\n} from \\"react-native\\";\\nimport { Button, Icon, Screen, Text } from \\"app/components\\";\\nimport { useSafeAreaInsets } from \\"react-native-safe-area-context\\";\\nimport { useStores } from \\"app/models\\";\\nimport { spacing } from \\"app/theme\\";\\n// success-line-end\\n\\ninterface WelcomeScreenProps extends AppStackScreenProps<\\"Welcome\\"> {}\\n\\nexport const WelcomeScreen: FC = observer(\\n function WelcomeScreen(_props) {\\n const [cameraPermission, setCameraPermission] =\\n React.useState();\\n // success-line-start\\n const [showScanner, setShowScanner] = React.useState(false);\\n const [isActive, setIsActive] = React.useState(false);\\n\\n const { codeStore } = useStores();\\n // success-line-end\\n\\n React.useEffect(() => {\\n Camera.getCameraPermissionStatus().then(setCameraPermission);\\n }, []);\\n\\n const promptForCameraPermissions = React.useCallback(async () => {\\n const permission = await Camera.requestCameraPermission();\\n Camera.getCameraPermissionStatus().then(setCameraPermission);\\n\\n if (permission === \\"denied\\") await Linking.openSettings();\\n }, [cameraPermission]);\\n\\n // success-line-start\\n const codeScanner = useCodeScanner({\\n codeTypes: [\\"qr\\", \\"ean-13\\"],\\n onCodeScanned: (codes) => {\\n setIsActive(false);\\n\\n codes.every((code) => {\\n if (code.value) {\\n codeStore.addCode(code.value);\\n }\\n return true;\\n });\\n\\n setShowScanner(false);\\n Alert.alert(\\"Code scanned!\\");\\n },\\n });\\n\\n const device = useCameraDevice(\\"back\\");\\n\\n const { right, top } = useSafeAreaInsets();\\n // success-line-end\\n\\n if (cameraPermission == null) {\\n // still loading\\n return null;\\n }\\n\\n // success-line-start\\n if (showScanner && device) {\\n return (\\n \\n \\n \\n setShowScanner(false)}\\n >\\n \\n \\n \\n \\n );\\n }\\n // success-line-end\\n\\n return (\\n \\n \\n \\n Camera Permission:{\\" \\"}\\n {cameraPermission === null ? \\"Loading...\\" : cameraPermission}\\n \\n {cameraPermission !== \\"granted\\" && (\\n \\n )}\\n \\n // success-line-start\\n \\n \\n \\n \\n \\n );\\n }\\n);\\n\\nconst $container: ViewStyle = {\\n flex: 1,\\n padding: 20,\\n justifyContent: \\"space-evenly\\",\\n};\\n\\n// success-line-start\\nconst $cameraContainer: ViewStyle = {\\n flex: 1,\\n};\\n\\nconst $cameraButtons: ViewStyle = {\\n position: \\"absolute\\",\\n};\\n\\nconst $closeCamera: ViewStyle = {\\n marginBottom: spacing.md,\\n width: 100,\\n height: 100,\\n borderRadius: 100 / 2,\\n backgroundColor: \\"rgba(140, 140, 140, 0.3)\\",\\n justifyContent: \\"center\\",\\n alignItems: \\"center\\",\\n};\\n// success-line-end\\n\\n","lastUpdated":"9 months ago","title":"React Native Vision Camera","publish_date":"2023-10-23","doc_name":"ReactNativeVisionCamera.md"},{"author":"Justin Poliachik","content":"npx ignite-cli new ReduxApp --yes --removeDemo\\n\\nyarn add @reduxjs/toolkit\\nyarn add react-redux\\n\\nimport { configureStore } from \\"@reduxjs/toolkit\\";\\nimport { TypedUseSelectorHook, useDispatch, useSelector } from \\"react-redux\\";\\nimport counterReducer from \\"./counterSlice\\";\\n\\nexport const store = configureStore({\\n reducer: {\\n counter: counterReducer,\\n // add other state here\\n },\\n});\\n\\n// Infer the `RootState` and `AppDispatch` types from the store itself\\nexport type RootState = ReturnType;\\nexport type AppDispatch = typeof store.dispatch;\\n\\n// Use throughout app instead of plain `useDispatch` and `useSelector` for type safety\\ntype DispatchFunc = () => AppDispatch;\\nexport const useAppDispatch: DispatchFunc = useDispatch;\\nexport const useAppSelector: TypedUseSelectorHook = useSelector;\\n\\nimport { createSlice } from \\"@reduxjs/toolkit\\";\\n\\n// Define a type for the slice state\\ninterface CounterState {\\n value: number;\\n}\\n\\n// Define the initial state using that type\\nconst initialState: CounterState = {\\n value: 0,\\n};\\n\\nexport const counterSlice = createSlice({\\n name: \\"counter\\",\\n // `createSlice` will infer the state type from the `initialState` argument\\n initialState,\\n reducers: {\\n increment: (state) => {\\n state.value += 1;\\n },\\n decrement: (state) => {\\n state.value -= 1;\\n },\\n },\\n});\\n\\nexport const { increment, decrement } = counterSlice.actions;\\nexport default counterSlice.reducer;\\n\\nimport { Provider } from \\"react-redux\\";\\nimport { store } from \\"./store/store\\";\\n\\n...\\n\\n\\n \\n\\n\\nimport React, { FC } from \\"react\\";\\nimport { View, ViewStyle } from \\"react-native\\";\\nimport { Button, Text } from \\"app/components\\";\\nimport { AppStackScreenProps } from \\"../navigators\\";\\nimport type { ThemedStyle } from \\"app/theme\\";\\nimport { useAppTheme } from \\"app/utils/useAppTheme\\";\\nimport { useSafeAreaInsetsStyle } from \\"../utils/useSafeAreaInsetsStyle\\";\\nimport { useAppDispatch, useAppSelector } from \\"app/store/store\\";\\nimport { decrement, increment } from \\"app/store/counterSlice\\";\\n\\ninterface WelcomeScreenProps extends AppStackScreenProps<\\"Welcome\\"> {}\\n\\nexport const WelcomeScreen: FC = () => {\\n const { themed } = useAppTheme();\\n const $containerInsets = useSafeAreaInsetsStyle([\\"top\\", \\"bottom\\"]);\\n const count = useAppSelector((state) => state.counter.value);\\n const dispatch = useAppDispatch();\\n return (\\n \\n \\n );\\n};\\n\\nconst $container: ThemedStyle = ({ colors }) => ({\\n flex: 1,\\n backgroundColor: colors.background,\\n});\\n\\nyarn add redux-persist\\n\\nimport { combineReducers, configureStore } from \\"@reduxjs/toolkit\\";\\nimport counterReducer from \\"./counterSlice\\";\\nimport { TypedUseSelectorHook, useDispatch, useSelector } from \\"react-redux\\";\\nimport { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER } from \\"redux-persist\\";\\nimport AsyncStorage from \\"@react-native-async-storage/async-storage\\";\\n\\nconst persistConfig = {\\n key: \\"root\\",\\n version: 1,\\n storage: AsyncStorage,\\n};\\n\\nconst rootReducer = combineReducers({\\n counter: counterReducer,\\n});\\n\\nconst persistedReducer = persistReducer(persistConfig, rootReducer);\\n\\nexport const store = configureStore({\\n reducer: persistedReducer,\\n middleware: (getDefaultMiddleware) =>\\n getDefaultMiddleware({\\n serializableCheck: {\\n ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],\\n },\\n }),\\n});\\n\\nexport const persistor = persistStore(store);\\n\\n// Infer the `RootState` and `AppDispatch` types from the store itself\\nexport type RootState = ReturnType;\\nexport type AppDispatch = typeof store.dispatch;\\n\\n// Use throughout app instead of plain `useDispatch` and `useSelector` for type safety\\ntype DispatchFunc = () => AppDispatch;\\nexport const useAppDispatch: DispatchFunc = useDispatch;\\nexport const useAppSelector: TypedUseSelectorHook = useSelector;\\n\\n...\\n\\nimport { persistor, store } from \\"./store/store\\"\\nimport { PersistGate } from \\"redux-persist/integration/react\\"\\n\\n...\\n\\nfunction App(props: AppProps) {\\n const { hideSplashScreen } = props\\n...\\n const onBeforeLiftPersistGate = () => {\\n // If your initialization scripts run very fast, it\'s good to show the splash screen for just a bit longer to prevent flicker.\\n // Slightly delaying splash screen hiding for better UX; can be customized or removed as needed,\\n // Note: (vanilla Android) The splash-screen will not appear if you launch your app via the terminal or Android Studio. Kill the app and launch it normally by tapping on the launcher icon. https://stackoverflow.com/a/69831106\\n // Note: (vanilla iOS) You might notice the splash-screen logo change size. This happens in debug/development mode. Try building the app for release.\\n setTimeout(hideSplashScreen, 500)\\n }\\n...\\n return (\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n )\\n}\\n\\nexport default App\\n\\n","lastUpdated":"9 weeks ago","title":"Redux","publish_date":"2024-01-16","doc_name":"Redux.md"},{"author":"Justin Poliachik","content":"yarn remove mobx mobx-react-lite mobx-state-tree reactotron-mst\\n\\nrm -rf ./app/models\\n\\n--import { mst } from \\"reactotron-mst\\"\\n\\n...\\n\\nconst reactotron = Reactotron.configure({\\n name: require(\\"../../package.json\\").name,\\n onConnect: () => {\\n /** since this file gets hot reloaded, let\'s clear the past logs every time we connect */\\n Reactotron.clear()\\n },\\n--}).use(\\n-- mst({\\n-- /** ignore some chatty `mobx-state-tree` actions */\\n-- filter: (event) => /postProcessSnapshot|@APPLY_SNAPSHOT/.test(event.name) === false,\\n-- }),\\n--)\\n++})\\n\\n--import { observer } from \\"mobx-react-lite\\"\\n\\n--export const WelcomeScreen: FC = observer(function WelcomeScreen(props) {\\n++export const WelcomeScreen: FC = (props) => {\\n ...\\n--})\\n++}\\n\\n--import { useStores } from \\"../models\\"\\n\\nconst AppStack = () => {\\n-- const { authenticationStore: { isAuthenticated } } = useStores()\\n++ const isAuthenticated = false // TODO: TEMPORARY VALUE - replace with alternative state management solution\\n\\n--import { observer } from \\"mobx-react-lite\\"\\n\\n--export const <%= props.pascalCaseName %> = observer(function <%= props.pascalCaseName %>(props: <%= props.pascalCaseName %>Props) {\\n++export const <%= props.pascalCaseName %> = (props: <%= props.pascalCaseName %>Props) => {\\n ...\\n--})\\n++}\\n\\n--import { useInitialRootStore } from \\"./models\\"\\n\\n--const { rehydrated } = useInitialRootStore(() => {\\n--setTimeout(hideSplashScreen, 500)\\n--})\\n++React.useEffect(() => {\\n++ setTimeout(hideSplashScreen, 500)\\n++}, [])\\n\\n--if (!rehydrated || !isNavigationStateRestored || !areFontsLoaded) return null\\n++if (!isNavigationStateRestored || !areFontsLoaded) return null\\n\\n--import type { ApiConfig, ApiFeedResponse } from \\"./api.types\\"\\n--import type { EpisodeSnapshotIn } from \\"../../models/Episode\\"\\n++import type { ApiConfig, ApiFeedResponse, EpisodeItem } from \\"./api.types\\"\\n\\n\\n--async getEpisodes(): Promise<{ kind: \\"ok\\"; episodes: EpisodeSnapshotIn[] } | GeneralApiProblem> {\\n++async getEpisodes(): Promise<{ kind: \\"ok\\"; episodes: EpisodeItem[] } | GeneralApiProblem> {\\n// make the api call\\n\\n--// This is where we transform the data into the shape we expect for our MST model.\\n--const episodes: EpisodeSnapshotIn[] =\\n-- rawData?.items.map((raw) => ({\\n-- ...raw,\\n-- })) ?? []\\n++const episodes = rawData?.items ?? []\\n\\n","lastUpdated":"10 months ago","title":"Remove MobX-State-Tree","publish_date":"2024-02-05","doc_name":"RemoveMobxStateTree.md"},{"author":"Mark Rickert","content":"npx ignite-cli new PizzaApp --workflow=cng --yes\\ncd PizzaApp\\n\\nimport {\\n withInfoPlist,\\n withAndroidManifest,\\n type ConfigPlugin,\\n type AndroidConfig,\\n type IOSConfig,\\n} from \\"@expo/config-plugins\\"\\n\\n// More info: https://developer.android.com/guide/topics/manifest/uses-feature-element\\nconst validAndroidFeatures = [\\n \\"android.hardware.audio.low_latency\\",\\n \\"android.hardware.audio.output\\",\\n \\"android.hardware.audio.pro\\",\\n \\"android.hardware.bluetooth\\",\\n \\"android.hardware.bluetooth_le\\",\\n \\"android.hardware.camera\\",\\n \\"android.hardware.camera.any\\",\\n \\"android.hardware.camera.autofocus\\",\\n \\"android.hardware.camera.capability.manual_post_processing\\",\\n \\"android.hardware.camera.capability.manual_sensor\\",\\n \\"android.hardware.camera.capability.raw\\",\\n \\"android.hardware.camera.external\\",\\n \\"android.hardware.camera.flash\\",\\n \\"android.hardware.camera.front\\",\\n \\"android.hardware.camera.level.full\\",\\n \\"android.hardware.consumerir\\",\\n \\"android.hardware.faketouch\\",\\n \\"android.hardware.faketouch.multitouch.distinct\\",\\n \\"android.hardware.faketouch.multitouch.jazzhand\\",\\n \\"android.hardware.fingerprint\\",\\n \\"android.hardware.gamepad\\",\\n \\"android.hardware.location\\",\\n \\"android.hardware.location.gps\\",\\n \\"android.hardware.location.network\\",\\n \\"android.hardware.microphone\\",\\n \\"android.hardware.nfc\\",\\n \\"android.hardware.nfc.hce\\",\\n \\"android.hardware.opengles.aep\\",\\n \\"android.hardware.screen.landscape\\",\\n \\"android.hardware.screen.portrait\\",\\n \\"android.hardware.sensor.accelerometer\\",\\n \\"android.hardware.sensor.ambient_temperature\\",\\n \\"android.hardware.sensor.barometer\\",\\n \\"android.hardware.sensor.compass\\",\\n \\"android.hardware.sensor.gyroscope\\",\\n \\"android.hardware.sensor.heartrate\\",\\n \\"android.hardware.sensor.heartrate.ecg\\",\\n \\"android.hardware.sensor.hifi_sensors\\",\\n \\"android.hardware.sensor.light\\",\\n \\"android.hardware.sensor.proximity\\",\\n \\"android.hardware.sensor.relative_humidity\\",\\n \\"android.hardware.sensor.stepcounter\\",\\n \\"android.hardware.sensor.stepdetector\\",\\n \\"android.hardware.telephony\\",\\n \\"android.hardware.telephony.cdma\\",\\n \\"android.hardware.telephony.gsm\\",\\n \\"android.hardware.touchscreen\\",\\n \\"android.hardware.touchscreen.multitouch\\",\\n \\"android.hardware.touchscreen.multitouch.distinct\\",\\n \\"android.hardware.touchscreen.multitouch.jazzhand\\",\\n \\"android.hardware.type.automotive\\",\\n \\"android.hardware.type.pc\\",\\n \\"android.hardware.type.television\\",\\n \\"android.hardware.type.watch\\",\\n \\"android.hardware.usb.accessory\\",\\n \\"android.hardware.usb.host\\",\\n \\"android.hardware.vulkan.compute\\",\\n \\"android.hardware.vulkan.level\\",\\n \\"android.hardware.vulkan.version\\",\\n \\"android.hardware.wifi\\",\\n \\"android.hardware.wifi.direct\\",\\n] as const\\n\\n// More info: https://developer.apple.com/documentation/bundleresources/information_property_list/uirequireddevicecapabilities/\\nconst validIOSFeatures = [\\n \\"accelerometer\\",\\n \\"arkit\\",\\n \\"arm64\\",\\n \\"armv7\\",\\n \\"auto-focus-camera\\",\\n \\"bluetooth-le\\",\\n \\"camera-flash\\",\\n \\"driverkit\\",\\n \\"front-facing-camera\\",\\n \\"gamekit\\",\\n \\"gps\\",\\n \\"gyroscope\\",\\n \\"healthkit\\",\\n \\"iphone-ipad-minimum-performance-a12\\",\\n \\"iphone-performance-gaming-tier\\",\\n \\"location-services\\",\\n \\"magnetometer\\",\\n \\"metal\\",\\n \\"microphone\\",\\n \\"nfc\\",\\n \\"opengles-1\\",\\n \\"opengles-2\\",\\n \\"opengles-3\\",\\n \\"peer-peer\\",\\n \\"sms\\",\\n \\"still-camera\\",\\n \\"telephony\\",\\n \\"video-camera\\",\\n \\"wifi\\",\\n] as const\\n\\ntype HardwareFeatureAndroid = (typeof validAndroidFeatures)[number]\\ntype HardwareFeatureIOS = (typeof validIOSFeatures)[number]\\n\\nexport const withRequiredHardware: ConfigPlugin<{\\n ios: Array\\n android: Array\\n}> = (config, { android, ios }) => {\\n // Add android required hardware\\n config = withAndroidManifest(config, (config) => {\\n config.modResults = addHardwareFeaturesToAndroidManifestManifest(config.modResults, android)\\n return config\\n })\\n\\n // Add ios required hardware\\n config = withInfoPlist(config, (config) => {\\n config.modResults = addRequiredDeviceCapabilitiesToInfoPlist(config.modResults, ios)\\n return config\\n })\\n\\n return config\\n}\\n\\nexport function addHardwareFeaturesToAndroidManifestManifest(\\n androidManifest: AndroidConfig.Manifest.AndroidManifest,\\n requiredFeatures: Array,\\n) {\\n // Add `` to the AndroidManifest.xml\\n if (!Array.isArray(androidManifest.manifest[\\"uses-feature\\"])) {\\n androidManifest.manifest[\\"uses-feature\\"] = []\\n }\\n\\n // Here we add the feature to the manifest:\\n // loop through the array of features and add them to the manifest if they don\'t exist\\n for (const feature of requiredFeatures) {\\n if (\\n !androidManifest.manifest[\\"uses-feature\\"].find((item) => item.$[\\"android:name\\"] === feature)\\n ) {\\n androidManifest.manifest[\\"uses-feature\\"]?.push({\\n $: {\\n \\"android:name\\": feature,\\n \\"android:required\\": \\"true\\",\\n },\\n })\\n }\\n }\\n\\n return androidManifest\\n}\\n\\nexport function addRequiredDeviceCapabilitiesToInfoPlist(\\n infoPlist: IOSConfig.InfoPlist,\\n requiredFeatures: Array,\\n) {\\n if (!infoPlist.UIRequiredDeviceCapabilities) {\\n infoPlist.UIRequiredDeviceCapabilities = []\\n }\\n const existingFeatures = infoPlist.UIRequiredDeviceCapabilities as Array\\n for (const f of requiredFeatures) {\\n if (!existingFeatures.includes(f)) {\\n existingFeatures.push(f)\\n }\\n }\\n\\n infoPlist.UIRequiredDeviceCapabilities = existingFeatures\\n return infoPlist\\n}\\n\\n return {\\n ...config,\\n plugins: [\\n ...existingPlugins,\\n require(\\"./plugins/withSplashScreen\\").withSplashScreen,\\n require(\\"./plugins/withFlipperDisabled\\").withFlipperDisabled,\\n // success-line-start\\n [\\n require(\\"./plugins/withRequiredHardware\\").withRequiredHardware,\\n {\\n // More info: https://developer.apple.com/documentation/bundleresources/information_property_list/uirequireddevicecapabilities/\\n ios: [\\"front-facing-camera\\", \\"microphone\\"],\\n // More info: https://developer.android.com/guide/topics/manifest/uses-feature-element\\n android: [\\"android.hardware.camera.front\\", \\"android.hardware.microphone\\"],\\n },\\n ],\\n // success-line-end\\n ],\\n }\\n\\nUIRequiredDeviceCapabilities\\n\\n armv7\\n // success-line-start\\n front-facing-camera\\n microphone\\n // success-line-end\\n\\n\\n// success-line\\n\\n// success-line\\n\\n\\n","lastUpdated":"10 months ago","title":"Requiring Hardware Features with Expo","publish_date":"2024-02-28","doc_name":"RequiringHardwareFeaturesWithExpo.md"},{"author":"Robin Heinze","content":"# Javascript Node CircleCI 2.0 configuration file\\n#\\n# Check https://circleci.com/docs/2.0/language-javascript/ for more details\\n#\\n\\ndefaults: &defaults\\n docker:\\n # Choose the version of Node you want here\\n - image: circleci/node:10.11\\n working_directory: ~/repo\\n\\nversion: 2\\njobs:\\n setup:\\n <<: *defaults\\n steps:\\n - checkout\\n - restore_cache:\\n name: Restore node modules\\n keys:\\n - v1-dependencies-{{ checksum \\"package.json\\" }}\\n # fallback to using the latest cache if no exact match is found\\n - v1-dependencies-\\n - run:\\n name: Install dependencies\\n command: |\\n yarn install\\n - save_cache:\\n name: Save node modules\\n paths:\\n - node_modules\\n key: v1-dependencies-{{ checksum \\"package.json\\" }}\\n\\n tests:\\n <<: *defaults\\n steps:\\n - checkout\\n - restore_cache:\\n name: Restore node modules\\n keys:\\n - v1-dependencies-{{ checksum \\"package.json\\" }}\\n # fallback to using the latest cache if no exact match is found\\n - v1-dependencies-\\n - run:\\n name: Install React Native CLI and Ignite CLI\\n command: |\\n sudo npm i -g ignite-cli react-native-cli\\n - run:\\n name: Run tests\\n command: yarn ci:test # this command will be added to/found in your package.json scripts\\n\\n publish:\\n <<: *defaults\\n steps:\\n - checkout\\n - run: echo \\"//registry.npmjs.org/:_authToken=$NPM_TOKEN\\" >> ~/.npmrc\\n - restore_cache:\\n name: Restore node modules\\n keys:\\n - v1-dependencies-{{ checksum \\"package.json\\" }}\\n # fallback to using the latest cache if no exact match is found\\n - v1-dependencies-\\n # Run semantic-release after all the above is set.\\n - run:\\n name: Publish to NPM\\n command: yarn ci:publish # this will be added to your package.json scripts\\n\\nworkflows:\\n version: 2\\n test_and_release:\\n jobs:\\n - setup\\n - tests:\\n requires:\\n - setup\\n - publish:\\n requires:\\n - tests\\n filters:\\n branches:\\n only: master\\n\\n","lastUpdated":"10 months ago","title":"Sample YAML for CircleCi for Ignite","publish_date":"2022-10-09","doc_name":"SampleYAMLCircleCI.md"},{"author":"Yulian Glukhenko","content":"yarn add @gorhom/bottom-sheet@^4\\n\\nyarn add react-native-reanimated react-native-gesture-handler\\n# or\\nexpo install react-native-reanimated react-native-gesture-handler\\n\\ntouch ./app/components/SelectField.tsx\\n\\nimport React, { forwardRef, Ref, useImperativeHandle } from \\"react\\";\\nimport { View, TouchableOpacity } from \\"react-native\\";\\nimport { TextField, TextFieldProps } from \\"./TextField\\";\\n\\nexport interface SelectFieldProps\\n extends Omit {}\\nexport interface SelectFieldRef {}\\n\\nexport const SelectField = forwardRef(function SelectField(\\n props: SelectFieldProps,\\n ref: Ref\\n) {\\n const { ...TextFieldProps } = props;\\n\\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === \\"disabled\\";\\n\\n useImperativeHandle(ref, () => ({}));\\n\\n return (\\n <>\\n \\n \\n \\n \\n \\n >\\n );\\n});\\n\\nimport { SelectField } from \\"../components/SelectField\\";\\n\\nfunction FavoriteNBATeamsScreen() {\\n return (\\n \\n );\\n}\\n\\n }\\n/>\\n\\nexport interface SelectFieldProps\\n extends Omit {\\n value?: string[];\\n renderValue?: (value: string[]) => string;\\n onSelect?: (newValue: string[]) => void;\\n multiple?: boolean;\\n options: { label: string; value: string }[];\\n}\\n\\n// ...\\n\\nconst {\\n value = [],\\n renderValue,\\n onSelect,\\n options = [],\\n multiple = true,\\n ...TextFieldProps\\n} = props;\\n\\nconst valueString =\\n renderValue?.(value) ??\\n value\\n .map((v) => options.find((o) => o.value === v)?.label)\\n .filter(Boolean)\\n .join(\\", \\");\\n\\nimport React, { forwardRef, Ref, useImperativeHandle } from \\"react\\";\\nimport { TouchableOpacity, View } from \\"react-native\\";\\n// success-line\\nimport { Icon } from \\"./Icon\\";\\nimport { TextField, TextFieldProps } from \\"./TextField\\";\\n\\nexport interface SelectFieldProps\\n extends Omit {\\n // success-line-start\\n value?: string[];\\n renderValue?: (value: string[]) => string;\\n onSelect?: (newValue: string[]) => void;\\n multiple?: boolean;\\n options: { label: string; value: string }[];\\n // success-line-end\\n}\\nexport interface SelectFieldRef {}\\n\\nexport const SelectField = forwardRef(function SelectField(\\n props: SelectFieldProps,\\n ref: Ref\\n) {\\n const {\\n // success-line-start\\n value = [],\\n onSelect,\\n renderValue,\\n options = [],\\n multiple = true,\\n // success-line-end\\n ...TextFieldProps\\n } = props;\\n\\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === \\"disabled\\";\\n\\n useImperativeHandle(ref, () => ({}));\\n\\n // success-line-start\\n const valueString =\\n renderValue?.(value) ??\\n value\\n .map((v) => options.find((o) => o.value === v)?.label)\\n .filter(Boolean)\\n .join(\\", \\");\\n // success-line-end\\n\\n return (\\n <>\\n \\n \\n }\\n // success-line-end\\n />\\n \\n \\n >\\n );\\n});\\n\\nimport { SelectField } from \\"../components/SelectField\\";\\n\\nconst teams = [\\n { label: \\"Hawks\\", value: \\"ATL\\" },\\n { label: \\"Celtics\\", value: \\"BOS\\" },\\n // ...\\n { label: \\"Jazz\\", value: \\"UTA\\" },\\n { label: \\"Wizards\\", value: \\"WAS\\" },\\n];\\n\\n// prettier-ignore\\nfunction FavoriteNBATeamsScreen() {\\n return (\\n <>\\n \\n\\n \\n\\n `Selected ${value.length} Teams`}\\n />\\n >\\n )\\n}\\n\\n//...\\n// success-line\\nimport { BottomSheetModalProvider } from \\"@gorhom/bottom-sheet\\";\\n\\n//...\\n\\nreturn (\\n \\n \\n // success-line\\n \\n \\n // success-line\\n \\n \\n \\n);\\n\\n//...\\n\\n// success-line-start\\nimport {\\n BottomSheetBackdrop,\\n BottomSheetFlatList,\\n BottomSheetFooter,\\n BottomSheetModal,\\n} from \\"@gorhom/bottom-sheet\\";\\n// success-line-end\\nimport React, { forwardRef, Ref, useImperativeHandle, useRef } from \\"react\\";\\nimport { TouchableOpacity, View, ViewStyle } from \\"react-native\\";\\nimport type { ThemedStyle } from \\"app/theme\\";\\nimport { useAppTheme } from \\"app/utils/useAppTheme\\";\\n// success-line\\nimport { useSafeAreaInsets } from \\"react-native-safe-area-context\\";\\n// success-line\\nimport { spacing } from \\"../theme\\";\\n// success-line\\nimport { Button } from \\"./Button\\";\\nimport { Icon } from \\"./Icon\\";\\n// success-line\\nimport { ListItem } from \\"./ListItem\\";\\nimport { TextField, TextFieldProps } from \\"./TextField\\";\\n\\nexport interface SelectFieldProps\\n extends Omit {\\n value?: string[];\\n renderValue?: (value: string[]) => string;\\n onSelect?: (newValue: string[]) => void;\\n multiple?: boolean;\\n options: { label: string; value: string }[];\\n}\\nexport interface SelectFieldRef {\\n // success-line-start\\n presentOptions: () => void;\\n dismissOptions: () => void;\\n // success-line-end\\n}\\n\\nexport const SelectField = forwardRef(function SelectField(\\n props: SelectFieldProps,\\n ref: Ref\\n) {\\n const {\\n value = [],\\n onSelect,\\n renderValue,\\n options = [],\\n multiple = true,\\n ...TextFieldProps\\n } = props;\\n // success-line-start\\n const sheet = useRef(null);\\n const { bottom } = useSafeAreaInsets();\\n // success-line-end\\n\\n const { themed } = useAppTheme();\\n\\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === \\"disabled\\";\\n\\n // success-line\\n useImperativeHandle(ref, () => ({ presentOptions, dismissOptions }));\\n\\n const valueString =\\n renderValue?.(value) ??\\n value\\n .map((v) => options.find((o) => o.value === v)?.label)\\n .filter(Boolean)\\n .join(\\", \\");\\n\\n // success-line-start\\n function presentOptions() {\\n if (disabled) return;\\n sheet.current?.present();\\n }\\n\\n function dismissOptions() {\\n sheet.current?.dismiss();\\n }\\n // success-line-end\\n\\n return (\\n <>\\n \\n \\n }\\n />\\n \\n \\n\\n {/* success-line-start */}\\n (\\n \\n )}\\n footerComponent={\\n !multiple\\n ? undefined\\n : (props) => (\\n \\n \\n \\n )\\n }\\n >\\n o.value}\\n renderItem={({ item, index }) => (\\n \\n )}\\n />\\n \\n {/* success-line-end */}\\n >\\n );\\n});\\n\\n// success-line-start\\nconst $bottomSheetFooter: ThemedStyle = ({ spacing }) => ({\\n paddingHorizontal: spacing.lg,\\n paddingBottom: spacing.xs,\\n});\\n\\nconst $listItem: ThemedStyle = ({ spacing }) => ({\\n paddingHorizontal: spacing.lg,\\n});\\n// success-line-end\\n\\nimport {\\n BottomSheetBackdrop,\\n BottomSheetFlatList,\\n BottomSheetFooter,\\n BottomSheetModal,\\n} from \\"@gorhom/bottom-sheet\\";\\nimport React, { forwardRef, Ref, useImperativeHandle, useRef } from \\"react\\";\\nimport { TouchableOpacity, View, ViewStyle } from \\"react-native\\";\\nimport { useSafeAreaInsets } from \\"react-native-safe-area-context\\";\\nimport type { ThemedStyle } from \\"app/theme\\";\\nimport { useAppTheme } from \\"app/utils/useAppTheme\\";\\nimport { Button } from \\"./Button\\";\\nimport { Icon } from \\"./Icon\\";\\nimport { ListItem } from \\"./ListItem\\";\\nimport { TextField, TextFieldProps } from \\"./TextField\\";\\n\\nexport interface SelectFieldProps\\n extends Omit {\\n value?: string[];\\n renderValue?: (value: string[]) => string;\\n onSelect?: (newValue: string[]) => void;\\n multiple?: boolean;\\n options: { label: string; value: string }[];\\n}\\nexport interface SelectFieldRef {\\n presentOptions: () => void;\\n dismissOptions: () => void;\\n}\\n\\n// success-line-start\\nfunction without(array: T[], value: T) {\\n return array.filter((v) => v !== value);\\n}\\n// success-line-end\\n\\nexport const SelectField = forwardRef(function SelectField(\\n props: SelectFieldProps,\\n ref: Ref\\n) {\\n const {\\n value = [],\\n onSelect,\\n renderValue,\\n options = [],\\n multiple = true,\\n ...TextFieldProps\\n } = props;\\n const sheet = useRef(null);\\n const { bottom } = useSafeAreaInsets();\\n const {\\n themed,\\n // success-line-start\\n theme: { colors },\\n // success-line-end\\n } = useAppTheme();\\n\\n const disabled = TextFieldProps.editable === false || TextFieldProps.status === \\"disabled\\";\\n\\n useImperativeHandle(ref, () => ({ presentOptions, dismissOptions }));\\n\\n const valueString =\\n renderValue?.(value) ??\\n value\\n .map((v) => options.find((o) => o.value === v)?.label)\\n .filter(Boolean)\\n .join(\\", \\");\\n\\n function presentOptions() {\\n if (disabled) return;\\n\\n sheet.current?.present();\\n }\\n\\n function dismissOptions() {\\n sheet.current?.dismiss();\\n }\\n\\n // success-line-start\\n function updateValue(optionValue: string) {\\n if (value.includes(optionValue)) {\\n onSelect?.(multiple ? without(value, optionValue) : []);\\n } else {\\n onSelect?.(multiple ? [...value, optionValue] : [optionValue]);\\n if (!multiple) dismissOptions();\\n }\\n }\\n // success-line-end\\n\\n return (\\n <>\\n \\n \\n }\\n />\\n \\n \\n\\n (\\n \\n )}\\n footerComponent={\\n !multiple\\n ? undefined\\n : (props) => (\\n \\n \\n \\n )\\n }\\n >\\n o.value}\\n renderItem={({ item, index }) => (\\n updateValue(item.value)}\\n // success-line-end\\n />\\n )}\\n />\\n \\n >\\n );\\n});\\n\\nconst $bottomSheetFooter: ThemedStyle = ({ spacing }) => ({\\n paddingHorizontal: spacing.lg,\\n paddingBottom: spacing.xs,\\n});\\n\\nconst $listItem: ThemedStyle = ({ spacing }) => ({\\n paddingHorizontal: spacing.lg,\\n});\\n\\nimport { SelectField } from \\"../components/SelectField\\";\\n\\nconst teams = [\\n { label: \\"Hawks\\", value: \\"ATL\\" },\\n { label: \\"Celtics\\", value: \\"BOS\\" },\\n // ...\\n { label: \\"Jazz\\", value: \\"UTA\\" },\\n { label: \\"Wizards\\", value: \\"WAS\\" },\\n];\\n\\nfunction FavoriteNBATeamsScreen() {\\n const [selectedTeam, setSelectedTeam] = useState([]);\\n const [selectedTeams, setSelectedTeams] = useState([]);\\n\\n return (\\n <>\\n \\n\\n `Selected ${value.length} Teams`}\\n />\\n >\\n );\\n}\\n\\n","lastUpdated":"9 weeks ago","title":"SelectField using `react-native-bottom-sheet`","publish_date":"2023-02-15","doc_name":"SelectFieldWithBottomSheet.mdx"},{"author":"Felipe Pe\xf1a","content":"mkdir monorepo-example\\ncd monorepo-example\\nyarn init -y\\n\\n{\\n \\"name\\": \\"monorepo-example\\",\\n // error-line\\n \\"packageManager\\": \\"yarn@3.8.4\\"\\n // success-line-start\\n \\"packageManager\\": \\"yarn@3.8.4\\",\\n \\"private\\": true,\\n \\"workspaces\\": [\\n \\"apps/*\\",\\n \\"packages/*\\"\\n ]\\n // success-line-end\\n}\\n\\nmkdir apps packages\\n\\nnpx ignite-cli@latest\\n\\ncd apps\\nnpx ignite-cli new mobile\\n\\n\ud83d\udcdd Do you want to use Expo?: Expo - Recommended for almost all apps [Default]\\n\ud83d\udcdd Which Expo workflow?: Expo Go - For simple apps that don\'t need custom native code [Default]\\n\ud83d\udcdd Do you want to initialize a git repository?: No\\n\ud83d\udcdd Remove demo code? We recommend leaving it in if it\'s your first time using Ignite: No\\n\ud83d\udcdd Which package manager do you want to use?: yarn\\n\ud83d\udcdd Do you want to install dependencies?: No\\n\\ntouch mobile/metro.config.js\\n\\n// Learn more https://docs.expo.io/guides/customizing-metro\\nconst { getDefaultConfig } = require(\'expo/metro-config\');\\n\\n// success-line-start\\n// Get monorepo root folder\\nconst monorepoRoot = path.resolve(projectRoot, \'../..\');\\n// success-line-end\\n\\n/** @type {import(\'expo/metro-config\').MetroConfig} */\\n// error-line\\nconst config = getDefaultConfig(__dirname);\\n// success-line\\nconst config = getDefaultConfig(projectRoot);\\n\\nconfig.transformer.getTransformOptions = async () => ({\\n transform: {\\n // Inline requires are very useful for deferring loading of large dependencies/components.\\n // For example, we use it in app.tsx to conditionally load Reactotron.\\n // However, this comes with some gotchas.\\n // Read more here: https://reactnative.dev/docs/optimizing-javascript-loading\\n // And here: https://github.com/expo/expo/issues/27279#issuecomment-1971610698\\n inlineRequires: true,\\n },\\n});\\n\\n// success-line-start\\n// 1. Watch all files within the monorepo\\nconfig.watchFolders = [monorepoRoot];\\n// 2. Let Metro know where to resolve packages and in what order\\nconfig.resolver.nodeModulesPaths = [\\n path.resolve(projectRoot, \'node_modules\'),\\n path.resolve(monorepoRoot, \'node_modules\'),\\n];\\n// success-line-end\\n\\n// This helps support certain popular third-party libraries\\n// such as Firebase that use the extension cjs.\\nconfig.resolver.sourceExts.push(\\"cjs\\")\\n\\nmodule.exports = config;\\n\\ncd ..\\nyarn install\\n\\nmkdir packages/eslint-config\\ncd packages/eslint-config\\n\\nyarn init -y\\n\\nyarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-plugin-react-native eslint-plugin-reactotron eslint-config-standard eslint-config-prettier --dev\\n\\n// success-line-start\\n{\\n \\"compilerOptions\\": {\\n \\"module\\": \\"commonjs\\",\\n \\"target\\": \\"es6\\",\\n \\"lib\\": [\\"es6\\", \\"dom\\"],\\n \\"jsx\\": \\"react\\",\\n \\"strict\\": true,\\n \\"esModuleInterop\\": true,\\n \\"skipLibCheck\\": true\\n }\\n }\\n // success-line-end\\n\\nmodule.exports = {\\n root: true,\\n parser: \\"@typescript-eslint/parser\\",\\n extends: [\\n \\"plugin:@typescript-eslint/recommended\\",\\n \\"plugin:react/recommended\\",\\n \\"plugin:react-native/all\\",\\n \\"standard\\",\\n \\"prettier\\",\\n ],\\n plugins: [\\n \\"@typescript-eslint\\",\\n \\"react\\",\\n \\"react-native\\",\\n \\"reactotron\\",\\n ],\\n parserOptions: {\\n ecmaFeatures: {\\n jsx: true,\\n },\\n },\\n settings: {\\n react: {\\n pragma: \\"React\\",\\n version: \\"detect\\",\\n },\\n },\\n globals: {\\n __DEV__: false,\\n jasmine: false,\\n beforeAll: false,\\n afterAll: false,\\n beforeEach: false,\\n afterEach: false,\\n test: false,\\n expect: false,\\n describe: false,\\n jest: false,\\n it: false,\\n },\\n rules: {\\n \\"@typescript-eslint/ban-ts-ignore\\": 0,\\n \\"@typescript-eslint/ban-ts-comment\\": 0,\\n \\"@typescript-eslint/explicit-function-return-type\\": 0,\\n \\"@typescript-eslint/explicit-member-accessibility\\": 0,\\n \\"@typescript-eslint/explicit-module-boundary-types\\": 0,\\n \\"@typescript-eslint/indent\\": 0,\\n \\"@typescript-eslint/member-delimiter-style\\": 0,\\n \\"@typescript-eslint/no-empty-interface\\": 0,\\n \\"@typescript-eslint/no-explicit-any\\": 0,\\n \\"@typescript-eslint/no-object-literal-type-assertion\\": 0,\\n \\"@typescript-eslint/no-var-requires\\": 0,\\n \\"@typescript-eslint/no-unused-vars\\": [\\n \\"error\\",\\n {\\n argsIgnorePattern: \\"^_\\",\\n varsIgnorePattern: \\"^_\\",\\n },\\n ],\\n \\"comma-dangle\\": 0,\\n \\"multiline-ternary\\": 0,\\n \\"no-undef\\": 0,\\n \\"no-unused-vars\\": 0,\\n \\"no-use-before-define\\": 0,\\n \\"no-global-assign\\": 0,\\n \\"quotes\\": 0,\\n \\"react-native/no-raw-text\\": 0,\\n \\"react/no-unescaped-entities\\": 0,\\n \\"react/prop-types\\": 0,\\n \\"space-before-function-paren\\": 0,\\n \\"reactotron/no-tron-in-production\\": \\"error\\",\\n },\\n}\\n// success-line-end\\n\\nnpx tsc\\n\\ncd ..\\ncd ..\\ncd apps/mobile\\n\\n\\"eslint\\": \\"8.17.0\\",\\n// success-line\\n \\"eslint-config\\": \\"workspace:^\\",\\n \\"eslint-config-prettier\\": \\"8.5.0\\",\\n\\n// error-line-start\\n\\"eslintConfig\\": {\\n \\"root\\": true,\\n \\"parser\\": \\"@typescript-eslint/parser\\",\\n \\"extends\\": [\\n \\"plugin:@typescript-eslint/recommended\\",\\n \\"plugin:react/recommended\\",\\n \\"plugin:react-native/all\\",\\n \\"standard\\",\\n \\"prettier\\"\\n ],\\n \\"plugins\\": [\\n \\"@typescript-eslint\\",\\n \\"react\\",\\n \\"react-native\\",\\n \\"reactotron\\"\\n ],\\n \\"parserOptions\\": {\\n \\"ecmaFeatures\\": {\\n \\"jsx\\": true\\n }\\n },\\n \\"settings\\": {\\n \\"react\\": {\\n \\"pragma\\": \\"React\\",\\n \\"version\\": \\"detect\\"\\n }\\n },\\n \\"globals\\": {\\n \\"__DEV__\\": false,\\n \\"jasmine\\": false,\\n \\"beforeAll\\": false,\\n \\"afterAll\\": false,\\n \\"beforeEach\\": false,\\n \\"afterEach\\": false,\\n \\"test\\": false,\\n \\"expect\\": false,\\n \\"describe\\": false,\\n \\"jest\\": false,\\n \\"it\\": false\\n },\\n \\"rules\\": {\\n \\"@typescript-eslint/ban-ts-ignore\\": 0,\\n \\"@typescript-eslint/ban-ts-comment\\": 0,\\n \\"@typescript-eslint/explicit-function-return-type\\": 0,\\n \\"@typescript-eslint/explicit-member-accessibility\\": 0,\\n \\"@typescript-eslint/explicit-module-boundary-types\\": 0,\\n \\"@typescript-eslint/indent\\": 0,\\n \\"@typescript-eslint/member-delimiter-style\\": 0,\\n \\"@typescript-eslint/no-empty-interface\\": 0,\\n \\"@typescript-eslint/no-explicit-any\\": 0,\\n \\"@typescript-eslint/no-object-literal-type-assertion\\": 0,\\n \\"@typescript-eslint/no-var-requires\\": 0,\\n \\"@typescript-eslint/no-unused-vars\\": [\\n \\"error\\",\\n {\\n \\"argsIgnorePattern\\": \\"^_\\",\\n \\"varsIgnorePattern\\": \\"^_\\"\\n }\\n ],\\n \\"comma-dangle\\": 0,\\n \\"multiline-ternary\\": 0,\\n \\"no-undef\\": 0,\\n \\"no-unused-vars\\": 0,\\n \\"no-use-before-define\\": 0,\\n \\"no-global-assign\\": 0,\\n \\"quotes\\": 0,\\n \\"react-native/no-raw-text\\": 0,\\n \\"react/no-unescaped-entities\\": 0,\\n \\"react/prop-types\\": 0,\\n \\"space-before-function-paren\\": 0,\\n \\"reactotron/no-tron-in-production\\": \\"error\\"\\n }\\n }\\n// error-line-end\\n// success-line-start\\n\\"eslintConfig\\": {\\n extends: [\\"@monorepo-example/eslint-config\\"],\\n}\\n// success-line-end\\n\\ncd ..\\ncd ..\\ncd packages\\n\\nmkdir ui-components\\ncd ui-components\\n\\nyarn init -y\\n\\nyarn add react react-native typescript --peer\\nyarn add @types/react @types/react-native --dev\\n\\n// success-line-start\\n{\\n \\"compilerOptions\\": {\\n \\"target\\": \\"es5\\",\\n \\"lib\\": [\\"dom\\", \\"es2017\\"],\\n \\"module\\": \\"commonjs\\",\\n \\"jsx\\": \\"react\\",\\n \\"declaration\\": true,\\n \\"outDir\\": \\"dist\\",\\n \\"strict\\": true,\\n \\"esModuleInterop\\": true,\\n \\"skipLibCheck\\": true\\n },\\n \\"include\\": [\\"src\\"],\\n \\"exclude\\": [\\"node_modules\\"]\\n}\\n // success-line-end\\n\\nmkdir src\\ntouch src/Badge.tsx\\n\\n// success-line-start\\nimport React, { FC } from \\"react\\"\\nimport { View, Text, StyleSheet, ViewStyle, TextStyle } from \\"react-native\\"\\n\\ninterface BadgeProps {\\n label: string\\n color?: string\\n backgroundColor?: string\\n style?: ViewStyle\\n textStyle?: TextStyle\\n}\\n\\nexport const Badge: FC = ({ label, color = \\"white\\", backgroundColor = \\"red\\", style, textStyle }) => {\\n return (\\n \\n {label}\\n \\n )\\n}\\n\\nconst styles = StyleSheet.create({\\n badge: {\\n paddingHorizontal: 8,\\n paddingVertical: 4,\\n borderRadius: 12,\\n alignSelf: \\"flex-start\\",\\n } satisfies ViewStyle,\\n text: {\\n fontSize: 12,\\n fontWeight: \\"bold\\",\\n } satisfies TextStyle,\\n})\\n// success-line-end\\n\\n// success-line-start\\nexport * from \\"./Badge\\"\\n// success-line-end\\n\\nnpx tsc\\n\\ncd ..\\ncd ..\\ncd apps/mobile\\n\\n \\"react-native-screens\\": \\"3.31.1\\",\\n // error-line\\n \\"react-native-web\\": \\"~0.19.6\\"\\n // success-line-start\\n \\"react-native-web\\": \\"~0.19.6\\",\\n \\"ui-components\\": \\"workspace:^\\"\\n // success-line-end\\n },\\n\\nimport { AppStackScreenProps } from \\"../navigators\\"\\nimport { colors, spacing } from \\"../theme\\"\\n// success-line\\nimport { Badge } from \\"ui-components\\"\\n\\n...\\n\\n\\n// success-line-start\\n{attemptsCount > 0 && (\\n 2 ? \\"red\\" : \\"blue\\"}\\n />\\n)}\\n// success-line-end\\n\\ncd ..\\ncd ..\\n\\nyarn\\n\\ncd apps/mobile\\nyarn ios\\n\\ncd apps/mobile\\nyarn android\\n\\n \\"scripts\\": {\\n ...\\n \\"serve:web\\": \\"npx server dist\\",\\n // error-line\\n \\"prebuild:clean\\": \\"npx expo prebuild --clean\\"\\n // success-line-start\\n \\"prebuild:clean\\": \\"npx expo prebuild --clean\\",\\n \\"mobile:ios\\" : \\"yarn workspace mobile ios\\",\\n \\"mobile:android\\" : \\"yarn workspace mobile android\\"\\n // success-line-end\\n },\\n\\n","lastUpdated":"40 seconds ago","title":"Setting up a Yarn monorepo with Ignite","publish_date":"2024-08-22","doc_name":"SettingUpYarnMonorepo.md"},{"author":"Justin Poliachik","content":"--\\"android\\": \\"npx expo start --android\\",\\n--\\"ios\\": \\"npx expo start --ios\\",\\n++\\"android\\": \\"npx expo run:android\\",\\n++\\"ios\\": \\"npx expo run:ios\\",\\n\\n--\\"android\\": \\"npx expo run:android\\",\\n--\\"ios\\": \\"npx expo run:ios\\",\\n++\\"android\\": \\"npx expo start --android\\",\\n++\\"ios\\": \\"npx expo start --ios\\",\\n\\nrm -rf android ios\\n\\nyarn remove react-native-mmkv\\nnpx expo install @react-native-async-storage/async-storage\\n\\nyarn remove react-native-keyboard-controller\\n\\n-import { KeyboardProvider } from \\"react-native-keyboard-controller\\"\\n\\n// ...\\n\\nreturn (\\n \\n \\n- \\n \\n- \\n \\n \\n)\\n\\nimport { useScrollToTop } from \\"@react-navigation/native\\";\\nimport { StatusBar, StatusBarProps, StatusBarStyle } from \\"expo-status-bar\\";\\nimport React, { useRef, useState } from \\"react\\";\\nimport {\\n KeyboardAvoidingView,\\n KeyboardAvoidingViewProps,\\n LayoutChangeEvent,\\n Platform,\\n ScrollView,\\n ScrollViewProps,\\n StyleProp,\\n View,\\n ViewStyle,\\n} from \\"react-native\\";\\nimport { $styles } from \\"@/theme\\";\\nimport { ExtendedEdge, useSafeAreaInsetsStyle } from \\"@/utils/useSafeAreaInsetsStyle\\";\\nimport { useAppTheme } from \\"@/utils/useAppTheme\\";\\n\\ninterface BaseScreenProps {\\n /**\\n * Children components.\\n */\\n children?: React.ReactNode;\\n /**\\n * Style for the outer content container useful for padding & margin.\\n */\\n style?: StyleProp;\\n /**\\n * Style for the inner content container useful for padding & margin.\\n */\\n contentContainerStyle?: StyleProp;\\n /**\\n * Override the default edges for the safe area.\\n */\\n safeAreaEdges?: ExtendedEdge[];\\n /**\\n * Background color\\n */\\n backgroundColor?: string;\\n /**\\n * Status bar setting. Defaults to dark.\\n */\\n statusBarStyle?: StatusBarStyle;\\n /**\\n * By how much should we offset the keyboard? Defaults to 0.\\n */\\n keyboardOffset?: number;\\n /**\\n * Pass any additional props directly to the StatusBar component.\\n */\\n StatusBarProps?: StatusBarProps;\\n /**\\n * Pass any additional props directly to the KeyboardAvoidingView component.\\n */\\n KeyboardAvoidingViewProps?: KeyboardAvoidingViewProps;\\n}\\n\\ninterface FixedScreenProps extends BaseScreenProps {\\n preset?: \\"fixed\\";\\n}\\ninterface ScrollScreenProps extends BaseScreenProps {\\n preset?: \\"scroll\\";\\n /**\\n * Should keyboard persist on screen tap. Defaults to handled.\\n * Only applies to scroll preset.\\n */\\n keyboardShouldPersistTaps?: \\"handled\\" | \\"always\\" | \\"never\\";\\n /**\\n * Pass any additional props directly to the ScrollView component.\\n */\\n ScrollViewProps?: ScrollViewProps;\\n}\\n\\ninterface AutoScreenProps extends Omit {\\n preset?: \\"auto\\";\\n /**\\n * Threshold to trigger the automatic disabling/enabling of scroll ability.\\n * Defaults to `{ percent: 0.92 }`.\\n */\\n scrollEnabledToggleThreshold?: { percent?: number; point?: number };\\n}\\n\\nexport type ScreenProps = ScrollScreenProps | FixedScreenProps | AutoScreenProps;\\n\\nconst isIos = Platform.OS === \\"ios\\";\\n\\ntype ScreenPreset = \\"fixed\\" | \\"scroll\\" | \\"auto\\";\\n\\n/**\\n * @param {ScreenPreset?} preset - The preset to check.\\n * @returns {boolean} - Whether the preset is non-scrolling.\\n */\\nfunction isNonScrolling(preset?: ScreenPreset) {\\n return !preset || preset === \\"fixed\\";\\n}\\n\\n/**\\n * Custom hook that handles the automatic enabling/disabling of scroll ability based on the content size and screen size.\\n * @param {UseAutoPresetProps} props - The props for the `useAutoPreset` hook.\\n * @returns {{boolean, Function, Function}} - The scroll state, and the `onContentSizeChange` and `onLayout` functions.\\n */\\nfunction useAutoPreset(props: AutoScreenProps): {\\n scrollEnabled: boolean;\\n onContentSizeChange: (w: number, h: number) => void;\\n onLayout: (e: LayoutChangeEvent) => void;\\n} {\\n const { preset, scrollEnabledToggleThreshold } = props;\\n const { percent = 0.92, point = 0 } = scrollEnabledToggleThreshold || {};\\n\\n const scrollViewHeight = useRef(null);\\n const scrollViewContentHeight = useRef(null);\\n const [scrollEnabled, setScrollEnabled] = useState(true);\\n\\n function updateScrollState() {\\n if (scrollViewHeight.current === null || scrollViewContentHeight.current === null) return;\\n\\n // check whether content fits the screen then toggle scroll state according to it\\n const contentFitsScreen = (function () {\\n if (point) {\\n return scrollViewContentHeight.current < scrollViewHeight.current - point;\\n } else {\\n return scrollViewContentHeight.current < scrollViewHeight.current * percent;\\n }\\n })();\\n\\n // content is less than the size of the screen, so we can disable scrolling\\n if (scrollEnabled && contentFitsScreen) setScrollEnabled(false);\\n\\n // content is greater than the size of the screen, so let\'s enable scrolling\\n if (!scrollEnabled && !contentFitsScreen) setScrollEnabled(true);\\n }\\n\\n /**\\n * @param {number} w - The width of the content.\\n * @param {number} h - The height of the content.\\n */\\n function onContentSizeChange(w: number, h: number) {\\n // update scroll-view content height\\n scrollViewContentHeight.current = h;\\n updateScrollState();\\n }\\n\\n /**\\n * @param {LayoutChangeEvent} e = The layout change event.\\n */\\n function onLayout(e: LayoutChangeEvent) {\\n const { height } = e.nativeEvent.layout;\\n // update scroll-view height\\n scrollViewHeight.current = height;\\n updateScrollState();\\n }\\n\\n // update scroll state on every render\\n if (preset === \\"auto\\") updateScrollState();\\n\\n return {\\n scrollEnabled: preset === \\"auto\\" ? scrollEnabled : true,\\n onContentSizeChange,\\n onLayout,\\n };\\n}\\n\\n/**\\n * @param {ScreenProps} props - The props for the `ScreenWithoutScrolling` component.\\n * @returns {JSX.Element} - The rendered `ScreenWithoutScrolling` component.\\n */\\nfunction ScreenWithoutScrolling(props: ScreenProps) {\\n const { style, contentContainerStyle, children } = props;\\n return (\\n \\n {children}\\n \\n );\\n}\\n\\n/**\\n * @param {ScreenProps} props - The props for the `ScreenWithScrolling` component.\\n * @returns {JSX.Element} - The rendered `ScreenWithScrolling` component.\\n */\\nfunction ScreenWithScrolling(props: ScreenProps) {\\n const {\\n children,\\n keyboardShouldPersistTaps = \\"handled\\",\\n contentContainerStyle,\\n ScrollViewProps,\\n style,\\n } = props as ScrollScreenProps;\\n\\n const ref = useRef(null);\\n\\n const { scrollEnabled, onContentSizeChange, onLayout } = useAutoPreset(props as AutoScreenProps);\\n\\n // Add native behavior of pressing the active tab to scroll to the top of the content\\n // More info at: https://reactnavigation.org/docs/use-scroll-to-top/\\n useScrollToTop(ref);\\n\\n return (\\n {\\n onLayout(e);\\n ScrollViewProps?.onLayout?.(e);\\n }}\\n onContentSizeChange={(w: number, h: number) => {\\n onContentSizeChange(w, h);\\n ScrollViewProps?.onContentSizeChange?.(w, h);\\n }}\\n style={[$outerStyle, ScrollViewProps?.style, style]}\\n contentContainerStyle={[\\n $innerStyle,\\n ScrollViewProps?.contentContainerStyle,\\n contentContainerStyle,\\n ]}\\n >\\n {children}\\n \\n );\\n}\\n\\n/**\\n * Represents a screen component that provides a consistent layout and behavior for different screen presets.\\n * The `Screen` component can be used with different presets such as \\"fixed\\", \\"scroll\\", or \\"auto\\".\\n * It handles safe area insets, status bar settings, keyboard avoiding behavior, and scrollability based on the preset.\\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Screen/}\\n * @param {ScreenProps} props - The props for the `Screen` component.\\n * @returns {JSX.Element} The rendered `Screen` component.\\n */\\nexport function Screen(props: ScreenProps) {\\n const {\\n theme: { colors },\\n themeContext,\\n } = useAppTheme();\\n const {\\n backgroundColor,\\n KeyboardAvoidingViewProps,\\n keyboardOffset = 0,\\n safeAreaEdges,\\n StatusBarProps,\\n statusBarStyle,\\n } = props;\\n\\n const $containerInsets = useSafeAreaInsetsStyle(safeAreaEdges);\\n\\n return (\\n \\n \\n\\n \\n {isNonScrolling(props.preset) ? (\\n \\n ) : (\\n \\n )}\\n \\n \\n );\\n}\\n\\nconst $containerStyle: ViewStyle = {\\n flex: 1,\\n height: \\"100%\\",\\n width: \\"100%\\",\\n};\\n\\nconst $outerStyle: ViewStyle = {\\n flex: 1,\\n height: \\"100%\\",\\n width: \\"100%\\",\\n};\\n\\nconst $innerStyle: ViewStyle = {\\n justifyContent: \\"flex-start\\",\\n alignItems: \\"stretch\\",\\n};\\n\\n","lastUpdated":"5 weeks ago","title":"Switch Between Expo Go and Expo CNG","publish_date":"2024-01-11","doc_name":"SwitchBetweenExpoGoCNG.md"},{"author":"Mark Rickert","content":"import { ThemeProvider as EmotionThemeProvider } from \\"@emotion/react\\";\\n\\nconst EmotionJSThemeProvider = (props: React.PropsWithChildren) => {\\n const { theme } = useAppTheme();\\n return {props.children};\\n};\\n\\nreturn (\\n \\n+ \\n \\n \\n \\n+ \\n \\n);\\n\\nimport styled from \\"@emotion/native\\";\\n\\nconst MyTextComponent = styled.Text`\\n margin: 10px;\\n color: ${({ theme }) => theme.colors.text};\\n background-color: ${({ theme }) => theme.colors.background};\\n`;\\n\\nexport const MyScreen = (props) => {\\n return (\\n \\n This text color and background will change when changing themes.\\n \\n );\\n};\\n\\n// Override Theme to get accurate typings for your project.\\nimport type { Theme as AppTheme } from \\"app/theme\\";\\nimport \\"@emotion/react\\";\\n\\ndeclare module \\"@emotion/react\\" {\\n export interface Theme extends AppTheme {}\\n}\\n\\nconst { setThemeContextOverride, themeContext } = useAppTheme();\\n\\nreturn (\\n {\\n LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); // Animate the transition\\n setThemeContextOverride(themeContext === \\"dark\\" ? \\"light\\" : \\"dark\\");\\n }}\\n text={`Switch Theme: ${themeContext}`}\\n />\\n);\\n\\n","lastUpdated":"7 weeks ago","title":"Theming Ignite with Emotion.js","publish_date":"2024-10-02","doc_name":"Theming-Emotion.mdx"},{"author":"Mark Rickert","content":"import { ThemeProvider as StyledThemeProvider } from \\"styled-components\\";\\n\\nconst StyledComponentsThemeProvider = (props: React.PropsWithChildren) => {\\n const { theme } = useAppTheme();\\n return ;\\n};\\n\\nreturn (\\n \\n+ \\n \\n \\n \\n+ \\n \\n);\\n\\nimport styled from \\"styled-components/native\\";\\n\\nconst MyTextComponent = styled.Text`\\n margin: 10px;\\n color: ${({ theme }) => theme.colors.text};\\n background-color: ${({ theme }) => theme.colors.background};\\n`;\\n\\nexport const MyScreen = (props) => {\\n return (\\n \\n This text color and background will change when changing themes.\\n \\n );\\n};\\n\\n// Override DefaultTheme to get accurate typings for your project.\\nimport type { Theme } from \\"app/theme\\";\\nimport \\"styled-components\\";\\nimport \\"styled-components/native\\";\\n\\ndeclare module \\"styled-components\\" {\\n export interface DefaultTheme extends Theme {}\\n}\\n\\ndeclare module \\"styled-components/native\\" {\\n export interface DefaultTheme extends Theme {}\\n}\\n\\nconst { setThemeContextOverride, themeContext } = useAppTheme();\\n\\nreturn (\\n {\\n LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); // Animate the transition\\n setThemeContextOverride(themeContext === \\"dark\\" ? \\"light\\" : \\"dark\\");\\n }}\\n text={`Switch Theme: ${themeContext}`}\\n />\\n);\\n\\n","lastUpdated":"7 weeks ago","title":"Theming Ignite with styled-components","publish_date":"2024-10-02","doc_name":"Theming-StyledComponents.mdx"},{"author":"Mark Rickert","content":"// Override UnistylesThemes to get accurate typings for your project.\\nimport type { Theme } from \\"app/theme\\";\\nimport \\"react-native-unistyles\\";\\n\\ntype AppThemes = {\\n light: Theme;\\n dark: Theme;\\n};\\n\\ndeclare module \\"react-native-unistyles\\" {\\n export interface UnistylesThemes extends AppThemes {}\\n}\\n\\n+import { UnistylesRegistry } from \\"react-native-unistyles\\"\\n+import { darkTheme, lightTheme } from \\"app/theme\\"\\n\\nSplashScreen.preventAutoHideAsync()\\n\\n+UnistylesRegistry.addThemes({\\n+ light: lightTheme,\\n+ dark: darkTheme,\\n+}).addConfig({\\n+ // adaptiveThemes: true,\\n+ initialTheme: \\"light\\",\\n+})\\n+\\n\\nfunction IgniteApp() {\\n return \\n}\\n\\n const setThemeContextOverride = useCallback((newTheme: ThemeContexts) => {\\n setTheme(newTheme)\\n+ UnistylesRuntime.setTheme(newTheme || \\"light\\")\\n }, [])\\n\\nimport { createStyleSheet, useStyles } from \\"react-native-unistyles\\";\\n\\nexport const MyScreen = (props) => {\\n const { styles } = useStyles($uniStyles);\\n return (\\n \\n This text color and background will change when changing themes.\\n \\n );\\n};\\n\\nconst $uniStyles = createStyleSheet((theme) => ({\\n text: {\\n color: theme.colors.text,\\n backgroundColor: theme.colors.background,\\n },\\n}));\\n\\nconst { setThemeContextOverride, themeContext } = useAppTheme();\\n\\nreturn (\\n {\\n LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); // Animate the transition\\n setThemeContextOverride(themeContext === \\"dark\\" ? \\"light\\" : \\"dark\\");\\n }}\\n text={`Switch Theme: ${themeContext}`}\\n />\\n);\\n\\n","lastUpdated":"7 weeks ago","title":"Theming Ignite with Unistyles","publish_date":"2024-10-02","doc_name":"Theming-Unistyles.mdx"},{"author":"Frank Calise","content":"import { Thing } from \\"../../../../../components/thing\\";\\n\\nimport { Thing } from \\"~/components/thing\\";\\n\\nyarn add -D babel-plugin-root-import\\n\\n{\\n // ...\\n \\"baseUrl\\": \\"./\\",\\n // the following assumes Ignite\'s app/ structure, however yours may differ\\n \\"paths\\": { \\"~/*\\": [\\"app/*\\"] }\\n}\\n\\n[\\n \\"babel-plugin-root-import\\",\\n {\\n root: __dirname,\\n rootPathPrefix: \\"~/\\",\\n // mapping ~/ to the ./app directory (again, your app structure may differ here)\\n rootPathSuffix: \\"app\\",\\n },\\n],\\n\\nimport { ListItem, Screen, Text } from \\"../../components\\";\\nimport { isRTL } from \\"../../i18n\\";\\nimport { DemoTabScreenProps } from \\"../../navigators/DemoNavigator\\";\\nimport { colors, spacing } from \\"../../theme\\";\\n\\nimport { ListItem, Screen, Text } from \\"~/components\\";\\nimport { isRTL } from \\"~/i18n\\";\\nimport { DemoTabScreenProps } from \\"~/navigators/DemoNavigator\\";\\nimport { colors, spacing } from \\"~/theme\\";\\n\\nyarn expo:start\\n\\nyarn expo:start --clear\\n\\n","lastUpdated":"1 year, 9 months ago","title":"TypeScript baseUrl Configuration","publish_date":"2022-10-24","doc_name":"TypeScriptBaseURL.md"},{"author":"Mark Rickert","content":"import * as React from \'react\';\\nimport { SectionList, SectionListProps, SectionListScrollParams } from \'react-native\';\\n\\ninterface SectionListHandle {\\n scrollToLocation: (params: SectionListScrollParams) => void;\\n}\\n\\n/**\\n * This is a wrapper around react-native\'s SectionList that adds protection against scrolling to an\\n * unknown (not rendered yet) location. This is useful for cases where the user wants to scroll to a\\n * position very far down the list but we haven\'t rendered that far yet.\\n *\\n * This adds onScrollToIndexFailed property to SectionList so that if the scroll fails, we calculate the approximate\\n * scroll position, scroll there, and then try again to get the exact position requested.\\n *\\n * Essentially, it\'s a \\"guess the position and retry the operation\\" strategy until the list is scrolled to the\\n * correct location.\\n */\\nexport const ScrollProtectedSectionList = React.forwardRef<\\n SectionListHandle,\\n SectionListProps\\n>((props, forwardedRef) => {\\n const internalRef = React.useRef(null);\\n const [lastScrollRequest, setLastScrollRequest] = React.useState();\\n const timeout = React.useRef>();\\n\\n const onScrollToIndexFailed = (info: {\\n index: number;\\n highestMeasuredFrameIndex: number;\\n averageItemLength: number;\\n }) => {\\n console.log(\'ScrollProtectedSectionList.onScrollToIndexFailed\', info);\\n\\n // Calculate the possible position of the item and scroll there using the internal scroll responder.\\n const offset = info.averageItemLength * info.index;\\n internalRef.current?.getScrollResponder()?.scrollTo({ x: 0, y: offset, animated: false });\\n\\n // If we know exactly where we want to scroll to, we can just scroll now since the item is likely visible.\\n // Otherwise it\'ll call this function recursively again.\\n if (lastScrollRequest) {\\n timeout.current = setTimeout(() => {\\n internalRef.current?.scrollToLocation(lastScrollRequest);\\n }, 100);\\n }\\n };\\n\\n // Clear the timeout if it still exists when the component unmounts.\\n React.useEffect(() => {\\n return () => timeout.current && clearTimeout(timeout.current);\\n }, []);\\n\\n React.useImperativeHandle(\\n forwardedRef,\\n () => ({\\n scrollToLocation: (params: SectionListScrollParams) => {\\n internalRef.current?.scrollToLocation(params);\\n setLastScrollRequest(params);\\n },\\n }),\\n [internalRef],\\n );\\n\\n return ;\\n});\\n\\n","lastUpdated":"6 months ago","title":"Scrolling to a location that hasn\'t been rendered using FlatList or SectionList","publish_date":"2022-10-09","doc_name":"UnrenderedItemInScrollView.md"},{"author":"Derek Greenberg","content":"yarn audit\\n\\nyarn outdated\\n\\nyarn upgrade-interactive\\nyarn upgrade-interactive --latest\\n\\n","lastUpdated":"10 months ago","title":"Updating Dependencies with Yarn Audit, Outdated and Upgrade","publish_date":"2022-10-09","doc_name":"UpdatingDependencies.md"},{"author":"Lizzi Lindboe","content":"# enable\\nadb shell settings put secure enabled_accessibility_services \\\\\\ncom.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService\\n# disable\\nadb shell settings put secure enabled_accessibility_services \\\\\\ncom.android.talkback/com.google.android.marvin.talkback.TalkBackService\\n\\n","lastUpdated":"1 year, 11 months ago","title":"Using Screen Readers","publish_date":"2022-10-09","doc_name":"UsingScreenReaders.md"},{"author":"Justin Poliachik","content":"npx ignite-cli new ZustandApp --yes\\n\\nyarn add zustand\\n\\nmkdir app/store\\n\\nimport { Instance, SnapshotOut, types } from \\"mobx-state-tree\\";\\n\\nexport const AuthenticationStoreModel = types\\n .model(\\"AuthenticationStore\\")\\n .props({\\n authToken: types.maybe(types.string),\\n authEmail: \\"\\",\\n })\\n .views((store) => ({\\n get isAuthenticated() {\\n return !!store.authToken;\\n },\\n get validationError() {\\n if (store.authEmail.length === 0) return \\"can\'t be blank\\";\\n if (store.authEmail.length < 6) return \\"must be at least 6 characters\\";\\n if (!/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(store.authEmail)) return \\"must be a valid email address\\";\\n return \\"\\";\\n },\\n }))\\n .actions((store) => ({\\n setAuthToken(value?: string) {\\n store.authToken = value;\\n },\\n setAuthEmail(value: string) {\\n store.authEmail = value.replace(/ /g, \\"\\");\\n },\\n logout() {\\n store.authToken = undefined;\\n store.authEmail = \\"\\";\\n },\\n }));\\n\\nexport interface AuthenticationStore extends Instance {}\\nexport interface AuthenticationStoreSnapshot extends SnapshotOut {}\\n\\nimport { StateCreator } from \\"zustand\\";\\nimport { RootStore } from \\"./RootStore\\";\\n\\n// Typescript interface for this store slice\\nexport interface AuthenticationStore {\\n authToken?: string;\\n authEmail: string;\\n setAuthToken: (value?: string) => void;\\n setAuthEmail: (value: string) => void;\\n logout: () => void;\\n}\\n\\n// create our store slice with default data and actions\\nexport const createAuthenticationSlice: StateCreator = (set) => ({\\n authToken: undefined,\\n authEmail: \\"\\",\\n setAuthToken: (value) => set({ authToken: value }),\\n setAuthEmail: (value) => set({ authEmail: value.replace(/ /g, \\"\\") }),\\n logout: () => set({ authToken: undefined, authEmail: \\"\\" }),\\n});\\n\\n// a selector can be used to grab the full AuthenticationStore\\nexport const authenticationStoreSelector = (state: RootStore) => ({\\n authToken: state.authToken,\\n authEmail: state.authEmail,\\n isAuthenticated: isAuthenticatedSelector(state),\\n setAuthToken: state.setAuthToken,\\n setAuthEmail: state.setAuthEmail,\\n logout: state.logout,\\n});\\n\\n// selectors can also be used for derived values\\nexport const isAuthenticatedSelector = (state: RootStore) => !!state.authToken;\\n\\nexport const validationErrorSelector = (state: RootStore) => {\\n if (state.authEmail.length === 0) return \\"can\'t be blank\\";\\n if (state.authEmail.length < 6) return \\"must be at least 6 characters\\";\\n if (!/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(state.authEmail)) return \\"must be a valid email address\\";\\n return \\"\\";\\n};\\n\\nimport { Instance, SnapshotOut, types } from \\"mobx-state-tree\\";\\nimport { api } from \\"../services/api\\";\\nimport { Episode, EpisodeModel } from \\"./Episode\\";\\nimport { withSetPropAction } from \\"./helpers/withSetPropAction\\";\\n\\nexport const EpisodeStoreModel = types\\n .model(\\"EpisodeStore\\")\\n .props({\\n episodes: types.array(EpisodeModel),\\n favorites: types.array(types.reference(EpisodeModel)),\\n favoritesOnly: false,\\n })\\n .actions(withSetPropAction)\\n .actions((store) => ({\\n async fetchEpisodes() {\\n const response = await api.getEpisodes();\\n if (response.kind === \\"ok\\") {\\n store.setProp(\\"episodes\\", response.episodes);\\n } else {\\n console.error(`Error fetching episodes: ${JSON.stringify(response)}`);\\n }\\n },\\n addFavorite(episode: Episode) {\\n store.favorites.push(episode);\\n },\\n removeFavorite(episode: Episode) {\\n store.favorites.remove(episode);\\n },\\n }))\\n .views((store) => ({\\n get episodesForList() {\\n return store.favoritesOnly ? store.favorites : store.episodes;\\n },\\n\\n hasFavorite(episode: Episode) {\\n return store.favorites.includes(episode);\\n },\\n }))\\n .actions((store) => ({\\n toggleFavorite(episode: Episode) {\\n if (store.hasFavorite(episode)) {\\n store.removeFavorite(episode);\\n } else {\\n store.addFavorite(episode);\\n }\\n },\\n }));\\n\\nexport interface EpisodeStore extends Instance {}\\nexport interface EpisodeStoreSnapshot extends SnapshotOut {}\\n\\nimport { api } from \\"../services/api\\";\\nimport { Episode } from \\"./Episode\\";\\nimport { StateCreator } from \\"zustand\\";\\nimport { RootStore } from \\"./RootStore\\";\\n\\nexport interface EpisodeStore {\\n episodes: Episode[];\\n favorites: string[];\\n favoritesOnly: boolean;\\n\\n fetchEpisodes: () => Promise;\\n addFavorite: (episode: Episode) => void;\\n removeFavorite: (episode: Episode) => void;\\n toggleFavorite: (episode: Episode) => void;\\n setFavoritesOnly: (value: boolean) => void;\\n}\\n\\nexport const createEpisodeSlice: StateCreator = (set, get) => ({\\n episodes: [],\\n favorites: [],\\n favoritesOnly: false,\\n\\n // Zustand supports async actions\\n fetchEpisodes: async () => {\\n const response = await api.getEpisodes();\\n if (response.kind === \\"ok\\") {\\n set({ episodes: response.episodes });\\n } else {\\n console.error(`Error fetching episodes: ${JSON.stringify(response)}`);\\n }\\n },\\n addFavorite: (episode) => set((state) => ({ favorites: [...state.favorites, episode.guid] })),\\n removeFavorite: (episode) => set((state) => ({ favorites: state.favorites.filter((guid) => guid !== episode.guid) })),\\n toggleFavorite: (episode) => {\\n // get() can be used within actions\\n if (get().favorites.includes(episode.guid)) {\\n get().removeFavorite(episode);\\n } else {\\n get().addFavorite(episode);\\n }\\n },\\n setFavoritesOnly: (value: boolean) => set({ favoritesOnly: value }),\\n});\\n\\nexport const episodeStoreSelector = (state: RootStore) => ({\\n episodes: state.episodes,\\n favorites: state.favorites,\\n favoritesOnly: state.favoritesOnly,\\n\\n // derived values can be included in selectors like this\\n episodesForList: getEpisodesForList(state),\\n\\n fetchEpisodes: state.fetchEpisodes,\\n addFavorite: state.addFavorite,\\n removeFavorite: state.removeFavorite,\\n toggleFavorite: state.toggleFavorite,\\n setFavoritesOnly: state.setFavoritesOnly,\\n\\n // we can also include helper functions that have access to state\\n hasFavorite: (episode: Episode) => {\\n return state.favorites.includes(episode.guid);\\n },\\n});\\n\\nexport const getEpisodesForList = (store: EpisodeStore) => {\\n return store.favoritesOnly ? store.episodes.filter((a) => store.favorites.includes(a.guid)) : store.episodes;\\n};\\n\\nimport { Instance, SnapshotIn, SnapshotOut, types } from \\"mobx-state-tree\\";\\nimport { withSetPropAction } from \\"./helpers/withSetPropAction\\";\\nimport { formatDate } from \\"../utils/formatDate\\";\\nimport { translate } from \\"../i18n\\";\\n\\ninterface Enclosure {\\n link: string;\\n type: string;\\n length: number;\\n duration: number;\\n rating: { scheme: string; value: string };\\n}\\n\\n/**\\n * This represents an episode of React Native Radio.\\n */\\nexport const EpisodeModel = types\\n .model(\\"Episode\\")\\n .props({\\n guid: types.identifier,\\n title: \\"\\",\\n pubDate: \\"\\", // Ex: 2022-08-12 21:05:36\\n link: \\"\\",\\n author: \\"\\",\\n thumbnail: \\"\\",\\n description: \\"\\",\\n content: \\"\\",\\n enclosure: types.frozen(),\\n categories: types.array(types.string),\\n })\\n .actions(withSetPropAction)\\n .views((episode) => ({\\n get parsedTitleAndSubtitle() {\\n const defaultValue = { title: episode.title?.trim(), subtitle: \\"\\" };\\n\\n if (!defaultValue.title) return defaultValue;\\n\\n const titleMatches = defaultValue.title.match(/^(RNR.*\\\\d)(?: - )(.*$)/);\\n\\n if (!titleMatches || titleMatches.length !== 3) return defaultValue;\\n\\n return { title: titleMatches[1], subtitle: titleMatches[2] };\\n },\\n get datePublished() {\\n try {\\n const formatted = formatDate(episode.pubDate);\\n return {\\n textLabel: formatted,\\n accessibilityLabel: translate(\\"demoPodcastListScreen.accessibility.publishLabel\\", {\\n date: formatted,\\n }),\\n };\\n } catch (error) {\\n return { textLabel: \\"\\", accessibilityLabel: \\"\\" };\\n }\\n },\\n get duration() {\\n const seconds = Number(episode.enclosure.duration);\\n const h = Math.floor(seconds / 3600);\\n const m = Math.floor((seconds % 3600) / 60);\\n const s = Math.floor((seconds % 3600) % 60);\\n\\n const hDisplay = h > 0 ? `${h}:` : \\"\\";\\n const mDisplay = m > 0 ? `${m}:` : \\"\\";\\n const sDisplay = s > 0 ? s : \\"\\";\\n return {\\n textLabel: hDisplay + mDisplay + sDisplay,\\n accessibilityLabel: translate(\\"demoPodcastListScreen.accessibility.durationLabel\\", {\\n hours: h,\\n minutes: m,\\n seconds: s,\\n }),\\n };\\n },\\n }));\\n\\nexport interface Episode extends Instance {}\\nexport interface EpisodeSnapshotOut extends SnapshotOut {}\\nexport interface EpisodeSnapshotIn extends SnapshotIn {}\\n\\nimport { formatDate } from \\"../utils/formatDate\\";\\nimport { translate } from \\"../i18n\\";\\n\\ninterface Enclosure {\\n link: string;\\n type: string;\\n length: number;\\n duration: number;\\n rating: { scheme: string; value: string };\\n}\\n\\nexport type Episode = {\\n guid: string;\\n title: string;\\n pubDate: string;\\n link: string;\\n author: string;\\n thumbnail: string;\\n description: string;\\n content: string;\\n enclosure: Enclosure;\\n categories: string[];\\n};\\n\\nexport const getParsedTitleAndSubtitle = (episode: Episode) => {\\n const defaultValue = { title: episode.title?.trim(), subtitle: \\"\\" };\\n\\n if (!defaultValue.title) return defaultValue;\\n\\n const titleMatches = defaultValue.title.match(/^(RNR.*\\\\d)(?: - )(.*$)/);\\n\\n if (!titleMatches || titleMatches.length !== 3) return defaultValue;\\n\\n return { title: titleMatches[1], subtitle: titleMatches[2] };\\n};\\n\\nexport const getDatePublished = (episode: Episode) => {\\n try {\\n const formatted = formatDate(episode.pubDate);\\n return {\\n textLabel: formatted,\\n accessibilityLabel: translate(\\"demoPodcastListScreen.accessibility.publishLabel\\", {\\n date: formatted,\\n }),\\n };\\n } catch (error) {\\n return { textLabel: \\"\\", accessibilityLabel: \\"\\" };\\n }\\n};\\n\\nexport const getDuration = (episode: Episode) => {\\n const seconds = Number(episode.enclosure.duration);\\n const h = Math.floor(seconds / 3600);\\n const m = Math.floor((seconds % 3600) / 60);\\n const s = Math.floor((seconds % 3600) % 60);\\n\\n const hDisplay = h > 0 ? `${h}:` : \\"\\";\\n const mDisplay = m > 0 ? `${m}:` : \\"\\";\\n const sDisplay = s > 0 ? s : \\"\\";\\n return {\\n textLabel: hDisplay + mDisplay + sDisplay,\\n accessibilityLabel: translate(\\"demoPodcastListScreen.accessibility.durationLabel\\", {\\n hours: h,\\n minutes: m,\\n seconds: s,\\n }),\\n };\\n};\\n\\nimport { create } from \\"zustand\\";\\nimport { useShallow } from \\"zustand/react/shallow\\";\\nimport { AuthenticationStore, authenticationStoreSelector, createAuthenticationSlice } from \\"./AuthenticationStore\\";\\nimport { EpisodeStore, createEpisodeSlice, episodeStoreSelector } from \\"./EpisodeStore\\";\\n\\nexport interface RootStore extends AuthenticationStore, EpisodeStore {}\\n\\nexport const useStore = create()((...a) => ({\\n ...createAuthenticationSlice(...a),\\n ...createEpisodeSlice(...a),\\n // add your state slices here\\n}));\\n\\n// optional: custom hooks can be used to pick pieces from state\\n// useShallow is used to help prevent unnecessary rerenders\\nexport const useAuthenticationStore = () => useStore(useShallow(authenticationStoreSelector));\\nexport const useEpisodeStore = () => useStore(useShallow(episodeStoreSelector));\\n\\nexport * from \\"./RootStore\\";\\nexport * from \\"./AuthenticationStore\\";\\nexport * from \\"./EpisodeStore\\";\\nexport * from \\"./Episode\\";\\n\\nimport { useStore, isAuthenticatedSelector } from \\"app/store\\";\\n\\nconst AppStack = () => {\\n\\n // use a selector to pick only that value\\n const isAuthenticated = useStore(isAuthenticatedSelector)\\n\\n return (\\n {\\n ;(async function load() {\\n setIsLoading(true)\\n await episodeStore.fetchEpisodes()\\n setIsLoading(false)\\n })()\\n-- }, [episodeStore])\\n++ }, [])\\n\\n\\n\\n-- episodeStore.setProp(\\"favoritesOnly\\", !episodeStore.favoritesOnly)\\n++ episodeStore.setFavoritesOnly(!episodeStore.favoritesOnly)\\n }\\n\\n\\nconst datePublished = getDatePublished(episode);\\nconst duration = getDuration(episode);\\nconst parsedTitleAndSubtitle = getParsedTitleAndSubtitle(episode);\\n\\n \\n--{episode.datePublished.textLabel}\\n++{datePublished.textLabel}\\n\\n\\n++import { useStore } from \\"app/store\\"\\n\\n--const {\\n-- authenticationStore: { logout },\\n--} = useStores()\\n++const logout = useStore((state) => state.logout)\\n\\n++import { useStore } from \\"app/store\\"\\n\\n--const {\\n-- authenticationStore: { logout },\\n--} = useStores()\\n++const logout = useStore((state) => state.logout)\\n\\n+import { Episode } from \\"app/store/Episode\\";\\n\\n-const episodes: EpisodeSnapshotIn[] =\\n+const episodes: Episode[] =\\n\\nimport { create } from \\"zustand\\";\\nimport { useShallow } from \\"zustand/react/shallow\\";\\nimport { persist, createJSONStorage } from \\"zustand/middleware\\";\\n\\nimport { AuthenticationStore, authenticationStoreSelector, createAuthenticationSlice } from \\"./AuthenticationStore\\";\\nimport { EpisodeStore, createEpisodeSlice, episodeStoreSelector } from \\"./EpisodeStore\\";\\nimport AsyncStorage from \\"@react-native-async-storage/async-storage\\";\\n\\nexport interface RootStore extends AuthenticationStore, EpisodeStore {\\n _hasHydrated: boolean;\\n setHasHydrated: (state: boolean) => void;\\n}\\n\\nexport const useStore = create()(\\n persist(\\n (...a) => ({\\n ...createAuthenticationSlice(...a),\\n ...createEpisodeSlice(...a),\\n // add your state slices here\\n\\n _hasHydrated: false,\\n setHasHydrated: (state) => {\\n const set = a[0];\\n set({\\n _hasHydrated: state,\\n });\\n },\\n }),\\n {\\n name: \\"zustand-app\\",\\n storage: createJSONStorage(() => AsyncStorage),\\n onRehydrateStorage: () => (state) => {\\n state?.setHasHydrated(true);\\n },\\n }\\n )\\n);\\n\\nexport const useAuthenticationStore = () => useStore(useShallow(authenticationStoreSelector));\\nexport const useEpisodeStore = () => useStore(useShallow(episodeStoreSelector));\\n\\n+import { useStore } from \\"./store\\"\\n\\n...\\n\\nconst [areFontsLoaded, fontLoadError] = useFonts(customFontsToLoad)\\n\\n-const { rehydrated } = useInitialRootStore(() => {\\n- setTimeout(hideSplashScreen, 500)\\n-})\\n\\n\\n+const rehydrated = useStore((state) => state._hasHydrated)\\n+useEffect(() => {\\n+ if (rehydrated) {\\n+ setTimeout(hideSplashScreen, 500)\\n+ }\\n+}, [rehydrated])\\n\\n\\n","lastUpdated":"4 weeks ago","title":"Zustand","publish_date":"2024-02-05","doc_name":"Zustand.md"}]}},"docusaurus-plugin-content-docs":{"default":{"path":"/docs","versions":[{"name":"current","label":"Next","isLast":true,"path":"/docs","mainDocId":"intro","docs":[{"id":"archive/index","path":"/docs/archive/","sidebar":"mainSidebar"},{"id":"archive/PristineExpoProject","path":"/docs/archive/PristineExpoProject","sidebar":"mainSidebar"},{"id":"communityRecipes/CustomVectorIcons","path":"/docs/communityRecipes/CustomVectorIcons","sidebar":"mainSidebar"},{"id":"communityRecipes/index","path":"/docs/communityRecipes/","sidebar":"mainSidebar"},{"id":"intro","path":"/docs/intro","sidebar":"mainSidebar"},{"id":"recipes/AccessibilityFontSizes","path":"/docs/recipes/AccessibilityFontSizes","sidebar":"mainSidebar"},{"id":"recipes/ApolloClientCache","path":"/docs/recipes/ApolloClientCache","sidebar":"mainSidebar"},{"id":"recipes/Authentication","path":"/docs/recipes/Authentication","sidebar":"mainSidebar"},{"id":"recipes/CircleCIRNSetup","path":"/docs/recipes/CircleCIRNSetup","sidebar":"mainSidebar"},{"id":"recipes/CreatingGreateExperienceForScreenReaders","path":"/docs/recipes/CreatingGreateExperienceForScreenReaders","sidebar":"mainSidebar"},{"id":"recipes/DetoxIntro","path":"/docs/recipes/DetoxIntro","sidebar":"mainSidebar"},{"id":"recipes/DistributingAuthTokenToAPI","path":"/docs/recipes/DistributingAuthTokenToAPI","sidebar":"mainSidebar"},{"id":"recipes/EASUpdate","path":"/docs/recipes/EASUpdate","sidebar":"mainSidebar"},{"id":"recipes/EnforcingImportOrder","path":"/docs/recipes/EnforcingImportOrder","sidebar":"mainSidebar"},{"id":"recipes/EnvironmentVariables","path":"/docs/recipes/EnvironmentVariables","sidebar":"mainSidebar"},{"id":"recipes/ExpoRouter","path":"/docs/recipes/ExpoRouter","sidebar":"mainSidebar"},{"id":"recipes/GeneratorComponentTests","path":"/docs/recipes/GeneratorComponentTests","sidebar":"mainSidebar"},{"id":"recipes/LocalFirstDataWithPowerSync","path":"/docs/recipes/LocalFirstDataWithPowerSync","sidebar":"mainSidebar"},{"id":"recipes/MaestroSetup","path":"/docs/recipes/MaestroSetup","sidebar":"mainSidebar"},{"id":"recipes/MigratingToI18Next","path":"/docs/recipes/MigratingToI18Next","sidebar":"mainSidebar"},{"id":"recipes/MigratingToMMKV","path":"/docs/recipes/MigratingToMMKV","sidebar":"mainSidebar"},{"id":"recipes/MonoreposOverview","path":"/docs/recipes/MonoreposOverview","sidebar":"mainSidebar"},{"id":"recipes/PatchingBuildingAndroid","path":"/docs/recipes/PatchingBuildingAndroid","sidebar":"mainSidebar"},{"id":"recipes/PrepForEASBuild","path":"/docs/recipes/PrepForEASBuild","sidebar":"mainSidebar"},{"id":"recipes/ReactNativeVisionCamera","path":"/docs/recipes/ReactNativeVisionCamera","sidebar":"mainSidebar"},{"id":"recipes/Redux","path":"/docs/recipes/Redux","sidebar":"mainSidebar"},{"id":"recipes/RemoveMobxStateTree","path":"/docs/recipes/RemoveMobxStateTree","sidebar":"mainSidebar"},{"id":"recipes/RequiringHardwareFeaturesWithExpo","path":"/docs/recipes/RequiringHardwareFeaturesWithExpo","sidebar":"mainSidebar"},{"id":"recipes/SampleYAMLCircleCI","path":"/docs/recipes/SampleYAMLCircleCI","sidebar":"mainSidebar"},{"id":"recipes/SelectFieldWithBottomSheet","path":"/docs/recipes/SelectFieldWithBottomSheet","sidebar":"mainSidebar"},{"id":"recipes/SettingUpYarnMonorepo","path":"/docs/recipes/SettingUpYarnMonorepo","sidebar":"mainSidebar"},{"id":"recipes/SwitchBetweenExpoGoCNG","path":"/docs/recipes/SwitchBetweenExpoGoCNG","sidebar":"mainSidebar"},{"id":"recipes/Theming-Emotion","path":"/docs/recipes/Theming-Emotion","sidebar":"mainSidebar"},{"id":"recipes/Theming-StyledComponents","path":"/docs/recipes/Theming-StyledComponents","sidebar":"mainSidebar"},{"id":"recipes/Theming-Unistyles","path":"/docs/recipes/Theming-Unistyles","sidebar":"mainSidebar"},{"id":"recipes/TypeScriptBaseURL","path":"/docs/recipes/TypeScriptBaseURL","sidebar":"mainSidebar"},{"id":"recipes/UnrenderedItemInScrollView","path":"/docs/recipes/UnrenderedItemInScrollView","sidebar":"mainSidebar"},{"id":"recipes/UpdatingDependencies","path":"/docs/recipes/UpdatingDependencies","sidebar":"mainSidebar"},{"id":"recipes/UpdatingIgnite","path":"/docs/recipes/UpdatingIgnite","sidebar":"mainSidebar"},{"id":"recipes/UsingScreenReaders","path":"/docs/recipes/UsingScreenReaders","sidebar":"mainSidebar"},{"id":"recipes/Zustand","path":"/docs/recipes/Zustand","sidebar":"mainSidebar"}],"draftIds":[],"sidebars":{"mainSidebar":{"link":{"path":"/docs/intro","label":"intro"}}}}],"breadcrumbs":true}}}'),s=JSON.parse('{"defaultLocale":"en","locales":["en"],"path":"i18n","currentLocale":"en","localeConfigs":{"en":{"label":"English","direction":"ltr","htmlLang":"en","calendar":"gregory","path":"en"}}}');var i=t(144);const l=JSON.parse('{"docusaurusVersion":"3.1.1","siteVersion":"0.0.0","pluginVersions":{"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"3.1.1"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"3.1.1"},"docusaurus-plugin-google-gtag":{"type":"package","name":"@docusaurus/plugin-google-gtag","version":"3.1.1"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"3.1.1"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"3.1.1"},"docusaurus-theme-search-algolia":{"type":"package","name":"@docusaurus/theme-search-algolia","version":"3.1.1"},"example-code-snippets":{"type":"local"}}}');var c=t(7624);const u={siteConfig:o.default,siteMetadata:l,globalData:a,i18n:s,codeTranslations:i},d=r.createContext(u);function p(e){let{children:n}=e;return(0,c.jsx)(d.Provider,{value:u,children:n})}},5852:(e,n,t)=>{"use strict";t.d(n,{c:()=>f});var r=t(1504),o=t(8684),a=t(6952),s=t(5684),i=t(7468),l=t(7624);function c(e){let{error:n,tryAgain:t}=e;return(0,l.jsxs)("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"flex-start",minHeight:"100vh",width:"100%",maxWidth:"80ch",fontSize:"20px",margin:"0 auto",padding:"1rem"},children:[(0,l.jsx)("h1",{style:{fontSize:"3rem"},children:"This page crashed"}),(0,l.jsx)("button",{type:"button",onClick:t,style:{margin:"1rem 0",fontSize:"2rem",cursor:"pointer",borderRadius:20,padding:"1rem"},children:"Try again"}),(0,l.jsx)(u,{error:n})]})}function u(e){let{error:n}=e;const t=(0,s.getErrorCausalChain)(n).map((e=>e.message)).join("\n\nCause:\n");return(0,l.jsx)("p",{style:{whiteSpace:"pre-wrap"},children:t})}function d(e){let{error:n,tryAgain:t}=e;return(0,l.jsxs)(f,{fallback:()=>(0,l.jsx)(c,{error:n,tryAgain:t}),children:[(0,l.jsx)(a.c,{children:(0,l.jsx)("title",{children:"Page Error"})}),(0,l.jsx)(i.c,{children:(0,l.jsx)(c,{error:n,tryAgain:t})})]})}const p=e=>(0,l.jsx)(d,{...e});class f extends r.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){o.c.canUseDOM&&this.setState({error:e})}render(){const{children:e}=this.props,{error:n}=this.state;if(n){const e={error:n,tryAgain:()=>this.setState({error:null})};return(this.props.fallback??p)(e)}return e??null}}},8684:(e,n,t)=>{"use strict";t.d(n,{c:()=>o});const r="undefined"!=typeof window&&"document"in window&&"createElement"in window.document,o={canUseDOM:r,canUseEventListeners:r&&("addEventListener"in window||"attachEvent"in window),canUseIntersectionObserver:r&&"IntersectionObserver"in window,canUseViewport:r&&"screen"in window}},6952:(e,n,t)=>{"use strict";t.d(n,{c:()=>a});t(1504);var r=t(2160),o=t(7624);function a(e){return(0,o.jsx)(r.So,{...e})}},867:(e,n,t)=>{"use strict";t.d(n,{c:()=>f});var r=t(1504),o=t(440),a=t(5684),s=t(8264),i=t(8136),l=t(8684),c=t(5976),u=t(964),d=t(7624);function p(e,n){let{isNavLink:t,to:p,href:f,activeClassName:m,isActive:g,"data-noBrokenLinkCheck":h,autoAddBaseUrl:y=!0,...b}=e;const{siteConfig:{trailingSlash:v,baseUrl:S}}=(0,s.c)(),{withBaseUrl:w}=(0,u.E)(),x=(0,c.c)(),k=(0,r.useRef)(null);(0,r.useImperativeHandle)(n,(()=>k.current));const T=p||f;const E=(0,i.c)(T),C=T?.replace("pathname://","");let A=void 0!==C?(_=C,y&&(e=>e.startsWith("/"))(_)?w(_):_):void 0;var _;A&&E&&(A=(0,a.applyTrailingSlash)(A,{trailingSlash:v,baseUrl:S}));const I=(0,r.useRef)(!1),P=t?o.Af:o.cH,R=l.c.canUseIntersectionObserver,L=(0,r.useRef)(),O=()=>{I.current||null==A||(window.docusaurus.preload(A),I.current=!0)};(0,r.useEffect)((()=>(!R&&E&&null!=A&&window.docusaurus.prefetch(A),()=>{R&&L.current&&L.current.disconnect()})),[L,A,R,E]);const N=A?.startsWith("#")??!1,F=!b.target||"_self"===b.target,j=!A||!E||!F||N;return h||!N&&j||x.collectLink(A),b.id&&x.collectAnchor(b.id),j?(0,d.jsx)("a",{ref:k,href:A,...T&&!E&&{target:"_blank",rel:"noopener noreferrer"},...b}):(0,d.jsx)(P,{...b,onMouseEnter:O,onTouchStart:O,innerRef:e=>{k.current=e,R&&e&&E&&(L.current=new window.IntersectionObserver((n=>{n.forEach((n=>{e===n.target&&(n.isIntersecting||n.intersectionRatio>0)&&(L.current.unobserve(e),L.current.disconnect(),null!=A&&window.docusaurus.prefetch(A))}))})),L.current.observe(e))},to:A,...t&&{isActive:g,activeClassName:m}})}const f=r.forwardRef(p)},4357:(e,n,t)=>{"use strict";t.d(n,{c:()=>c,G:()=>l});var r=t(1504),o=t(7624);function a(e,n){const t=e.split(/(\{\w+\})/).map(((e,t)=>{if(t%2==1){const t=n?.[e.slice(1,-1)];if(void 0!==t)return t}return e}));return t.some((e=>(0,r.isValidElement)(e)))?t.map(((e,n)=>(0,r.isValidElement)(e)?r.cloneElement(e,{key:n}):e)).filter((e=>""!==e)):t.join("")}var s=t(144);function i(e){let{id:n,message:t}=e;if(void 0===n&&void 0===t)throw new Error("Docusaurus translation declarations must have at least a translation id or a default translation message");return s[n??t]??t??n}function l(e,n){let{message:t,id:r}=e;return a(i({message:t,id:r}),n)}function c(e){let{children:n,id:t,values:r}=e;if(n&&"string"!=typeof n)throw console.warn("Illegal children",n),new Error("The Docusaurus component only accept simple string values");const s=i({message:n,id:t});return(0,o.jsx)(o.Fragment,{children:a(s,r)})}},2488:(e,n,t)=>{"use strict";t.d(n,{M:()=>r});const r="default"},8136:(e,n,t)=>{"use strict";function r(e){return/^(?:\w*:|\/\/)/.test(e)}function o(e){return void 0!==e&&!r(e)}t.d(n,{_:()=>r,c:()=>o})},964:(e,n,t)=>{"use strict";t.d(n,{E:()=>s,c:()=>i});var r=t(1504),o=t(8264),a=t(8136);function s(){const{siteConfig:{baseUrl:e,url:n}}=(0,o.c)(),t=(0,r.useCallback)(((t,r)=>function(e,n,t,r){let{forcePrependBaseUrl:o=!1,absolute:s=!1}=void 0===r?{}:r;if(!t||t.startsWith("#")||(0,a._)(t))return t;if(o)return n+t.replace(/^\//,"");if(t===n.replace(/\/$/,""))return n;const i=t.startsWith(n)?t:n+t.replace(/^\//,"");return s?e+i:i}(n,e,t,r)),[n,e]);return{withBaseUrl:t}}function i(e,n){void 0===n&&(n={});const{withBaseUrl:t}=s();return t(e,n)}},5976:(e,n,t)=>{"use strict";t.d(n,{c:()=>s});var r=t(1504);t(7624);const o=r.createContext({collectAnchor:()=>{},collectLink:()=>{}}),a=()=>(0,r.useContext)(o);function s(){return a()}},8264:(e,n,t)=>{"use strict";t.d(n,{c:()=>a});var r=t(1504),o=t(136);function a(){return(0,r.useContext)(o.e)}},3160:(e,n,t)=>{"use strict";t.d(n,{MP:()=>s,mm:()=>a});var r=t(8264),o=t(2488);function a(e,n){void 0===n&&(n={});const t=function(){const{globalData:e}=(0,r.c)();return e}()[e];if(!t&&n.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin.`);return t}function s(e,n,t){void 0===n&&(n=o.M),void 0===t&&(t={});const r=a(e),s=r?.[n];if(!s&&t.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin with id "${n}".`);return s}},3664:(e,n,t)=>{"use strict";t.d(n,{c:()=>a});var r=t(1504),o=t(240);function a(){return(0,r.useContext)(o.e)}},5288:(e,n,t)=>{"use strict";t.d(n,{c:()=>o});var r=t(1504);const o=t(8684).c.canUseDOM?r.useLayoutEffect:r.useEffect},8120:(e,n,t)=>{"use strict";t.d(n,{c:()=>o});const r=e=>"object"==typeof e&&!!e&&Object.keys(e).length>0;function o(e){const n={};return function e(t,o){Object.entries(t).forEach((t=>{let[a,s]=t;const i=o?`${o}.${a}`:a;r(s)?e(s,i):n[i]=s}))}(e),n}},5548:(e,n,t)=>{"use strict";t.d(n,{Y:()=>s,e:()=>a});var r=t(1504),o=t(7624);const a=r.createContext(null);function s(e){let{children:n,value:t}=e;const s=r.useContext(a),i=(0,r.useMemo)((()=>function(e){let{parent:n,value:t}=e;if(!n){if(!t)throw new Error("Unexpected: no Docusaurus route context found");if(!("plugin"in t))throw new Error("Unexpected: Docusaurus topmost route context has no `plugin` attribute");return t}const r={...n.data,...t?.data};return{plugin:n.plugin,data:r}}({parent:s,value:t})),[s,t]);return(0,o.jsx)(a.Provider,{value:i,children:n})}},5172:(e,n,t)=>{"use strict";t.d(n,{wB:()=>g,UF:()=>d,mU:()=>p,L0:()=>c,i8:()=>h,OK:()=>u,aA:()=>m,gN:()=>f});var r=t(5592),o=t(3160);const a=e=>e.versions.find((e=>e.isLast));function s(e,n){const t=a(e);return[...e.versions.filter((e=>e!==t)),t].find((e=>!!(0,r.ot)(n,{path:e.path,exact:!1,strict:!1})))}function i(e,n){const t=s(e,n),o=t?.docs.find((e=>!!(0,r.ot)(n,{path:e.path,exact:!0,strict:!1})));return{activeVersion:t,activeDoc:o,alternateDocVersions:o?function(n){const t={};return e.versions.forEach((e=>{e.docs.forEach((r=>{r.id===n&&(t[e.name]=r)}))})),t}(o.id):{}}}const l={},c=()=>(0,o.mm)("docusaurus-plugin-content-docs")??l,u=e=>(0,o.MP)("docusaurus-plugin-content-docs",e,{failfast:!0});function d(e){void 0===e&&(e={});const n=c(),{pathname:t}=(0,r.IT)();return function(e,n,t){void 0===t&&(t={});const o=Object.entries(e).sort(((e,n)=>n[1].path.localeCompare(e[1].path))).find((e=>{let[,t]=e;return!!(0,r.ot)(n,{path:t.path,exact:!1,strict:!1})})),a=o?{pluginId:o[0],pluginData:o[1]}:void 0;if(!a&&t.failfast)throw new Error(`Can't find active docs plugin for "${n}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(e).map((e=>e.path)).join(", ")}`);return a}(n,t,e)}function p(e){void 0===e&&(e={});const n=d(e),{pathname:t}=(0,r.IT)();if(!n)return;return{activePlugin:n,activeVersion:s(n.pluginData,t)}}function f(e){return u(e).versions}function m(e){const n=u(e);return a(n)}function g(e){const n=u(e),{pathname:t}=(0,r.IT)();return i(n,t)}function h(e){const n=u(e),{pathname:t}=(0,r.IT)();return function(e,n){const t=a(e);return{latestDocSuggestion:i(e,n).alternateDocVersions[t.name],latestVersionSuggestion:t}}(n,t)}},7483:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>r});const r={onRouteDidUpdate(e){let{location:n,previousLocation:t}=e;!t||n.pathname===t.pathname&&n.search===t.search&&n.hash===t.hash||setTimeout((()=>{window.gtag("set","page_path",n.pathname+n.search+n.hash),window.gtag("event","page_view")}))}}},1976:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>a});var r=t(2272),o=t.n(r);o().configure({showSpinner:!1});const a={onRouteUpdate(e){let{location:n,previousLocation:t}=e;if(t&&n.pathname!==t.pathname){const e=window.setTimeout((()=>{o().start()}),200);return()=>window.clearTimeout(e)}},onRouteDidUpdate(){o().done()}}},5396:(e,n,t)=>{"use strict";t.r(n);var r=t(5720),o=t(7768);!function(e){const{themeConfig:{prism:n}}=o.default,{additionalLanguages:r}=n;globalThis.Prism=e,r.forEach((e=>{"php"===e&&t(1808),t(4096)(`./prism-${e}`)})),delete globalThis.Prism}(r.sp)},6448:(e,n,t)=>{"use strict";t.d(n,{c:()=>u});t(1504);var r=t(5456),o=t(4357),a=t(1824),s=t(867),i=t(5976);const l={anchorWithStickyNavbar:"anchorWithStickyNavbar_LWe7",anchorWithHideOnScrollNavbar:"anchorWithHideOnScrollNavbar_WYt5"};var c=t(7624);function u(e){let{as:n,id:t,...u}=e;const d=(0,i.c)(),{navbar:{hideOnScroll:p}}=(0,a.y)();if("h1"===n||!t)return(0,c.jsx)(n,{...u,id:void 0});d.collectAnchor(t);const f=(0,o.G)({id:"theme.common.headingLinkTitle",message:"Direct link to {heading}",description:"Title for link to heading"},{heading:"string"==typeof u.children?u.children:t});return(0,c.jsxs)(n,{...u,className:(0,r.c)("anchor",p?l.anchorWithHideOnScrollNavbar:l.anchorWithStickyNavbar,u.className),id:t,children:[u.children,(0,c.jsx)(s.c,{className:"hash-link",to:`#${t}`,"aria-label":f,title:f,children:"\u200b"})]})}},3232:(e,n,t)=>{"use strict";t.d(n,{c:()=>a});t(1504);const r={iconExternalLink:"iconExternalLink_nPIU"};var o=t(7624);function a(e){let{width:n=13.5,height:t=13.5}=e;return(0,o.jsx)("svg",{width:n,height:t,"aria-hidden":"true",viewBox:"0 0 24 24",className:r.iconExternalLink,children:(0,o.jsx)("path",{fill:"currentColor",d:"M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"})})}},7468:(e,n,t)=>{"use strict";t.d(n,{c:()=>On});var r=t(1504),o=t(5456),a=t(5852),s=t(5008),i=t(5592),l=t(4357),c=t(7124),u=t(7624);const d="__docusaurus_skipToContent_fallback";function p(e){e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")}function f(){const e=(0,r.useRef)(null),{action:n}=(0,i.Uz)(),t=(0,r.useCallback)((e=>{e.preventDefault();const n=document.querySelector("main:first-of-type")??document.getElementById(d);n&&p(n)}),[]);return(0,c.c)((t=>{let{location:r}=t;e.current&&!r.hash&&"PUSH"===n&&p(e.current)})),{containerRef:e,onClick:t}}const m=(0,l.G)({id:"theme.common.skipToMainContent",description:"The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",message:"Skip to main content"});function g(e){const n=e.children??m,{containerRef:t,onClick:r}=f();return(0,u.jsx)("div",{ref:t,role:"region","aria-label":m,children:(0,u.jsx)("a",{...e,href:`#${d}`,onClick:r,children:n})})}var h=t(5864),y=t(204);const b={skipToContent:"skipToContent_fXgn"};function v(){return(0,u.jsx)(g,{className:b.skipToContent})}var S=t(1824),w=t(3868);function x(e){let{width:n=21,height:t=21,color:r="currentColor",strokeWidth:o=1.2,className:a,...s}=e;return(0,u.jsx)("svg",{viewBox:"0 0 15 15",width:n,height:t,...s,children:(0,u.jsx)("g",{stroke:r,strokeWidth:o,children:(0,u.jsx)("path",{d:"M.75.75l13.5 13.5M14.25.75L.75 14.25"})})})}const k={closeButton:"closeButton_CVFx"};function T(e){return(0,u.jsx)("button",{type:"button","aria-label":(0,l.G)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"}),...e,className:(0,o.c)("clean-btn close",k.closeButton,e.className),children:(0,u.jsx)(x,{width:14,height:14,strokeWidth:3.1})})}const E={content:"content_knG7"};function C(e){const{announcementBar:n}=(0,S.y)(),{content:t}=n;return(0,u.jsx)("div",{...e,className:(0,o.c)(E.content,e.className),dangerouslySetInnerHTML:{__html:t}})}const A={announcementBar:"announcementBar_mb4j",announcementBarPlaceholder:"announcementBarPlaceholder_vyr4",announcementBarClose:"announcementBarClose_gvF7",announcementBarContent:"announcementBarContent_xLdY"};function _(){const{announcementBar:e}=(0,S.y)(),{isActive:n,close:t}=(0,w.el)();if(!n)return null;const{backgroundColor:r,textColor:o,isCloseable:a}=e;return(0,u.jsxs)("div",{className:A.announcementBar,style:{backgroundColor:r,color:o},role:"banner",children:[a&&(0,u.jsx)("div",{className:A.announcementBarPlaceholder}),(0,u.jsx)(C,{className:A.announcementBarContent}),a&&(0,u.jsx)(T,{onClick:t,className:A.announcementBarClose})]})}var I=t(8200),P=t(3943);var R=t(1100),L=t(5168);const O=r.createContext(null);function N(e){let{children:n}=e;const t=function(){const e=(0,I.q)(),n=(0,L.MF)(),[t,o]=(0,r.useState)(!1),a=null!==n.component,s=(0,R.i0)(a);return(0,r.useEffect)((()=>{a&&!s&&o(!0)}),[a,s]),(0,r.useEffect)((()=>{a?e.shown||o(!0):o(!1)}),[e.shown,a]),(0,r.useMemo)((()=>[t,o]),[t])}();return(0,u.jsx)(O.Provider,{value:t,children:n})}function F(e){if(e.component){const n=e.component;return(0,u.jsx)(n,{...e.props})}}function j(){const e=(0,r.useContext)(O);if(!e)throw new R.AH("NavbarSecondaryMenuDisplayProvider");const[n,t]=e,o=(0,r.useCallback)((()=>t(!1)),[t]),a=(0,L.MF)();return(0,r.useMemo)((()=>({shown:n,hide:o,content:F(a)})),[o,a,n])}function M(e){let{header:n,primaryMenu:t,secondaryMenu:r}=e;const{shown:a}=j();return(0,u.jsxs)("div",{className:"navbar-sidebar",children:[n,(0,u.jsxs)("div",{className:(0,o.c)("navbar-sidebar__items",{"navbar-sidebar__items--show-secondary":a}),children:[(0,u.jsx)("div",{className:"navbar-sidebar__item menu",children:t}),(0,u.jsx)("div",{className:"navbar-sidebar__item menu",children:r})]})]})}var B=t(6528),D=t(3664);function $(e){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,u.jsx)("path",{fill:"currentColor",d:"M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"})})}function V(e){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,u.jsx)("path",{fill:"currentColor",d:"M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"})})}const U={toggle:"toggle_vylO",toggleButton:"toggleButton_gllP",darkToggleIcon:"darkToggleIcon_wfgR",lightToggleIcon:"lightToggleIcon_pyhR",toggleButtonDisabled:"toggleButtonDisabled_aARS"};function z(e){let{className:n,buttonClassName:t,value:r,onChange:a}=e;const s=(0,D.c)(),i=(0,l.G)({message:"Switch between dark and light mode (currently {mode})",id:"theme.colorToggle.ariaLabel",description:"The ARIA label for the navbar color mode toggle"},{mode:"dark"===r?(0,l.G)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"}):(0,l.G)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"})});return(0,u.jsx)("div",{className:(0,o.c)(U.toggle,n),children:(0,u.jsxs)("button",{className:(0,o.c)("clean-btn",U.toggleButton,!s&&U.toggleButtonDisabled,t),type:"button",onClick:()=>a("dark"===r?"light":"dark"),disabled:!s,title:i,"aria-label":i,"aria-live":"polite",children:[(0,u.jsx)($,{className:(0,o.c)(U.toggleIcon,U.lightToggleIcon)}),(0,u.jsx)(V,{className:(0,o.c)(U.toggleIcon,U.darkToggleIcon)})]})})}const H=r.memo(z),W={darkNavbarColorModeToggle:"darkNavbarColorModeToggle_X3D1"};function G(e){let{className:n}=e;const t=(0,S.y)().navbar.style,r=(0,S.y)().colorMode.disableSwitch,{colorMode:o,setColorMode:a}=(0,B.U)();return r?null:(0,u.jsx)(H,{className:n,buttonClassName:"dark"===t?W.darkNavbarColorModeToggle:void 0,value:o,onChange:a})}var q=t(8164);function K(){return(0,u.jsx)(q.c,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function Y(){const e=(0,I.q)();return(0,u.jsx)("button",{type:"button","aria-label":(0,l.G)({id:"theme.docs.sidebar.closeSidebarButtonAriaLabel",message:"Close navigation bar",description:"The ARIA label for close button of mobile sidebar"}),className:"clean-btn navbar-sidebar__close",onClick:()=>e.toggle(),children:(0,u.jsx)(x,{color:"var(--ifm-color-emphasis-600)"})})}function X(){return(0,u.jsxs)("div",{className:"navbar-sidebar__brand",children:[(0,u.jsx)(K,{}),(0,u.jsx)(G,{className:"margin-right--md"}),(0,u.jsx)(Y,{})]})}var Q=t(867),Z=t(964),J=t(8136),ee=t(1064),ne=t(3232);function te(e){let{activeBasePath:n,activeBaseRegex:t,to:r,href:o,label:a,html:s,isDropdownLink:i,prependBaseUrlToHref:l,...c}=e;const d=(0,Z.c)(r),p=(0,Z.c)(n),f=(0,Z.c)(o,{forcePrependBaseUrl:!0}),m=a&&o&&!(0,J.c)(o),g=s?{dangerouslySetInnerHTML:{__html:s}}:{children:(0,u.jsxs)(u.Fragment,{children:[a,m&&(0,u.jsx)(ne.c,{...i&&{width:12,height:12}})]})};return o?(0,u.jsx)(Q.c,{href:l?f:o,...c,...g}):(0,u.jsx)(Q.c,{to:d,isNavLink:!0,...(n||t)&&{isActive:(e,n)=>t?(0,ee._)(t,n.pathname):n.pathname.startsWith(p)},...c,...g})}function re(e){let{className:n,isDropdownItem:t=!1,...r}=e;const a=(0,u.jsx)(te,{className:(0,o.c)(t?"dropdown__link":"navbar__item navbar__link",n),isDropdownLink:t,...r});return t?(0,u.jsx)("li",{children:a}):a}function oe(e){let{className:n,isDropdownItem:t,...r}=e;return(0,u.jsx)("li",{className:"menu__list-item",children:(0,u.jsx)(te,{className:(0,o.c)("menu__link",n),...r})})}function ae(e){let{mobile:n=!1,position:t,...r}=e;const o=n?oe:re;return(0,u.jsx)(o,{...r,activeClassName:r.activeClassName??(n?"menu__link--active":"navbar__link--active")})}var se=t(8448),ie=t(3376),le=t(8264);const ce={dropdownNavbarItemMobile:"dropdownNavbarItemMobile_S0Fm"};function ue(e,n){return e.some((e=>function(e,n){return!!(0,ie.Sc)(e.to,n)||!!(0,ee._)(e.activeBaseRegex,n)||!(!e.activeBasePath||!n.startsWith(e.activeBasePath))}(e,n)))}function de(e){let{items:n,position:t,className:a,onClick:s,...i}=e;const l=(0,r.useRef)(null),[c,d]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{const e=e=>{l.current&&!l.current.contains(e.target)&&d(!1)};return document.addEventListener("mousedown",e),document.addEventListener("touchstart",e),document.addEventListener("focusin",e),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e),document.removeEventListener("focusin",e)}}),[l]),(0,u.jsxs)("div",{ref:l,className:(0,o.c)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===t,"dropdown--show":c}),children:[(0,u.jsx)(te,{"aria-haspopup":"true","aria-expanded":c,role:"button",href:i.to?void 0:"#",className:(0,o.c)("navbar__link",a),...i,onClick:i.to?void 0:e=>e.preventDefault(),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),d(!c))},children:i.children??i.label}),(0,u.jsx)("ul",{className:"dropdown__menu",children:n.map(((e,n)=>(0,r.createElement)(He,{isDropdownItem:!0,activeClassName:"dropdown__link--active",...e,key:n})))})]})}function pe(e){let{items:n,className:t,position:a,onClick:s,...l}=e;const c=function(){const{siteConfig:{baseUrl:e}}=(0,le.c)(),{pathname:n}=(0,i.IT)();return n.replace(e,"/")}(),d=ue(n,c),{collapsed:p,toggleCollapsed:f,setCollapsed:m}=(0,se.a)({initialState:()=>!d});return(0,r.useEffect)((()=>{d&&m(!d)}),[c,d,m]),(0,u.jsxs)("li",{className:(0,o.c)("menu__list-item",{"menu__list-item--collapsed":p}),children:[(0,u.jsx)(te,{role:"button",className:(0,o.c)(ce.dropdownNavbarItemMobile,"menu__link menu__link--sublist menu__link--sublist-caret",t),...l,onClick:e=>{e.preventDefault(),f()},children:l.children??l.label}),(0,u.jsx)(se.U,{lazy:!0,as:"ul",className:"menu__list",collapsed:p,children:n.map(((e,n)=>(0,r.createElement)(He,{mobile:!0,isDropdownItem:!0,onClick:s,activeClassName:"menu__link--active",...e,key:n})))})]})}function fe(e){let{mobile:n=!1,...t}=e;const r=n?pe:de;return(0,u.jsx)(r,{...t})}var me=t(1616);function ge(e){let{width:n=20,height:t=20,...r}=e;return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:n,height:t,"aria-hidden":!0,...r,children:(0,u.jsx)("path",{fill:"currentColor",d:"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"})})}const he="iconLanguage_nlXk";var ye=t(7104);function be(){return r.createElement("svg",{width:"15",height:"15",className:"DocSearch-Control-Key-Icon"},r.createElement("path",{d:"M4.505 4.496h2M5.505 5.496v5M8.216 4.496l.055 5.993M10 7.5c.333.333.5.667.5 1v2M12.326 4.5v5.996M8.384 4.496c1.674 0 2.116 0 2.116 1.5s-.442 1.5-2.116 1.5M3.205 9.303c-.09.448-.277 1.21-1.241 1.203C1 10.5.5 9.513.5 8V7c0-1.57.5-2.5 1.464-2.494.964.006 1.134.598 1.24 1.342M12.553 10.5h1.953",strokeWidth:"1.2",stroke:"currentColor",fill:"none",strokeLinecap:"square"}))}var ve=t(5052),Se=["translations"];function we(){return we=Object.assign||function(e){for(var n=1;ne.length)&&(n=e.length);for(var t=0,r=new Array(n);t=0||(o[t]=e[t]);return o}(e,n);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(o[t]=e[t])}return o}var Ee="Ctrl";var Ce=r.forwardRef((function(e,n){var t=e.translations,o=void 0===t?{}:t,a=Te(e,Se),s=o.buttonText,i=void 0===s?"Search":s,l=o.buttonAriaLabel,c=void 0===l?"Search":l,u=xe((0,r.useState)(null),2),d=u[0],p=u[1];return(0,r.useEffect)((function(){"undefined"!=typeof navigator&&(/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)?p("\u2318"):p(Ee))}),[]),r.createElement("button",we({type:"button",className:"DocSearch DocSearch-Button","aria-label":c},a,{ref:n}),r.createElement("span",{className:"DocSearch-Button-Container"},r.createElement(ve.I,null),r.createElement("span",{className:"DocSearch-Button-Placeholder"},i)),r.createElement("span",{className:"DocSearch-Button-Keys"},null!==d&&r.createElement(r.Fragment,null,r.createElement("kbd",{className:"DocSearch-Button-Key"},d===Ee?r.createElement(be,null):d),r.createElement("kbd",{className:"DocSearch-Button-Key"},"K"))))})),Ae=t(6952),_e=t(7092),Ie=t(9032),Pe=t(4456);const Re={button:{buttonText:(0,l.G)({id:"theme.SearchBar.label",message:"Search",description:"The ARIA label and placeholder for search button"}),buttonAriaLabel:(0,l.G)({id:"theme.SearchBar.label",message:"Search",description:"The ARIA label and placeholder for search button"})},modal:{searchBox:{resetButtonTitle:(0,l.G)({id:"theme.SearchModal.searchBox.resetButtonTitle",message:"Clear the query",description:"The label and ARIA label for search box reset button"}),resetButtonAriaLabel:(0,l.G)({id:"theme.SearchModal.searchBox.resetButtonTitle",message:"Clear the query",description:"The label and ARIA label for search box reset button"}),cancelButtonText:(0,l.G)({id:"theme.SearchModal.searchBox.cancelButtonText",message:"Cancel",description:"The label and ARIA label for search box cancel button"}),cancelButtonAriaLabel:(0,l.G)({id:"theme.SearchModal.searchBox.cancelButtonText",message:"Cancel",description:"The label and ARIA label for search box cancel button"})},startScreen:{recentSearchesTitle:(0,l.G)({id:"theme.SearchModal.startScreen.recentSearchesTitle",message:"Recent",description:"The title for recent searches"}),noRecentSearchesText:(0,l.G)({id:"theme.SearchModal.startScreen.noRecentSearchesText",message:"No recent searches",description:"The text when no recent searches"}),saveRecentSearchButtonTitle:(0,l.G)({id:"theme.SearchModal.startScreen.saveRecentSearchButtonTitle",message:"Save this search",description:"The label for save recent search button"}),removeRecentSearchButtonTitle:(0,l.G)({id:"theme.SearchModal.startScreen.removeRecentSearchButtonTitle",message:"Remove this search from history",description:"The label for remove recent search button"}),favoriteSearchesTitle:(0,l.G)({id:"theme.SearchModal.startScreen.favoriteSearchesTitle",message:"Favorite",description:"The title for favorite searches"}),removeFavoriteSearchButtonTitle:(0,l.G)({id:"theme.SearchModal.startScreen.removeFavoriteSearchButtonTitle",message:"Remove this search from favorites",description:"The label for remove favorite search button"})},errorScreen:{titleText:(0,l.G)({id:"theme.SearchModal.errorScreen.titleText",message:"Unable to fetch results",description:"The title for error screen of search modal"}),helpText:(0,l.G)({id:"theme.SearchModal.errorScreen.helpText",message:"You might want to check your network connection.",description:"The help text for error screen of search modal"})},footer:{selectText:(0,l.G)({id:"theme.SearchModal.footer.selectText",message:"to select",description:"The explanatory text of the action for the enter key"}),selectKeyAriaLabel:(0,l.G)({id:"theme.SearchModal.footer.selectKeyAriaLabel",message:"Enter key",description:"The ARIA label for the Enter key button that makes the selection"}),navigateText:(0,l.G)({id:"theme.SearchModal.footer.navigateText",message:"to navigate",description:"The explanatory text of the action for the Arrow up and Arrow down key"}),navigateUpKeyAriaLabel:(0,l.G)({id:"theme.SearchModal.footer.navigateUpKeyAriaLabel",message:"Arrow up",description:"The ARIA label for the Arrow up key button that makes the navigation"}),navigateDownKeyAriaLabel:(0,l.G)({id:"theme.SearchModal.footer.navigateDownKeyAriaLabel",message:"Arrow down",description:"The ARIA label for the Arrow down key button that makes the navigation"}),closeText:(0,l.G)({id:"theme.SearchModal.footer.closeText",message:"to close",description:"The explanatory text of the action for Escape key"}),closeKeyAriaLabel:(0,l.G)({id:"theme.SearchModal.footer.closeKeyAriaLabel",message:"Escape key",description:"The ARIA label for the Escape key button that close the modal"}),searchByText:(0,l.G)({id:"theme.SearchModal.footer.searchByText",message:"Search by",description:"The text explain that the search is making by Algolia"})},noResultsScreen:{noResultsText:(0,l.G)({id:"theme.SearchModal.noResultsScreen.noResultsText",message:"No results for",description:"The text explains that there are no results for the following search"}),suggestedQueryText:(0,l.G)({id:"theme.SearchModal.noResultsScreen.suggestedQueryText",message:"Try searching for",description:"The text for the suggested query when no results are found for the following search"}),reportMissingResultsText:(0,l.G)({id:"theme.SearchModal.noResultsScreen.reportMissingResultsText",message:"Believe this query should return results?",description:"The text for the question where the user thinks there are missing results"}),reportMissingResultsLinkText:(0,l.G)({id:"theme.SearchModal.noResultsScreen.reportMissingResultsLinkText",message:"Let us know.",description:"The text for the link to report missing results"})}},placeholder:(0,l.G)({id:"theme.SearchModal.placeholder",message:"Search docs",description:"The placeholder of the input of the DocSearch pop-up modal"})};let Le=null;function Oe(e){let{hit:n,children:t}=e;return(0,u.jsx)(Q.c,{to:n.url,children:t})}function Ne(e){let{state:n,onClose:t}=e;const r=(0,_e.Y)();return(0,u.jsx)(Q.c,{to:r(n.query),onClick:t,children:(0,u.jsx)(l.c,{id:"theme.SearchBar.seeAll",values:{count:n.context.nbHits},children:"See all {count} results"})})}function Fe(e){let{contextualSearch:n,externalUrlRegex:o,...a}=e;const{siteMetadata:s}=(0,le.c)(),l=(0,Ie.Q)(),c=function(){const{locale:e,tags:n}=(0,Pe.mY)();return[`language:${e}`,n.map((e=>`docusaurus_tag:${e}`))]}(),d=a.searchParameters?.facetFilters??[],p=n?function(e,n){const t=e=>"string"==typeof e?[e]:e;return[...t(e),...t(n)]}(c,d):d,f={...a.searchParameters,facetFilters:p},m=(0,i.Uz)(),g=(0,r.useRef)(null),h=(0,r.useRef)(null),[y,b]=(0,r.useState)(!1),[v,S]=(0,r.useState)(void 0),w=(0,r.useCallback)((()=>Le?Promise.resolve():Promise.all([t.e(2528).then(t.bind(t,148)),Promise.all([t.e(2176),t.e(1676)]).then(t.bind(t,1676)),Promise.all([t.e(2176),t.e(8879)]).then(t.bind(t,8879))]).then((e=>{let[{DocSearchModal:n}]=e;Le=n}))),[]),x=(0,r.useCallback)((()=>{w().then((()=>{g.current=document.createElement("div"),document.body.insertBefore(g.current,document.body.firstChild),b(!0)}))}),[w,b]),k=(0,r.useCallback)((()=>{b(!1),g.current?.remove()}),[b]),T=(0,r.useCallback)((e=>{w().then((()=>{b(!0),S(e.key)}))}),[w,b,S]),E=(0,r.useRef)({navigate(e){let{itemUrl:n}=e;(0,ee._)(o,n)?window.location.href=n:m.push(n)}}).current,C=(0,r.useRef)((e=>a.transformItems?a.transformItems(e):e.map((e=>({...e,url:l(e.url)}))))).current,A=(0,r.useMemo)((()=>e=>(0,u.jsx)(Ne,{...e,onClose:k})),[k]),_=(0,r.useCallback)((e=>(e.addAlgoliaAgent("docusaurus",s.docusaurusVersion),e)),[s.docusaurusVersion]);return function(e){var n=e.isOpen,t=e.onOpen,o=e.onClose,a=e.onInput,s=e.searchButtonRef;r.useEffect((function(){function e(e){var r;(27===e.keyCode&&n||"k"===(null===(r=e.key)||void 0===r?void 0:r.toLowerCase())&&(e.metaKey||e.ctrlKey)||!function(e){var n=e.target,t=n.tagName;return n.isContentEditable||"INPUT"===t||"SELECT"===t||"TEXTAREA"===t}(e)&&"/"===e.key&&!n)&&(e.preventDefault(),n?o():document.body.classList.contains("DocSearch--active")||document.body.classList.contains("DocSearch--active")||t()),s&&s.current===document.activeElement&&a&&/[a-zA-Z0-9]/.test(String.fromCharCode(e.keyCode))&&a(e)}return window.addEventListener("keydown",e),function(){window.removeEventListener("keydown",e)}}),[n,t,o,a,s])}({isOpen:y,onOpen:x,onClose:k,onInput:T,searchButtonRef:h}),(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(Ae.c,{children:(0,u.jsx)("link",{rel:"preconnect",href:`https://${a.appId}-dsn.algolia.net`,crossOrigin:"anonymous"})}),(0,u.jsx)(Ce,{onTouchStart:w,onFocus:w,onMouseOver:w,onClick:x,ref:h,translations:Re.button}),y&&Le&&g.current&&(0,ye.createPortal)((0,u.jsx)(Le,{onClose:k,initialScrollY:window.scrollY,initialQuery:v,navigator:E,transformItems:C,hitComponent:Oe,transformSearchClient:_,...a.searchPagePath&&{resultsFooterComponent:A},...a,searchParameters:f,placeholder:Re.placeholder,translations:Re.modal}),g.current)]})}function je(){const{siteConfig:e}=(0,le.c)();return(0,u.jsx)(Fe,{...e.themeConfig.algolia})}const Me={navbarSearchContainer:"navbarSearchContainer_Bca1"};function Be(e){let{children:n,className:t}=e;return(0,u.jsx)("div",{className:(0,o.c)(t,Me.navbarSearchContainer),children:n})}var De=t(5172),$e=t(5492);var Ve=t(4592);const Ue=e=>e.docs.find((n=>n.id===e.mainDocId));const ze={default:ae,localeDropdown:function(e){let{mobile:n,dropdownItemsBefore:t,dropdownItemsAfter:r,queryString:o="",...a}=e;const{i18n:{currentLocale:s,locales:c,localeConfigs:d}}=(0,le.c)(),p=(0,me.D)(),{search:f,hash:m}=(0,i.IT)(),g=[...t,...c.map((e=>{const t=`${`pathname://${p.createUrl({locale:e,fullyQualified:!1})}`}${f}${m}${o}`;return{label:d[e].label,lang:d[e].htmlLang,to:t,target:"_self",autoAddBaseUrl:!1,className:e===s?n?"menu__link--active":"dropdown__link--active":""}})),...r],h=n?(0,l.G)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):d[s].label;return(0,u.jsx)(fe,{...a,mobile:n,label:(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(ge,{className:he}),h]}),items:g})},search:function(e){let{mobile:n,className:t}=e;return n?null:(0,u.jsx)(Be,{className:t,children:(0,u.jsx)(je,{})})},dropdown:fe,html:function(e){let{value:n,className:t,mobile:r=!1,isDropdownItem:a=!1}=e;const s=a?"li":"div";return(0,u.jsx)(s,{className:(0,o.c)({navbar__item:!r&&!a,"menu__list-item":r},t),dangerouslySetInnerHTML:{__html:n}})},doc:function(e){let{docId:n,label:t,docsPluginId:r,...o}=e;const{activeDoc:a}=(0,De.wB)(r),s=(0,$e.Qf)(n,r),i=a?.path===s?.path;return null===s||s.unlisted&&!i?null:(0,u.jsx)(ae,{exact:!0,...o,isActive:()=>i||!!a?.sidebar&&a.sidebar===s.sidebar,label:t??s.id,to:s.path})},docSidebar:function(e){let{sidebarId:n,label:t,docsPluginId:r,...o}=e;const{activeDoc:a}=(0,De.wB)(r),s=(0,$e.Ab)(n,r).link;if(!s)throw new Error(`DocSidebarNavbarItem: Sidebar with ID "${n}" doesn't have anything to be linked to.`);return(0,u.jsx)(ae,{exact:!0,...o,isActive:()=>a?.sidebar===n,label:t??s.label,to:s.path})},docsVersion:function(e){let{label:n,to:t,docsPluginId:r,...o}=e;const a=(0,$e.b7)(r)[0],s=n??a.label,i=t??(e=>e.docs.find((n=>n.id===e.mainDocId)))(a).path;return(0,u.jsx)(ae,{...o,label:s,to:i})},docsVersionDropdown:function(e){let{mobile:n,docsPluginId:t,dropdownActiveClassDisabled:r,dropdownItemsBefore:o,dropdownItemsAfter:a,...s}=e;const{search:c,hash:d}=(0,i.IT)(),p=(0,De.wB)(t),f=(0,De.gN)(t),{savePreferredVersionName:m}=(0,Ve.iy)(t),g=[...o,...f.map((e=>{const n=p.alternateDocVersions[e.name]??Ue(e);return{label:e.label,to:`${n.path}${c}${d}`,isActive:()=>e===p.activeVersion,onClick:()=>m(e.name)}})),...a],h=(0,$e.b7)(t)[0],y=n&&g.length>1?(0,l.G)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):h.label,b=n&&g.length>1?void 0:Ue(h).path;return g.length<=1?(0,u.jsx)(ae,{...s,mobile:n,label:y,to:b,isActive:r?()=>!1:void 0}):(0,u.jsx)(fe,{...s,mobile:n,label:y,to:b,items:g,isActive:r?()=>!1:void 0})}};function He(e){let{type:n,...t}=e;const r=function(e,n){return e&&"default"!==e?e:"items"in n?"dropdown":"default"}(n,t),o=ze[r];if(!o)throw new Error(`No NavbarItem component found for type "${n}".`);return(0,u.jsx)(o,{...t})}function We(){const e=(0,I.q)(),n=(0,S.y)().navbar.items;return(0,u.jsx)("ul",{className:"menu__list",children:n.map(((n,t)=>(0,r.createElement)(He,{mobile:!0,...n,onClick:()=>e.toggle(),key:t})))})}function Ge(e){return(0,u.jsx)("button",{...e,type:"button",className:"clean-btn navbar-sidebar__back",children:(0,u.jsx)(l.c,{id:"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel",description:"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)",children:"\u2190 Back to main menu"})})}function qe(){const e=0===(0,S.y)().navbar.items.length,n=j();return(0,u.jsxs)(u.Fragment,{children:[!e&&(0,u.jsx)(Ge,{onClick:()=>n.hide()}),n.content]})}function Ke(){const e=(0,I.q)();var n;return void 0===(n=e.shown)&&(n=!0),(0,r.useEffect)((()=>(document.body.style.overflow=n?"hidden":"visible",()=>{document.body.style.overflow="visible"})),[n]),e.shouldRender?(0,u.jsx)(M,{header:(0,u.jsx)(X,{}),primaryMenu:(0,u.jsx)(We,{}),secondaryMenu:(0,u.jsx)(qe,{})}):null}const Ye={navbarHideable:"navbarHideable_m1mJ",navbarHidden:"navbarHidden_jGov"};function Xe(e){return(0,u.jsx)("div",{role:"presentation",...e,className:(0,o.c)("navbar-sidebar__backdrop",e.className)})}function Qe(e){let{children:n}=e;const{navbar:{hideOnScroll:t,style:a}}=(0,S.y)(),s=(0,I.q)(),{navbarRef:i,isNavbarVisible:d}=function(e){const[n,t]=(0,r.useState)(e),o=(0,r.useRef)(!1),a=(0,r.useRef)(0),s=(0,r.useCallback)((e=>{null!==e&&(a.current=e.getBoundingClientRect().height)}),[]);return(0,P.SM)(((n,r)=>{let{scrollY:s}=n;if(!e)return;if(s=i?t(!1):s+c{if(!e)return;const r=n.location.hash;if(r?document.getElementById(r.substring(1)):void 0)return o.current=!0,void t(!1);t(!0)})),{navbarRef:s,isNavbarVisible:n}}(t);return(0,u.jsxs)("nav",{ref:i,"aria-label":(0,l.G)({id:"theme.NavBar.navAriaLabel",message:"Main",description:"The ARIA label for the main navigation"}),className:(0,o.c)("navbar","navbar--fixed-top",t&&[Ye.navbarHideable,!d&&Ye.navbarHidden],{"navbar--dark":"dark"===a,"navbar--primary":"primary"===a,"navbar-sidebar--show":s.shown}),children:[n,(0,u.jsx)(Xe,{onClick:s.toggle}),(0,u.jsx)(Ke,{})]})}var Ze=t(5684);const Je={errorBoundaryError:"errorBoundaryError_a6uf",errorBoundaryFallback:"errorBoundaryFallback_VBag"};function en(e){return(0,u.jsx)("button",{type:"button",...e,children:(0,u.jsx)(l.c,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again rendering when the React error boundary captures an error",children:"Try again"})})}function nn(e){let{error:n}=e;const t=(0,Ze.getErrorCausalChain)(n).map((e=>e.message)).join("\n\nCause:\n");return(0,u.jsx)("p",{className:Je.errorBoundaryError,children:t})}class tn extends r.Component{componentDidCatch(e,n){throw this.props.onError(e,n)}render(){return this.props.children}}const rn="right";function on(e){let{width:n=30,height:t=30,className:r,...o}=e;return(0,u.jsx)("svg",{className:r,width:n,height:t,viewBox:"0 0 30 30","aria-hidden":"true",...o,children:(0,u.jsx)("path",{stroke:"currentColor",strokeLinecap:"round",strokeMiterlimit:"10",strokeWidth:"2",d:"M4 7h22M4 15h22M4 23h22"})})}function an(){const{toggle:e,shown:n}=(0,I.q)();return(0,u.jsx)("button",{onClick:e,"aria-label":(0,l.G)({id:"theme.docs.sidebar.toggleSidebarButtonAriaLabel",message:"Toggle navigation bar",description:"The ARIA label for hamburger menu button of mobile navigation"}),"aria-expanded":n,className:"navbar__toggle clean-btn",type:"button",children:(0,u.jsx)(on,{})})}const sn={colorModeToggle:"colorModeToggle_DEke"};function ln(e){let{items:n}=e;return(0,u.jsx)(u.Fragment,{children:n.map(((e,n)=>(0,u.jsx)(tn,{onError:n=>new Error(`A theme navbar item failed to render.\nPlease double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:\n${JSON.stringify(e,null,2)}`,{cause:n}),children:(0,u.jsx)(He,{...e})},n)))})}function cn(e){let{left:n,right:t}=e;return(0,u.jsxs)("div",{className:"navbar__inner",children:[(0,u.jsx)("div",{className:"navbar__items",children:n}),(0,u.jsx)("div",{className:"navbar__items navbar__items--right",children:t})]})}function un(){const e=(0,I.q)(),n=(0,S.y)().navbar.items,[t,r]=function(e){function n(e){return"left"===(e.position??rn)}return[e.filter(n),e.filter((e=>!n(e)))]}(n),o=n.find((e=>"search"===e.type));return(0,u.jsx)(cn,{left:(0,u.jsxs)(u.Fragment,{children:[!e.disabled&&(0,u.jsx)(an,{}),(0,u.jsx)(K,{}),(0,u.jsx)(ln,{items:t})]}),right:(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(ln,{items:r}),(0,u.jsx)(G,{className:sn.colorModeToggle}),!o&&(0,u.jsx)(Be,{children:(0,u.jsx)(je,{})})]})})}function dn(){return(0,u.jsx)(Qe,{children:(0,u.jsx)(un,{})})}function pn(e){let{item:n}=e;const{to:t,href:r,label:o,prependBaseUrlToHref:a,...s}=n,i=(0,Z.c)(t),l=(0,Z.c)(r,{forcePrependBaseUrl:!0});return(0,u.jsxs)(Q.c,{className:"footer__link-item",...r?{href:a?l:r}:{to:i},...s,children:[o,r&&!(0,J.c)(r)&&(0,u.jsx)(ne.c,{})]})}function fn(e){let{item:n}=e;return n.html?(0,u.jsx)("li",{className:"footer__item",dangerouslySetInnerHTML:{__html:n.html}}):(0,u.jsx)("li",{className:"footer__item",children:(0,u.jsx)(pn,{item:n})},n.href??n.to)}function mn(e){let{column:n}=e;return(0,u.jsxs)("div",{className:"col footer__col",children:[(0,u.jsx)("div",{className:"footer__title",children:n.title}),(0,u.jsx)("ul",{className:"footer__items clean-list",children:n.items.map(((e,n)=>(0,u.jsx)(fn,{item:e},n)))})]})}function gn(e){let{columns:n}=e;return(0,u.jsx)("div",{className:"row footer__links",children:n.map(((e,n)=>(0,u.jsx)(mn,{column:e},n)))})}function hn(){return(0,u.jsx)("span",{className:"footer__link-separator",children:"\xb7"})}function yn(e){let{item:n}=e;return n.html?(0,u.jsx)("span",{className:"footer__link-item",dangerouslySetInnerHTML:{__html:n.html}}):(0,u.jsx)(pn,{item:n})}function bn(e){let{links:n}=e;return(0,u.jsx)("div",{className:"footer__links text--center",children:(0,u.jsx)("div",{className:"footer__links",children:n.map(((e,t)=>(0,u.jsxs)(r.Fragment,{children:[(0,u.jsx)(yn,{item:e}),n.length!==t+1&&(0,u.jsx)(hn,{})]},t)))})})}function vn(e){let{links:n}=e;return function(e){return"title"in e[0]}(n)?(0,u.jsx)(gn,{columns:n}):(0,u.jsx)(bn,{links:n})}var Sn=t(1964);const wn={footerLogoLink:"footerLogoLink_BH7S"};function xn(e){let{logo:n}=e;const{withBaseUrl:t}=(0,Z.E)(),r={light:t(n.src),dark:t(n.srcDark??n.src)};return(0,u.jsx)(Sn.c,{className:(0,o.c)("footer__logo",n.className),alt:n.alt,sources:r,width:n.width,height:n.height,style:n.style})}function kn(e){let{logo:n}=e;return n.href?(0,u.jsx)(Q.c,{href:n.href,className:wn.footerLogoLink,target:n.target,children:(0,u.jsx)(xn,{logo:n})}):(0,u.jsx)(xn,{logo:n})}function Tn(e){let{copyright:n}=e;return(0,u.jsx)("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:n}})}function En(e){let{style:n,links:t,logo:r,copyright:a}=e;return(0,u.jsx)("footer",{className:(0,o.c)("footer",{"footer--dark":"dark"===n}),children:(0,u.jsxs)("div",{className:"container container-fluid",children:[t,(r||a)&&(0,u.jsxs)("div",{className:"footer__bottom text--center",children:[r&&(0,u.jsx)("div",{className:"margin-bottom--sm",children:r}),a]})]})})}function Cn(){const{footer:e}=(0,S.y)();if(!e)return null;const{copyright:n,links:t,logo:r,style:o}=e;return(0,u.jsx)(En,{style:o,links:t&&t.length>0&&(0,u.jsx)(vn,{links:t}),logo:r&&(0,u.jsx)(kn,{logo:r}),copyright:n&&(0,u.jsx)(Tn,{copyright:n})})}const An=r.memo(Cn),_n=(0,R.qY)([B.C,w.qu,P.S2,Ve.gc,s.w7,function(e){let{children:n}=e;return(0,u.jsx)(L.Ub,{children:(0,u.jsx)(I.y,{children:(0,u.jsx)(N,{children:n})})})}]);function In(e){let{children:n}=e;return(0,u.jsx)(_n,{children:n})}var Pn=t(6448);function Rn(e){let{error:n,tryAgain:t}=e;return(0,u.jsx)("main",{className:"container margin-vert--xl",children:(0,u.jsx)("div",{className:"row",children:(0,u.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,u.jsx)(Pn.c,{as:"h1",className:"hero__title",children:(0,u.jsx)(l.c,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed",children:"This page crashed."})}),(0,u.jsx)("div",{className:"margin-vert--lg",children:(0,u.jsx)(en,{onClick:t,className:"button button--primary shadow--lw"})}),(0,u.jsx)("hr",{}),(0,u.jsx)("div",{className:"margin-vert--md",children:(0,u.jsx)(nn,{error:n})})]})})})}const Ln={mainWrapper:"mainWrapper_z2l0"};function On(e){const{children:n,noFooter:t,wrapperClassName:r,title:i,description:l}=e;return(0,y.W)(),(0,u.jsxs)(In,{children:[(0,u.jsx)(s.U7,{title:i,description:l}),(0,u.jsx)(v,{}),(0,u.jsx)(_,{}),(0,u.jsx)(dn,{}),(0,u.jsx)("div",{id:d,className:(0,o.c)(h.W.wrapper.main,Ln.mainWrapper,r),children:(0,u.jsx)(a.c,{fallback:e=>(0,u.jsx)(Rn,{...e}),children:n})}),!t&&(0,u.jsx)(An,{})]})}},8164:(e,n,t)=>{"use strict";t.d(n,{c:()=>u});t(1504);var r=t(867),o=t(964),a=t(8264),s=t(1824),i=t(1964),l=t(7624);function c(e){let{logo:n,alt:t,imageClassName:r}=e;const a={light:(0,o.c)(n.src),dark:(0,o.c)(n.srcDark||n.src)},s=(0,l.jsx)(i.c,{className:n.className,sources:a,height:n.height,width:n.width,alt:t,style:n.style});return r?(0,l.jsx)("div",{className:r,children:s}):s}function u(e){const{siteConfig:{title:n}}=(0,a.c)(),{navbar:{title:t,logo:i}}=(0,s.y)(),{imageClassName:u,titleClassName:d,...p}=e,f=(0,o.c)(i?.href||"/"),m=t?"":n,g=i?.alt??m;return(0,l.jsxs)(r.c,{to:f,...p,...i?.target&&{target:i.target},children:[i&&(0,l.jsx)(c,{logo:i,alt:g,imageClassName:u}),null!=t&&(0,l.jsx)("b",{className:d,children:t})]})}},8712:(e,n,t)=>{"use strict";t.d(n,{c:()=>a});t(1504);var r=t(6952),o=t(7624);function a(e){let{locale:n,version:t,tag:a}=e;const s=n;return(0,o.jsxs)(r.c,{children:[n&&(0,o.jsx)("meta",{name:"docusaurus_locale",content:n}),t&&(0,o.jsx)("meta",{name:"docusaurus_version",content:t}),a&&(0,o.jsx)("meta",{name:"docusaurus_tag",content:a}),s&&(0,o.jsx)("meta",{name:"docsearch:language",content:s}),t&&(0,o.jsx)("meta",{name:"docsearch:version",content:t}),a&&(0,o.jsx)("meta",{name:"docsearch:docusaurus_tag",content:a})]})}},1964:(e,n,t)=>{"use strict";t.d(n,{c:()=>u});var r=t(1504),o=t(5456),a=t(3664),s=t(6528);const i={themedComponent:"themedComponent_mlkZ","themedComponent--light":"themedComponent--light_NVdE","themedComponent--dark":"themedComponent--dark_xIcU"};var l=t(7624);function c(e){let{className:n,children:t}=e;const c=(0,a.c)(),{colorMode:u}=(0,s.U)();return(0,l.jsx)(l.Fragment,{children:(c?"dark"===u?["dark"]:["light"]:["light","dark"]).map((e=>{const a=t({theme:e,className:(0,o.c)(n,i.themedComponent,i[`themedComponent--${e}`])});return(0,l.jsx)(r.Fragment,{children:a},e)}))})}function u(e){const{sources:n,className:t,alt:r,...o}=e;return(0,l.jsx)(c,{className:t,children:e=>{let{theme:t,className:a}=e;return(0,l.jsx)("img",{src:n[t],alt:r,className:a,...o})}})}},8448:(e,n,t)=>{"use strict";t.d(n,{U:()=>y,a:()=>c});var r=t(1504),o=t(8684),a=t(5288),s=t(3856),i=t(7624);const l="ease-in-out";function c(e){let{initialState:n}=e;const[t,o]=(0,r.useState)(n??!1),a=(0,r.useCallback)((()=>{o((e=>!e))}),[]);return{collapsed:t,setCollapsed:o,toggleCollapsed:a}}const u={display:"none",overflow:"hidden",height:"0px"},d={display:"block",overflow:"visible",height:"auto"};function p(e,n){const t=n?u:d;e.style.display=t.display,e.style.overflow=t.overflow,e.style.height=t.height}function f(e){let{collapsibleRef:n,collapsed:t,animation:o}=e;const a=(0,r.useRef)(!1);(0,r.useEffect)((()=>{const e=n.current;function r(){const n=e.scrollHeight,t=o?.duration??function(e){if((0,s.I)())return 1;const n=e/36;return Math.round(10*(4+15*n**.25+n/5))}(n);return{transition:`height ${t}ms ${o?.easing??l}`,height:`${n}px`}}function i(){const n=r();e.style.transition=n.transition,e.style.height=n.height}if(!a.current)return p(e,t),void(a.current=!0);return e.style.willChange="height",function(){const n=requestAnimationFrame((()=>{t?(i(),requestAnimationFrame((()=>{e.style.height=u.height,e.style.overflow=u.overflow}))):(e.style.display="block",requestAnimationFrame((()=>{i()})))}));return()=>cancelAnimationFrame(n)}()}),[n,t,o])}function m(e){if(!o.c.canUseDOM)return e?u:d}function g(e){let{as:n="div",collapsed:t,children:o,animation:a,onCollapseTransitionEnd:s,className:l,disableSSRStyle:c}=e;const u=(0,r.useRef)(null);return f({collapsibleRef:u,collapsed:t,animation:a}),(0,i.jsx)(n,{ref:u,style:c?void 0:m(t),onTransitionEnd:e=>{"height"===e.propertyName&&(p(u.current,t),s?.(t))},className:l,children:o})}function h(e){let{collapsed:n,...t}=e;const[o,s]=(0,r.useState)(!n),[l,c]=(0,r.useState)(n);return(0,a.c)((()=>{n||s(!0)}),[n]),(0,a.c)((()=>{o&&c(n)}),[o,n]),o?(0,i.jsx)(g,{...t,collapsed:l}):null}function y(e){let{lazy:n,...t}=e;const r=n?h:g;return(0,i.jsx)(r,{...t})}},3868:(e,n,t)=>{"use strict";t.d(n,{el:()=>g,qu:()=>m});var r=t(1504),o=t(3664),a=t(1148),s=t(1100),i=t(1824),l=t(7624);const c=(0,a.GS)("docusaurus.announcement.dismiss"),u=(0,a.GS)("docusaurus.announcement.id"),d=()=>"true"===c.get(),p=e=>c.set(String(e)),f=r.createContext(null);function m(e){let{children:n}=e;const t=function(){const{announcementBar:e}=(0,i.y)(),n=(0,o.c)(),[t,a]=(0,r.useState)((()=>!!n&&d()));(0,r.useEffect)((()=>{a(d())}),[]);const s=(0,r.useCallback)((()=>{p(!0),a(!0)}),[]);return(0,r.useEffect)((()=>{if(!e)return;const{id:n}=e;let t=u.get();"annoucement-bar"===t&&(t="announcement-bar");const r=n!==t;u.set(n),r&&p(!1),!r&&d()||a(!1)}),[e]),(0,r.useMemo)((()=>({isActive:!!e&&!t,close:s})),[e,t,s])}();return(0,l.jsx)(f.Provider,{value:t,children:n})}function g(){const e=(0,r.useContext)(f);if(!e)throw new s.AH("AnnouncementBarProvider");return e}},6528:(e,n,t)=>{"use strict";t.d(n,{C:()=>h,U:()=>y});var r=t(1504),o=t(8684),a=t(1100),s=t(1148),i=t(1824),l=t(7624);const c=r.createContext(void 0),u="theme",d=(0,s.GS)(u),p={light:"light",dark:"dark"},f=e=>e===p.dark?p.dark:p.light,m=e=>o.c.canUseDOM?f(document.documentElement.getAttribute("data-theme")):f(e),g=e=>{d.set(f(e))};function h(e){let{children:n}=e;const t=function(){const{colorMode:{defaultMode:e,disableSwitch:n,respectPrefersColorScheme:t}}=(0,i.y)(),[o,a]=(0,r.useState)(m(e));(0,r.useEffect)((()=>{n&&d.del()}),[n]);const s=(0,r.useCallback)((function(n,r){void 0===r&&(r={});const{persist:o=!0}=r;n?(a(n),o&&g(n)):(a(t?window.matchMedia("(prefers-color-scheme: dark)").matches?p.dark:p.light:e),d.del())}),[t,e]);(0,r.useEffect)((()=>{document.documentElement.setAttribute("data-theme",f(o))}),[o]),(0,r.useEffect)((()=>{if(n)return;const e=e=>{if(e.key!==u)return;const n=d.get();null!==n&&s(f(n))};return window.addEventListener("storage",e),()=>window.removeEventListener("storage",e)}),[n,s]);const l=(0,r.useRef)(!1);return(0,r.useEffect)((()=>{if(n&&!t)return;const e=window.matchMedia("(prefers-color-scheme: dark)"),r=()=>{window.matchMedia("print").matches||l.current?l.current=window.matchMedia("print").matches:s(null)};return e.addListener(r),()=>e.removeListener(r)}),[s,n,t]),(0,r.useMemo)((()=>({colorMode:o,setColorMode:s,get isDarkTheme(){return o===p.dark},setLightTheme(){s(p.light)},setDarkTheme(){s(p.dark)}})),[o,s])}();return(0,l.jsx)(c.Provider,{value:t,children:n})}function y(){const e=(0,r.useContext)(c);if(null==e)throw new a.AH("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},4592:(e,n,t)=>{"use strict";t.d(n,{eM:()=>S,gc:()=>y,iy:()=>v});var r=t(1504),o=t(5172),a=t(2488),s=t(1824),i=t(5492),l=t(1100),c=t(1148),u=t(7624);const d=e=>`docs-preferred-version-${e}`,p={save:(e,n,t)=>{(0,c.GS)(d(e),{persistence:n}).set(t)},read:(e,n)=>(0,c.GS)(d(e),{persistence:n}).get(),clear:(e,n)=>{(0,c.GS)(d(e),{persistence:n}).del()}},f=e=>Object.fromEntries(e.map((e=>[e,{preferredVersionName:null}])));const m=r.createContext(null);function g(){const e=(0,o.L0)(),n=(0,s.y)().docs.versionPersistence,t=(0,r.useMemo)((()=>Object.keys(e)),[e]),[a,i]=(0,r.useState)((()=>f(t)));(0,r.useEffect)((()=>{i(function(e){let{pluginIds:n,versionPersistence:t,allDocsData:r}=e;function o(e){const n=p.read(e,t);return r[e].versions.some((e=>e.name===n))?{preferredVersionName:n}:(p.clear(e,t),{preferredVersionName:null})}return Object.fromEntries(n.map((e=>[e,o(e)])))}({allDocsData:e,versionPersistence:n,pluginIds:t}))}),[e,n,t]);return[a,(0,r.useMemo)((()=>({savePreferredVersion:function(e,t){p.save(e,n,t),i((n=>({...n,[e]:{preferredVersionName:t}})))}})),[n])]}function h(e){let{children:n}=e;const t=g();return(0,u.jsx)(m.Provider,{value:t,children:n})}function y(e){let{children:n}=e;return i.c1?(0,u.jsx)(h,{children:n}):(0,u.jsx)(u.Fragment,{children:n})}function b(){const e=(0,r.useContext)(m);if(!e)throw new l.AH("DocsPreferredVersionContextProvider");return e}function v(e){void 0===e&&(e=a.M);const n=(0,o.OK)(e),[t,s]=b(),{preferredVersionName:i}=t[e];return{preferredVersion:n.versions.find((e=>e.name===i))??null,savePreferredVersionName:(0,r.useCallback)((n=>{s.savePreferredVersion(e,n)}),[s,e])}}function S(){const e=(0,o.L0)(),[n]=b();function t(t){const r=e[t],{preferredVersionName:o}=n[t];return r.versions.find((e=>e.name===o))??null}const r=Object.keys(e);return Object.fromEntries(r.map((e=>[e,t(e)])))}},6192:(e,n,t)=>{"use strict";t.d(n,{m:()=>c,y:()=>l});var r=t(1504),o=t(1100),a=t(7624);const s=Symbol("EmptyContext"),i=r.createContext(s);function l(e){let{children:n,name:t,items:o}=e;const s=(0,r.useMemo)((()=>t&&o?{name:t,items:o}:null),[t,o]);return(0,a.jsx)(i.Provider,{value:s,children:n})}function c(){const e=(0,r.useContext)(i);if(e===s)throw new o.AH("DocsSidebarProvider");return e}},9920:(e,n,t)=>{"use strict";t.d(n,{E:()=>l,Q:()=>i});var r=t(1504),o=t(1100),a=t(7624);const s=r.createContext(null);function i(e){let{children:n,version:t}=e;return(0,a.jsx)(s.Provider,{value:t,children:n})}function l(){const e=(0,r.useContext)(s);if(null===e)throw new o.AH("DocsVersionProvider");return e}},8200:(e,n,t)=>{"use strict";t.d(n,{q:()=>f,y:()=>p});var r=t(1504),o=t(5168),a=t(1432),s=t(632),i=t(1824),l=t(1100),c=t(7624);const u=r.createContext(void 0);function d(){const e=function(){const e=(0,o.MF)(),{items:n}=(0,i.y)().navbar;return 0===n.length&&!e.component}(),n=(0,a.U)(),t=!e&&"mobile"===n,[l,c]=(0,r.useState)(!1);(0,s.a4)((()=>{if(l)return c(!1),!1}));const u=(0,r.useCallback)((()=>{c((e=>!e))}),[]);return(0,r.useEffect)((()=>{"desktop"===n&&c(!1)}),[n]),(0,r.useMemo)((()=>({disabled:e,shouldRender:t,toggle:u,shown:l})),[e,t,u,l])}function p(e){let{children:n}=e;const t=d();return(0,c.jsx)(u.Provider,{value:t,children:n})}function f(){const e=r.useContext(u);if(void 0===e)throw new l.AH("NavbarMobileSidebarProvider");return e}},5168:(e,n,t)=>{"use strict";t.d(n,{MF:()=>l,Mx:()=>c,Ub:()=>i});var r=t(1504),o=t(1100),a=t(7624);const s=r.createContext(null);function i(e){let{children:n}=e;const t=(0,r.useState)({component:null,props:null});return(0,a.jsx)(s.Provider,{value:t,children:n})}function l(){const e=(0,r.useContext)(s);if(!e)throw new o.AH("NavbarSecondaryMenuContentProvider");return e[0]}function c(e){let{component:n,props:t}=e;const a=(0,r.useContext)(s);if(!a)throw new o.AH("NavbarSecondaryMenuContentProvider");const[,i]=a,l=(0,o.Mh)(t);return(0,r.useEffect)((()=>{i({component:n,props:l})}),[i,n,l]),(0,r.useEffect)((()=>()=>i({component:null,props:null})),[i]),null}},204:(e,n,t)=>{"use strict";t.d(n,{m:()=>o,W:()=>a});var r=t(1504);const o="navigation-with-keyboard";function a(){(0,r.useEffect)((()=>{function e(e){"keydown"===e.type&&"Tab"===e.key&&document.body.classList.add(o),"mousedown"===e.type&&document.body.classList.remove(o)}return document.addEventListener("keydown",e),document.addEventListener("mousedown",e),()=>{document.body.classList.remove(o),document.removeEventListener("keydown",e),document.removeEventListener("mousedown",e)}}),[])}},7092:(e,n,t)=>{"use strict";t.d(n,{Y:()=>l,a:()=>i});var r=t(1504),o=t(8264),a=t(632);const s="q";function i(){return(0,a.E9)(s)}function l(){const{siteConfig:{baseUrl:e,themeConfig:n}}=(0,o.c)(),{algolia:{searchPagePath:t}}=n;return(0,r.useCallback)((n=>`${e}${t}?${s}=${encodeURIComponent(n)}`),[e,t])}},1432:(e,n,t)=>{"use strict";t.d(n,{U:()=>i});var r=t(1504),o=t(8684);const a={desktop:"desktop",mobile:"mobile",ssr:"ssr"},s=996;function i(e){let{desktopBreakpoint:n=s}=void 0===e?{}:e;const[t,i]=(0,r.useState)((()=>"ssr"));return(0,r.useEffect)((()=>{function e(){i(function(e){if(!o.c.canUseDOM)throw new Error("getWindowSize() should only be called after React hydration");return window.innerWidth>e?a.desktop:a.mobile}(n))}return e(),window.addEventListener("resize",e),()=>{window.removeEventListener("resize",e)}}),[n]),t}},5864:(e,n,t)=>{"use strict";t.d(n,{W:()=>r});const r={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",unlistedBanner:"theme-unlisted-banner",admonitionType:e=>`theme-admonition-${e}`},layout:{},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:e=>`theme-doc-sidebar-item-category-level-${e}`,docSidebarItemLinkLevel:e=>`theme-doc-sidebar-item-link-level-${e}`},blog:{}}},3856:(e,n,t)=>{"use strict";function r(){return window.matchMedia("(prefers-reduced-motion: reduce)").matches}t.d(n,{I:()=>r})},5492:(e,n,t)=>{"use strict";t.d(n,{Ab:()=>x,Gw:()=>f,Md:()=>h,Qf:()=>k,Uj:()=>T,b7:()=>w,c1:()=>p,js:()=>S,mg:()=>b});var r=t(1504),o=t(5592),a=t(5464),s=t(5172),i=t(4592),l=t(9920),c=t(6192),u=t(7128),d=t(3376);const p=!!s.L0;function f(e){return"link"!==e.type||e.unlisted?"category"===e.type?function(e){if(e.href&&!e.linkUnlisted)return e.href;for(const n of e.items){const e=f(n);if(e)return e}}(e):void 0:e.href}const m=(e,n)=>void 0!==e&&(0,d.Sc)(e,n),g=(e,n)=>e.some((e=>h(e,n)));function h(e,n){return"link"===e.type?m(e.href,n):"category"===e.type&&(m(e.href,n)||g(e.items,n))}function y(e,n){switch(e.type){case"category":return h(e,n)||e.items.some((e=>y(e,n)));case"link":return!e.unlisted||h(e,n);default:return!0}}function b(e,n){return(0,r.useMemo)((()=>e.filter((e=>y(e,n)))),[e,n])}function v(e){let{sidebarItems:n,pathname:t,onlyCategories:r=!1}=e;const o=[];return function e(n){for(const a of n)if("category"===a.type&&((0,d.Sc)(a.href,t)||e(a.items))||"link"===a.type&&(0,d.Sc)(a.href,t)){return r&&"category"!==a.type||o.unshift(a),!0}return!1}(n),o}function S(){const e=(0,c.m)(),{pathname:n}=(0,o.IT)(),t=(0,s.UF)()?.pluginData.breadcrumbs;return!1!==t&&e?v({sidebarItems:e.items,pathname:n}):null}function w(e){const{activeVersion:n}=(0,s.wB)(e),{preferredVersion:t}=(0,i.iy)(e),o=(0,s.aA)(e);return(0,r.useMemo)((()=>(0,u.U)([n,t,o].filter(Boolean))),[n,t,o])}function x(e,n){const t=w(n);return(0,r.useMemo)((()=>{const n=t.flatMap((e=>e.sidebars?Object.entries(e.sidebars):[])),r=n.find((n=>n[0]===e));if(!r)throw new Error(`Can't find any sidebar with id "${e}" in version${t.length>1?"s":""} ${t.map((e=>e.name)).join(", ")}".\nAvailable sidebar ids are:\n- ${n.map((e=>e[0])).join("\n- ")}`);return r[1]}),[e,t])}function k(e,n){const t=w(n);return(0,r.useMemo)((()=>{const n=t.flatMap((e=>e.docs)),r=n.find((n=>n.id===e));if(!r){if(t.flatMap((e=>e.draftIds)).includes(e))return null;throw new Error(`Couldn't find any doc with id "${e}" in version${t.length>1?"s":""} "${t.map((e=>e.name)).join(", ")}".\nAvailable doc ids are:\n- ${(0,u.U)(n.map((e=>e.id))).join("\n- ")}`)}return r}),[e,t])}function T(e){let{route:n}=e;const t=(0,o.IT)(),r=(0,l.E)(),s=n.routes,i=s.find((e=>(0,o.ot)(t.pathname,e)));if(!i)return null;const c=i.sidebar,u=c?r.docsSidebars[c]:void 0;return{docElement:(0,a.k)(s),sidebarName:c,sidebarItems:u}}},8648:(e,n,t)=>{"use strict";t.d(n,{g:()=>o});var r=t(8264);function o(e){const{siteConfig:n}=(0,r.c)(),{title:t,titleDelimiter:o}=n;return e?.trim().length?`${e.trim()} ${o} ${t}`:t}},632:(e,n,t)=>{"use strict";t.d(n,{E9:()=>l,_M:()=>i,a4:()=>s});var r=t(1504),o=t(5592),a=t(1100);function s(e){!function(e){const n=(0,o.Uz)(),t=(0,a.yA)(e);(0,r.useEffect)((()=>n.block(((e,n)=>t(e,n)))),[n,t])}(((n,t)=>{if("POP"===t)return e(n,t)}))}function i(e){return function(e){const n=(0,o.Uz)();return(0,r.useSyncExternalStore)(n.listen,(()=>e(n)),(()=>e(n)))}((n=>null===e?null:new URLSearchParams(n.location.search).get(e)))}function l(e){const n=i(e)??"",t=function(){const e=(0,o.Uz)();return(0,r.useCallback)(((n,t,r)=>{const o=new URLSearchParams(e.location.search);t?o.set(n,t):o.delete(n),(r?.push?e.push:e.replace)({search:o.toString()})}),[e])}();return[n,(0,r.useCallback)(((n,r)=>{t(e,n,r)}),[t,e])]}},7128:(e,n,t)=>{"use strict";function r(e,n){return void 0===n&&(n=(e,n)=>e===n),e.filter(((t,r)=>e.findIndex((e=>n(e,t)))!==r))}function o(e){return Array.from(new Set(e))}t.d(n,{U:()=>o,w:()=>r})},5008:(e,n,t)=>{"use strict";t.d(n,{cr:()=>f,U7:()=>d,w7:()=>m});var r=t(1504),o=t(5456),a=t(6952),s=t(5548);function i(){const e=r.useContext(s.e);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}var l=t(964),c=t(8648),u=t(7624);function d(e){let{title:n,description:t,keywords:r,image:o,children:s}=e;const i=(0,c.g)(n),{withBaseUrl:d}=(0,l.E)(),p=o?d(o,{absolute:!0}):void 0;return(0,u.jsxs)(a.c,{children:[n&&(0,u.jsx)("title",{children:i}),n&&(0,u.jsx)("meta",{property:"og:title",content:i}),t&&(0,u.jsx)("meta",{name:"description",content:t}),t&&(0,u.jsx)("meta",{property:"og:description",content:t}),r&&(0,u.jsx)("meta",{name:"keywords",content:Array.isArray(r)?r.join(","):r}),p&&(0,u.jsx)("meta",{property:"og:image",content:p}),p&&(0,u.jsx)("meta",{name:"twitter:image",content:p}),s]})}const p=r.createContext(void 0);function f(e){let{className:n,children:t}=e;const s=r.useContext(p),i=(0,o.c)(s,n);return(0,u.jsxs)(p.Provider,{value:i,children:[(0,u.jsx)(a.c,{children:(0,u.jsx)("html",{className:i})}),t]})}function m(e){let{children:n}=e;const t=i(),r=`plugin-${t.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,"")}`;const a=`plugin-id-${t.plugin.id}`;return(0,u.jsx)(f,{className:(0,o.c)(r,a),children:n})}},1100:(e,n,t)=>{"use strict";t.d(n,{AH:()=>l,Mh:()=>c,i0:()=>i,qY:()=>u,yA:()=>s});var r=t(1504),o=t(5288),a=t(7624);function s(e){const n=(0,r.useRef)(e);return(0,o.c)((()=>{n.current=e}),[e]),(0,r.useCallback)((function(){return n.current(...arguments)}),[])}function i(e){const n=(0,r.useRef)();return(0,o.c)((()=>{n.current=e})),n.current}class l extends Error{constructor(e,n){super(),this.name="ReactContextError",this.message=`Hook ${this.stack?.split("\n")[1]?.match(/at (?:\w+\.)?(?\w+)/)?.groups.name??""} is called outside the <${e}>. ${n??""}`}}function c(e){const n=Object.entries(e);return n.sort(((e,n)=>e[0].localeCompare(n[0]))),(0,r.useMemo)((()=>e),n.flat())}function u(e){return n=>{let{children:t}=n;return(0,a.jsx)(a.Fragment,{children:e.reduceRight(((e,n)=>(0,a.jsx)(n,{children:e})),t)})}}},1064:(e,n,t)=>{"use strict";function r(e,n){return void 0!==e&&void 0!==n&&new RegExp(e,"gi").test(n)}t.d(n,{_:()=>r})},3376:(e,n,t)=>{"use strict";t.d(n,{Sc:()=>s,Y5:()=>i});var r=t(1504),o=t(628),a=t(8264);function s(e,n){const t=e=>(!e||e.endsWith("/")?e:`${e}/`)?.toLowerCase();return t(e)===t(n)}function i(){const{baseUrl:e}=(0,a.c)().siteConfig;return(0,r.useMemo)((()=>function(e){let{baseUrl:n,routes:t}=e;function r(e){return e.path===n&&!0===e.exact}function o(e){return e.path===n&&!e.exact}return function e(n){if(0===n.length)return;return n.find(r)||e(n.filter(o).flatMap((e=>e.routes??[])))}(t)}({routes:o.c,baseUrl:e})),[e])}},3943:(e,n,t)=>{"use strict";t.d(n,{MV:()=>m,S2:()=>u,SM:()=>f,yI:()=>g});var r=t(1504),o=t(8684),a=t(3664),s=t(5288),i=t(1100),l=t(7624);const c=r.createContext(void 0);function u(e){let{children:n}=e;const t=function(){const e=(0,r.useRef)(!0);return(0,r.useMemo)((()=>({scrollEventsEnabledRef:e,enableScrollEvents:()=>{e.current=!0},disableScrollEvents:()=>{e.current=!1}})),[])}();return(0,l.jsx)(c.Provider,{value:t,children:n})}function d(){const e=(0,r.useContext)(c);if(null==e)throw new i.AH("ScrollControllerProvider");return e}const p=()=>o.c.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null;function f(e,n){void 0===n&&(n=[]);const{scrollEventsEnabledRef:t}=d(),o=(0,r.useRef)(p()),a=(0,i.yA)(e);(0,r.useEffect)((()=>{const e=()=>{if(!t.current)return;const e=p();a(e,o.current),o.current=e},n={passive:!0};return e(),window.addEventListener("scroll",e,n),()=>window.removeEventListener("scroll",e,n)}),[a,t,...n])}function m(){const e=d(),n=function(){const e=(0,r.useRef)({elem:null,top:0}),n=(0,r.useCallback)((n=>{e.current={elem:n,top:n.getBoundingClientRect().top}}),[]),t=(0,r.useCallback)((()=>{const{current:{elem:n,top:t}}=e;if(!n)return{restored:!1};const r=n.getBoundingClientRect().top-t;return r&&window.scrollBy({left:0,top:r}),e.current={elem:null,top:0},{restored:0!==r}}),[]);return(0,r.useMemo)((()=>({save:n,restore:t})),[t,n])}(),t=(0,r.useRef)(void 0),o=(0,r.useCallback)((r=>{n.save(r),e.disableScrollEvents(),t.current=()=>{const{restored:r}=n.restore();if(t.current=void 0,r){const n=()=>{e.enableScrollEvents(),window.removeEventListener("scroll",n)};window.addEventListener("scroll",n)}else e.enableScrollEvents()}}),[e,n]);return(0,s.c)((()=>{queueMicrotask((()=>t.current?.()))})),{blockElementScrollPositionUntilNextRender:o}}function g(){const e=(0,r.useRef)(null),n=(0,a.c)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:t=>{e.current=n?function(e){return window.scrollTo({top:e,behavior:"smooth"}),()=>{}}(t):function(e){let n=null;const t=document.documentElement.scrollTop>e;return function r(){const o=document.documentElement.scrollTop;(t&&o>e||!t&&on&&cancelAnimationFrame(n)}(t)},cancelScroll:()=>e.current?.()}}},4456:(e,n,t)=>{"use strict";t.d(n,{SE:()=>i,e6:()=>s,mY:()=>l});var r=t(5172),o=t(8264),a=t(4592);const s="default";function i(e,n){return`docs-${e}-${n}`}function l(){const{i18n:e}=(0,o.c)(),n=(0,r.L0)(),t=(0,r.mU)(),l=(0,a.eM)();const c=[s,...Object.keys(n).map((function(e){const r=t?.activePlugin.pluginId===e?t.activeVersion:void 0,o=l[e],a=n[e].versions.find((e=>e.isLast));return i(e,(r??o??a).name)}))];return{locale:e.currentLocale,tags:c}}},1148:(e,n,t)=>{"use strict";t.d(n,{GS:()=>c,IN:()=>u});var r=t(1504);const o="localStorage";function a(e){let{key:n,oldValue:t,newValue:r,storage:o}=e;if(t===r)return;const a=document.createEvent("StorageEvent");a.initStorageEvent("storage",!1,!1,n,t,r,window.location.href,o),window.dispatchEvent(a)}function s(e){if(void 0===e&&(e=o),"undefined"==typeof window)throw new Error("Browser storage is not available on Node.js/Docusaurus SSR process.");if("none"===e)return null;try{return window[e]}catch(t){return n=t,i||(console.warn("Docusaurus browser storage is not available.\nPossible reasons: running Docusaurus in an iframe, in an incognito browser session, or using too strict browser privacy settings.",n),i=!0),null}var n}let i=!1;const l={get:()=>null,set:()=>{},del:()=>{},listen:()=>()=>{}};function c(e,n){if("undefined"==typeof window)return function(e){function n(){throw new Error(`Illegal storage API usage for storage key "${e}".\nDocusaurus storage APIs are not supposed to be called on the server-rendering process.\nPlease only call storage APIs in effects and event handlers.`)}return{get:n,set:n,del:n,listen:n}}(e);const t=s(n?.persistence);return null===t?l:{get:()=>{try{return t.getItem(e)}catch(n){return console.error(`Docusaurus storage error, can't get key=${e}`,n),null}},set:n=>{try{const r=t.getItem(e);t.setItem(e,n),a({key:e,oldValue:r,newValue:n,storage:t})}catch(r){console.error(`Docusaurus storage error, can't set ${e}=${n}`,r)}},del:()=>{try{const n=t.getItem(e);t.removeItem(e),a({key:e,oldValue:n,newValue:null,storage:t})}catch(n){console.error(`Docusaurus storage error, can't delete key=${e}`,n)}},listen:n=>{try{const r=r=>{r.storageArea===t&&r.key===e&&n(r)};return window.addEventListener("storage",r),()=>window.removeEventListener("storage",r)}catch(r){return console.error(`Docusaurus storage error, can't listen for changes of key=${e}`,r),()=>{}}}}}function u(e,n){const t=(0,r.useRef)((()=>null===e?l:c(e,n))).current(),o=(0,r.useCallback)((e=>"undefined"==typeof window?()=>{}:t.listen(e)),[t]);return[(0,r.useSyncExternalStore)(o,(()=>"undefined"==typeof window?null:t.get()),(()=>null)),t]}},1616:(e,n,t)=>{"use strict";t.d(n,{D:()=>s});var r=t(8264),o=t(5592),a=t(5684);function s(){const{siteConfig:{baseUrl:e,url:n,trailingSlash:t},i18n:{defaultLocale:s,currentLocale:i}}=(0,r.c)(),{pathname:l}=(0,o.IT)(),c=(0,a.applyTrailingSlash)(l,{trailingSlash:t,baseUrl:e}),u=i===s?e:e.replace(`/${i}/`,"/"),d=c.replace(e,"");return{createUrl:function(e){let{locale:t,fullyQualified:r}=e;return`${r?n:""}${function(e){return e===s?`${u}`:`${u}${e}/`}(t)}${d}`}}}},7124:(e,n,t)=>{"use strict";t.d(n,{c:()=>s});var r=t(1504),o=t(5592),a=t(1100);function s(e){const n=(0,o.IT)(),t=(0,a.i0)(n),s=(0,a.yA)(e);(0,r.useEffect)((()=>{t&&n!==t&&s({location:n,previousLocation:t})}),[s,n,t])}},1824:(e,n,t)=>{"use strict";t.d(n,{y:()=>o});var r=t(8264);function o(){return(0,r.c)().siteConfig.themeConfig}},8589:(e,n,t)=>{"use strict";t.d(n,{E:()=>o});var r=t(8264);function o(){const{siteConfig:{themeConfig:e}}=(0,r.c)();return e}},9032:(e,n,t)=>{"use strict";t.d(n,{Q:()=>i});var r=t(1504),o=t(1064),a=t(964),s=t(8589);function i(){const{withBaseUrl:e}=(0,a.E)(),{algolia:{externalUrlRegex:n,replaceSearchResultPathname:t}}=(0,s.E)();return(0,r.useCallback)((r=>{const a=new URL(r);if((0,o._)(n,a.href))return r;const s=`${a.pathname+a.hash}`;return e(function(e,n){return n?e.replaceAll(new RegExp(n.from,"g"),n.to):e}(s,t))}),[e,n,t])}},1600:(e,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.default=function(e,n){const{trailingSlash:t,baseUrl:r}=n;if(e.startsWith("#"))return e;if(void 0===t)return e;const[o]=e.split(/[#?]/),a="/"===o||o===r?o:(s=o,t?function(e){return e.endsWith("/")?e:`${e}/`}(s):function(e){return e.endsWith("/")?e.slice(0,-1):e}(s));var s;return e.replace(o,a)}},4292:(e,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.getErrorCausalChain=void 0,n.getErrorCausalChain=function e(n){return n.cause?[n,...e(n.cause)]:[n]}},5684:function(e,n,t){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(n,"__esModule",{value:!0}),n.getErrorCausalChain=n.applyTrailingSlash=n.blogPostContainerID=void 0,n.blogPostContainerID="__blog-post-container";var o=t(1600);Object.defineProperty(n,"applyTrailingSlash",{enumerable:!0,get:function(){return r(o).default}});var a=t(4292);Object.defineProperty(n,"getErrorCausalChain",{enumerable:!0,get:function(){return a.getErrorCausalChain}})},8064:(e,n,t)=>{"use strict";t.d(n,{iU:()=>S,Yf:()=>C,gh:()=>f,Wi:()=>_,Ep:()=>p});var r=t(6404);function o(e){return"/"===e.charAt(0)}function a(e,n){for(var t=n,r=t+1,o=e.length;r=0;p--){var f=s[p];"."===f?a(s,p):".."===f?(a(s,p),d++):d&&(a(s,p),d--)}if(!c)for(;d--;d)s.unshift("..");!c||""===s[0]||s[0]&&o(s[0])||s.unshift("");var m=s.join("/");return t&&"/"!==m.substr(-1)&&(m+="/"),m};var i=t(6136);function l(e){return"/"===e.charAt(0)?e:"/"+e}function c(e){return"/"===e.charAt(0)?e.substr(1):e}function u(e,n){return function(e,n){return 0===e.toLowerCase().indexOf(n.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(n.length))}(e,n)?e.substr(n.length):e}function d(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function p(e){var n=e.pathname,t=e.search,r=e.hash,o=n||"/";return t&&"?"!==t&&(o+="?"===t.charAt(0)?t:"?"+t),r&&"#"!==r&&(o+="#"===r.charAt(0)?r:"#"+r),o}function f(e,n,t,o){var a;"string"==typeof e?(a=function(e){var n=e||"/",t="",r="",o=n.indexOf("#");-1!==o&&(r=n.substr(o),n=n.substr(0,o));var a=n.indexOf("?");return-1!==a&&(t=n.substr(a),n=n.substr(0,a)),{pathname:n,search:"?"===t?"":t,hash:"#"===r?"":r}}(e),a.state=n):(void 0===(a=(0,r.c)({},e)).pathname&&(a.pathname=""),a.search?"?"!==a.search.charAt(0)&&(a.search="?"+a.search):a.search="",a.hash?"#"!==a.hash.charAt(0)&&(a.hash="#"+a.hash):a.hash="",void 0!==n&&void 0===a.state&&(a.state=n));try{a.pathname=decodeURI(a.pathname)}catch(i){throw i instanceof URIError?new URIError('Pathname "'+a.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):i}return t&&(a.key=t),o?a.pathname?"/"!==a.pathname.charAt(0)&&(a.pathname=s(a.pathname,o.pathname)):a.pathname=o.pathname:a.pathname||(a.pathname="/"),a}function m(){var e=null;var n=[];return{setPrompt:function(n){return e=n,function(){e===n&&(e=null)}},confirmTransitionTo:function(n,t,r,o){if(null!=e){var a="function"==typeof e?e(n,t):e;"string"==typeof a?"function"==typeof r?r(a,o):o(!0):o(!1!==a)}else o(!0)},appendListener:function(e){var t=!0;function r(){t&&e.apply(void 0,arguments)}return n.push(r),function(){t=!1,n=n.filter((function(e){return e!==r}))}},notifyListeners:function(){for(var e=arguments.length,t=new Array(e),r=0;rn?t.splice(n,t.length-n,o):t.push(o),d({action:r,location:o,index:n,entries:t})}}))},replace:function(e,n){var r="REPLACE",o=f(e,n,g(),S.location);u.confirmTransitionTo(o,r,t,(function(e){e&&(S.entries[S.index]=o,d({action:r,location:o}))}))},go:v,goBack:function(){v(-1)},goForward:function(){v(1)},canGo:function(e){var n=S.index+e;return n>=0&&n{"use strict";var r=t(2168),o={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},a={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},s={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},i={};function l(e){return r.isMemo(e)?s:i[e.$$typeof]||o}i[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},i[r.Memo]=s;var c=Object.defineProperty,u=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,p=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,m=Object.prototype;e.exports=function e(n,t,r){if("string"!=typeof t){if(m){var o=f(t);o&&o!==m&&e(n,o,r)}var s=u(t);d&&(s=s.concat(d(t)));for(var i=l(n),g=l(t),h=0;h{"use strict";e.exports=function(e,n,t,r,o,a,s,i){if(!e){var l;if(void 0===n)l=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[t,r,o,a,s,i],u=0;(l=new Error(n.replace(/%s/g,(function(){return c[u++]})))).name="Invariant Violation"}throw l.framesToPop=1,l}}},9600:e=>{e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},1462:(e,n,t)=>{"use strict";t.r(n)},9115:(e,n,t)=>{"use strict";t.r(n)},2272:function(e,n,t){var r,o;r=function(){var e,n,t={version:"0.2.0"},r=t.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:''};function o(e,n,t){return et?t:e}function a(e){return 100*(-1+e)}function s(e,n,t){var o;return(o="translate3d"===r.positionUsing?{transform:"translate3d("+a(e)+"%,0,0)"}:"translate"===r.positionUsing?{transform:"translate("+a(e)+"%,0)"}:{"margin-left":a(e)+"%"}).transition="all "+n+"ms "+t,o}t.configure=function(e){var n,t;for(n in e)void 0!==(t=e[n])&&e.hasOwnProperty(n)&&(r[n]=t);return this},t.status=null,t.set=function(e){var n=t.isStarted();e=o(e,r.minimum,1),t.status=1===e?null:e;var a=t.render(!n),c=a.querySelector(r.barSelector),u=r.speed,d=r.easing;return a.offsetWidth,i((function(n){""===r.positionUsing&&(r.positionUsing=t.getPositioningCSS()),l(c,s(e,u,d)),1===e?(l(a,{transition:"none",opacity:1}),a.offsetWidth,setTimeout((function(){l(a,{transition:"all "+u+"ms linear",opacity:0}),setTimeout((function(){t.remove(),n()}),u)}),u)):setTimeout(n,u)})),this},t.isStarted=function(){return"number"==typeof t.status},t.start=function(){t.status||t.set(0);var e=function(){setTimeout((function(){t.status&&(t.trickle(),e())}),r.trickleSpeed)};return r.trickle&&e(),this},t.done=function(e){return e||t.status?t.inc(.3+.5*Math.random()).set(1):this},t.inc=function(e){var n=t.status;return n?("number"!=typeof e&&(e=(1-n)*o(Math.random()*n,.1,.95)),n=o(n+e,0,.994),t.set(n)):t.start()},t.trickle=function(){return t.inc(Math.random()*r.trickleRate)},e=0,n=0,t.promise=function(r){return r&&"resolved"!==r.state()?(0===n&&t.start(),e++,n++,r.always((function(){0==--n?(e=0,t.done()):t.set((e-n)/e)})),this):this},t.render=function(e){if(t.isRendered())return document.getElementById("nprogress");u(document.documentElement,"nprogress-busy");var n=document.createElement("div");n.id="nprogress",n.innerHTML=r.template;var o,s=n.querySelector(r.barSelector),i=e?"-100":a(t.status||0),c=document.querySelector(r.parent);return l(s,{transition:"all 0 linear",transform:"translate3d("+i+"%,0,0)"}),r.showSpinner||(o=n.querySelector(r.spinnerSelector))&&f(o),c!=document.body&&u(c,"nprogress-custom-parent"),c.appendChild(n),n},t.remove=function(){d(document.documentElement,"nprogress-busy"),d(document.querySelector(r.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&f(e)},t.isRendered=function(){return!!document.getElementById("nprogress")},t.getPositioningCSS=function(){var e=document.body.style,n="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return n+"Perspective"in e?"translate3d":n+"Transform"in e?"translate":"margin"};var i=function(){var e=[];function n(){var t=e.shift();t&&t(n)}return function(t){e.push(t),1==e.length&&n()}}(),l=function(){var e=["Webkit","O","Moz","ms"],n={};function t(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,(function(e,n){return n.toUpperCase()}))}function r(n){var t=document.body.style;if(n in t)return n;for(var r,o=e.length,a=n.charAt(0).toUpperCase()+n.slice(1);o--;)if((r=e[o]+a)in t)return r;return n}function o(e){return e=t(e),n[e]||(n[e]=r(e))}function a(e,n,t){n=o(n),e.style[n]=t}return function(e,n){var t,r,o=arguments;if(2==o.length)for(t in n)void 0!==(r=n[t])&&n.hasOwnProperty(t)&&a(e,t,r);else a(e,o[1],o[2])}}();function c(e,n){return("string"==typeof e?e:p(e)).indexOf(" "+n+" ")>=0}function u(e,n){var t=p(e),r=t+n;c(t,n)||(e.className=r.substring(1))}function d(e,n){var t,r=p(e);c(e,n)&&(t=r.replace(" "+n+" "," "),e.className=t.substring(1,t.length-1))}function p(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function f(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return t},void 0===(o="function"==typeof r?r.call(n,t,n,e):r)||(e.exports=o)},1596:()=>{!function(e){var n="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",t={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},r={bash:t,environment:{pattern:RegExp("\\$"+n),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+n),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+n),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:r},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:t}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:r},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:r.entity}}],environment:{pattern:RegExp("\\$?"+n),alias:"constant"},variable:r.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},t.inside=e.languages.bash;for(var o=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],a=r.variable[1].inside,s=0;s{!function(e){e.languages.diff={coord:[/^(?:\*{3}|-{3}|\+{3}).*$/m,/^@@.*@@$/m,/^\d.*$/m]};var n={"deleted-sign":"-","deleted-arrow":"<","inserted-sign":"+","inserted-arrow":">",unchanged:" ",diff:"!"};Object.keys(n).forEach((function(t){var r=n[t],o=[];/^\w+$/.test(t)||o.push(/\w+/.exec(t)[0]),"diff"===t&&o.push("bold"),e.languages.diff[t]={pattern:RegExp("^(?:["+r+"].*(?:\r\n?|\n|(?![\\s\\S])))+","m"),alias:o,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(t)[0]}}}})),Object.defineProperty(e.languages.diff,"PREFIXES",{value:n})}(Prism)},5960:()=>{!function(e){e.languages.ejs={delimiter:{pattern:/^<%[-_=]?|[-_]?%>$/,alias:"punctuation"},comment:/^#[\s\S]*/,"language-javascript":{pattern:/[\s\S]+/,inside:e.languages.javascript}},e.hooks.add("before-tokenize",(function(n){e.languages["markup-templating"].buildPlaceholders(n,"ejs",/<%(?!%)[\s\S]+?%>/g)})),e.hooks.add("after-tokenize",(function(n){e.languages["markup-templating"].tokenizePlaceholders(n,"ejs")})),e.languages.eta=e.languages.ejs}(Prism)},9264:()=>{Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json},1808:()=>{!function(e){function n(e,n){return"___"+e.toUpperCase()+n+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(t,r,o,a){if(t.language===r){var s=t.tokenStack=[];t.code=t.code.replace(o,(function(e){if("function"==typeof a&&!a(e))return e;for(var o,i=s.length;-1!==t.code.indexOf(o=n(r,i));)++i;return s[i]=e,o})),t.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(t,r){if(t.language===r&&t.tokenStack){t.grammar=e.languages[r];var o=0,a=Object.keys(t.tokenStack);!function s(i){for(var l=0;l=a.length);l++){var c=i[l];if("string"==typeof c||c.content&&"string"==typeof c.content){var u=a[o],d=t.tokenStack[u],p="string"==typeof c?c:c.content,f=n(r,u),m=p.indexOf(f);if(m>-1){++o;var g=p.substring(0,m),h=new e.Token(r,e.tokenize(d,t.grammar),"language-"+r,d),y=p.substring(m+f.length),b=[];g&&b.push.apply(b,s([g])),b.push(h),y&&b.push.apply(b,s([y])),"string"==typeof c?i.splice.apply(i,[l,1].concat(b)):c.content=b}}else c.content&&s(c.content)}return i}(t.tokens)}}}})}(Prism)},712:()=>{!function(e){e.languages.ruby=e.languages.extend("clike",{comment:{pattern:/#.*|^=begin\s[\s\S]*?^=end/m,greedy:!0},"class-name":{pattern:/(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,operator:/\.{2,3}|&\.|===|=>|[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,punctuation:/[(){}[\].,;]/}),e.languages.insertBefore("ruby","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}});var n={pattern:/((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,lookbehind:!0,inside:{content:{pattern:/^(#\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:e.languages.ruby},delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"}}};delete e.languages.ruby.function;var t="(?:"+[/([^a-zA-Z0-9\s{(\[<=])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/\((?:[^()\\]|\\[\s\S]|\((?:[^()\\]|\\[\s\S])*\))*\)/.source,/\{(?:[^{}\\]|\\[\s\S]|\{(?:[^{}\\]|\\[\s\S])*\})*\}/.source,/\[(?:[^\[\]\\]|\\[\s\S]|\[(?:[^\[\]\\]|\\[\s\S])*\])*\]/.source,/<(?:[^<>\\]|\\[\s\S]|<(?:[^<>\\]|\\[\s\S])*>)*>/.source].join("|")+")",r=/(?:"(?:\\.|[^"\\\r\n])*"|(?:\b[a-zA-Z_]\w*|[^\s\0-\x7F]+)[?!]?|\$.)/.source;e.languages.insertBefore("ruby","keyword",{"regex-literal":[{pattern:RegExp(/%r/.source+t+/[egimnosux]{0,6}/.source),greedy:!0,inside:{interpolation:n,regex:/[\s\S]+/}},{pattern:/(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,lookbehind:!0,greedy:!0,inside:{interpolation:n,regex:/[\s\S]+/}}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:[{pattern:RegExp(/(^|[^:]):/.source+r),lookbehind:!0,greedy:!0},{pattern:RegExp(/([\r\n{(,][ \t]*)/.source+r+/(?=:(?!:))/.source),lookbehind:!0,greedy:!0}],"method-definition":{pattern:/(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,lookbehind:!0,inside:{function:/\b\w+$/,keyword:/^self\b/,"class-name":/^\w+/,punctuation:/\./}}}),e.languages.insertBefore("ruby","string",{"string-literal":[{pattern:RegExp(/%[qQiIwWs]?/.source+t),greedy:!0,inside:{interpolation:n,string:/[\s\S]+/}},{pattern:/("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,greedy:!0,inside:{interpolation:n,string:/[\s\S]+/}},{pattern:/<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?/}},interpolation:n,string:/[\s\S]+/}},{pattern:/<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?'|'$/}},string:/[\s\S]+/}}],"command-literal":[{pattern:RegExp(/%x/.source+t),greedy:!0,inside:{interpolation:n,command:{pattern:/[\s\S]+/,alias:"string"}}},{pattern:/`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,greedy:!0,inside:{interpolation:n,command:{pattern:/[\s\S]+/,alias:"string"}}}]}),delete e.languages.ruby.string,e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,constant:/\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/}),e.languages.rb=e.languages.ruby}(Prism)},4096:(e,n,t)=>{var r={"./prism-bash":1596,"./prism-diff":1496,"./prism-ejs":5960,"./prism-json":9264,"./prism-markup-templating":1808,"./prism-ruby":712};function o(e){var n=a(e);return t(n)}function a(e){if(!t.o(r,e)){var n=new Error("Cannot find module '"+e+"'");throw n.code="MODULE_NOT_FOUND",n}return r[e]}o.keys=function(){return Object.keys(r)},o.resolve=a,e.exports=o,o.id=4096},9776:(e,n,t)=>{"use strict";var r=t(9143);function o(){}function a(){}a.resetWarningCache=o,e.exports=function(){function e(e,n,t,o,a,s){if(s!==r){var i=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw i.name="Invariant Violation",i}}function n(){return e}e.isRequired=e;var t={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:n,element:e,elementType:e,instanceOf:n,node:e,objectOf:n,oneOf:n,oneOfType:n,shape:n,exact:n,checkPropTypes:a,resetWarningCache:o};return t.PropTypes=t,t}},3268:(e,n,t)=>{e.exports=t(9776)()},9143:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},9516:(e,n,t)=>{"use strict";var r=t(1504),o=t(4712);function a(e){for(var n="https://reactjs.org/docs/error-decoder.html?invariant="+e,t=1;tn}return!1}(n,t,o,r)&&(t=null),r||null===o?function(e){return!!d.call(m,e)||!d.call(f,e)&&(p.test(e)?m[e]=!0:(f[e]=!0,!1))}(n)&&(null===t?e.removeAttribute(n):e.setAttribute(n,""+t)):o.mustUseProperty?e[o.propertyName]=null===t?3!==o.type&&"":t:(n=o.attributeName,r=o.attributeNamespace,null===t?e.removeAttribute(n):(t=3===(o=o.type)||4===o&&!0===t?"":""+t,r?e.setAttributeNS(r,n,t):e.setAttribute(n,t))))}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach((function(e){var n=e.replace(y,b);h[n]=new g(n,1,!1,e,null,!1,!1)})),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach((function(e){var n=e.replace(y,b);h[n]=new g(n,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)})),["xml:base","xml:lang","xml:space"].forEach((function(e){var n=e.replace(y,b);h[n]=new g(n,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)})),["tabIndex","crossOrigin"].forEach((function(e){h[e]=new g(e,1,!1,e.toLowerCase(),null,!1,!1)})),h.xlinkHref=new g("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach((function(e){h[e]=new g(e,1,!1,e.toLowerCase(),null,!0,!0)}));var S=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,w=Symbol.for("react.element"),x=Symbol.for("react.portal"),k=Symbol.for("react.fragment"),T=Symbol.for("react.strict_mode"),E=Symbol.for("react.profiler"),C=Symbol.for("react.provider"),A=Symbol.for("react.context"),_=Symbol.for("react.forward_ref"),I=Symbol.for("react.suspense"),P=Symbol.for("react.suspense_list"),R=Symbol.for("react.memo"),L=Symbol.for("react.lazy");Symbol.for("react.scope"),Symbol.for("react.debug_trace_mode");var O=Symbol.for("react.offscreen");Symbol.for("react.legacy_hidden"),Symbol.for("react.cache"),Symbol.for("react.tracing_marker");var N=Symbol.iterator;function F(e){return null===e||"object"!=typeof e?null:"function"==typeof(e=N&&e[N]||e["@@iterator"])?e:null}var j,M=Object.assign;function B(e){if(void 0===j)try{throw Error()}catch(t){var n=t.stack.trim().match(/\n( *(at )?)/);j=n&&n[1]||""}return"\n"+j+e}var D=!1;function $(e,n){if(!e||D)return"";D=!0;var t=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(n)if(n=function(){throw Error()},Object.defineProperty(n.prototype,"props",{set:function(){throw Error()}}),"object"==typeof Reflect&&Reflect.construct){try{Reflect.construct(n,[])}catch(c){var r=c}Reflect.construct(e,[],n)}else{try{n.call()}catch(c){r=c}e.call(n.prototype)}else{try{throw Error()}catch(c){r=c}e()}}catch(c){if(c&&r&&"string"==typeof c.stack){for(var o=c.stack.split("\n"),a=r.stack.split("\n"),s=o.length-1,i=a.length-1;1<=s&&0<=i&&o[s]!==a[i];)i--;for(;1<=s&&0<=i;s--,i--)if(o[s]!==a[i]){if(1!==s||1!==i)do{if(s--,0>--i||o[s]!==a[i]){var l="\n"+o[s].replace(" at new "," at ");return e.displayName&&l.includes("")&&(l=l.replace("",e.displayName)),l}}while(1<=s&&0<=i);break}}}finally{D=!1,Error.prepareStackTrace=t}return(e=e?e.displayName||e.name:"")?B(e):""}function V(e){switch(e.tag){case 5:return B(e.type);case 16:return B("Lazy");case 13:return B("Suspense");case 19:return B("SuspenseList");case 0:case 2:case 15:return e=$(e.type,!1);case 11:return e=$(e.type.render,!1);case 1:return e=$(e.type,!0);default:return""}}function U(e){if(null==e)return null;if("function"==typeof e)return e.displayName||e.name||null;if("string"==typeof e)return e;switch(e){case k:return"Fragment";case x:return"Portal";case E:return"Profiler";case T:return"StrictMode";case I:return"Suspense";case P:return"SuspenseList"}if("object"==typeof e)switch(e.$$typeof){case A:return(e.displayName||"Context")+".Consumer";case C:return(e._context.displayName||"Context")+".Provider";case _:var n=e.render;return(e=e.displayName)||(e=""!==(e=n.displayName||n.name||"")?"ForwardRef("+e+")":"ForwardRef"),e;case R:return null!==(n=e.displayName||null)?n:U(e.type)||"Memo";case L:n=e._payload,e=e._init;try{return U(e(n))}catch(t){}}return null}function z(e){var n=e.type;switch(e.tag){case 24:return"Cache";case 9:return(n.displayName||"Context")+".Consumer";case 10:return(n._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=(e=n.render).displayName||e.name||"",n.displayName||(""!==e?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return n;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return U(n);case 8:return n===T?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if("function"==typeof n)return n.displayName||n.name||null;if("string"==typeof n)return n}return null}function H(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":case"object":return e;default:return""}}function W(e){var n=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase()&&("checkbox"===n||"radio"===n)}function G(e){e._valueTracker||(e._valueTracker=function(e){var n=W(e)?"checked":"value",t=Object.getOwnPropertyDescriptor(e.constructor.prototype,n),r=""+e[n];if(!e.hasOwnProperty(n)&&void 0!==t&&"function"==typeof t.get&&"function"==typeof t.set){var o=t.get,a=t.set;return Object.defineProperty(e,n,{configurable:!0,get:function(){return o.call(this)},set:function(e){r=""+e,a.call(this,e)}}),Object.defineProperty(e,n,{enumerable:t.enumerable}),{getValue:function(){return r},setValue:function(e){r=""+e},stopTracking:function(){e._valueTracker=null,delete e[n]}}}}(e))}function q(e){if(!e)return!1;var n=e._valueTracker;if(!n)return!0;var t=n.getValue(),r="";return e&&(r=W(e)?e.checked?"true":"false":e.value),(e=r)!==t&&(n.setValue(e),!0)}function K(e){if(void 0===(e=e||("undefined"!=typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(n){return e.body}}function Y(e,n){var t=n.checked;return M({},n,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=t?t:e._wrapperState.initialChecked})}function X(e,n){var t=null==n.defaultValue?"":n.defaultValue,r=null!=n.checked?n.checked:n.defaultChecked;t=H(null!=n.value?n.value:t),e._wrapperState={initialChecked:r,initialValue:t,controlled:"checkbox"===n.type||"radio"===n.type?null!=n.checked:null!=n.value}}function Q(e,n){null!=(n=n.checked)&&v(e,"checked",n,!1)}function Z(e,n){Q(e,n);var t=H(n.value),r=n.type;if(null!=t)"number"===r?(0===t&&""===e.value||e.value!=t)&&(e.value=""+t):e.value!==""+t&&(e.value=""+t);else if("submit"===r||"reset"===r)return void e.removeAttribute("value");n.hasOwnProperty("value")?ee(e,n.type,t):n.hasOwnProperty("defaultValue")&&ee(e,n.type,H(n.defaultValue)),null==n.checked&&null!=n.defaultChecked&&(e.defaultChecked=!!n.defaultChecked)}function J(e,n,t){if(n.hasOwnProperty("value")||n.hasOwnProperty("defaultValue")){var r=n.type;if(!("submit"!==r&&"reset"!==r||void 0!==n.value&&null!==n.value))return;n=""+e._wrapperState.initialValue,t||n===e.value||(e.value=n),e.defaultValue=n}""!==(t=e.name)&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,""!==t&&(e.name=t)}function ee(e,n,t){"number"===n&&K(e.ownerDocument)===e||(null==t?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+t&&(e.defaultValue=""+t))}var ne=Array.isArray;function te(e,n,t,r){if(e=e.options,n){n={};for(var o=0;o"+n.valueOf().toString()+"",n=ce.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;n.firstChild;)e.appendChild(n.firstChild)}},"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(e,n,t,r){MSApp.execUnsafeLocalFunction((function(){return ue(e,n)}))}:ue);function pe(e,n){if(n){var t=e.firstChild;if(t&&t===e.lastChild&&3===t.nodeType)return void(t.nodeValue=n)}e.textContent=n}var fe={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},me=["Webkit","ms","Moz","O"];function ge(e,n,t){return null==n||"boolean"==typeof n||""===n?"":t||"number"!=typeof n||0===n||fe.hasOwnProperty(e)&&fe[e]?(""+n).trim():n+"px"}function he(e,n){for(var t in e=e.style,n)if(n.hasOwnProperty(t)){var r=0===t.indexOf("--"),o=ge(t,n[t],r);"float"===t&&(t="cssFloat"),r?e.setProperty(t,o):e[t]=o}}Object.keys(fe).forEach((function(e){me.forEach((function(n){n=n+e.charAt(0).toUpperCase()+e.substring(1),fe[n]=fe[e]}))}));var ye=M({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function be(e,n){if(n){if(ye[e]&&(null!=n.children||null!=n.dangerouslySetInnerHTML))throw Error(a(137,e));if(null!=n.dangerouslySetInnerHTML){if(null!=n.children)throw Error(a(60));if("object"!=typeof n.dangerouslySetInnerHTML||!("__html"in n.dangerouslySetInnerHTML))throw Error(a(61))}if(null!=n.style&&"object"!=typeof n.style)throw Error(a(62))}}function ve(e,n){if(-1===e.indexOf("-"))return"string"==typeof n.is;switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Se=null;function we(e){return(e=e.target||e.srcElement||window).correspondingUseElement&&(e=e.correspondingUseElement),3===e.nodeType?e.parentNode:e}var xe=null,ke=null,Te=null;function Ee(e){if(e=So(e)){if("function"!=typeof xe)throw Error(a(280));var n=e.stateNode;n&&(n=xo(n),xe(e.stateNode,e.type,n))}}function Ce(e){ke?Te?Te.push(e):Te=[e]:ke=e}function Ae(){if(ke){var e=ke,n=Te;if(Te=ke=null,Ee(e),n)for(e=0;e