diff --git a/404.html b/404.html index 394f6108..a0ac1f02 100644 --- a/404.html +++ b/404.html @@ -5,8 +5,8 @@ Page Not Found | Ignite Cookbook for React Native - - + +
@@ -33,7 +33,7 @@

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- - + + \ No newline at end of file diff --git a/assets/js/03224537.b07b497a.js b/assets/js/03224537.7cce6a8b.js similarity index 85% rename from assets/js/03224537.b07b497a.js rename to assets/js/03224537.7cce6a8b.js index 58d0abdc..c2814502 100644 --- a/assets/js/03224537.b07b497a.js +++ b/assets/js/03224537.7cce6a8b.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[6275],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>m});var o=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=o.createContext({}),u=function(e){var t=o.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},c=function(e){var t=u(e.components);return o.createElement(l.Provider,{value:t},e.children)},p={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},d=o.forwardRef((function(e,t){var n=e.components,r=e.mdxType,a=e.originalType,l=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),d=u(n),m=r,f=d["".concat(l,".").concat(m)]||d[m]||p[m]||a;return n?o.createElement(f,i(i({ref:t},c),{},{components:n})):o.createElement(f,i({ref:t},c))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=n.length,i=new Array(a);i[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:r,i[1]=s;for(var u=2;u{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>p,frontMatter:()=>a,metadata:()=>s,toc:()=>u});var o=n(7462),r=(n(7294),n(3905));const a={title:"Detox Intro",description:"A quick look at Detox and what makes it useful",tags:["Testing","Intro"],last_update:{author:"Jamon Holmgren"},publish_date:new Date("2022-10-09T00:00:00.000Z")},i=void 0,s={unversionedId:"recipes/DetoxIntro",id:"recipes/DetoxIntro",title:"Detox Intro",description:"A quick look at Detox and what makes it useful",source:"@site/docs/recipes/DetoxIntro.md",sourceDirName:"recipes",slug:"/recipes/DetoxIntro",permalink:"/docs/recipes/DetoxIntro",draft:!1,tags:[{label:"Testing",permalink:"/docs/tags/testing"},{label:"Intro",permalink:"/docs/tags/intro"}],version:"current",lastUpdatedBy:"Jamon Holmgren",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Detox Intro",description:"A quick look at Detox and what makes it useful",tags:["Testing","Intro"],last_update:{author:"Jamon Holmgren"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Creating a Good Experience for Screen Readers",permalink:"/docs/recipes/CreatingGreateExperienceForScreenReaders"},next:{title:"Distributing Auth Token to APIs",permalink:"/docs/recipes/DistributingAuthTokenToAPI"}},l={},u=[{value:"Installation",id:"installation",level:3},{value:"What's unique about Detox",id:"whats-unique-about-detox",level:3}],c={toc:u};function p(e){let{components:t,...n}=e;return(0,r.kt)("wrapper",(0,o.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Detox is a library for end-to-end testing of React Native apps. This wiki provides information on how to use Detox effectively."),(0,r.kt)("h3",{id:"installation"},"Installation"),(0,r.kt)("p",null,"Detox's ",(0,r.kt)("a",{parentName:"p",href:"https://wix.github.io/Detox/docs/introduction/getting-started/"},"documentation for installation"),"."),(0,r.kt)("p",null,"It's included ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/infinitered/ignite/tree/master/boilerplate/detox"},"by default in Ignite"),"."),(0,r.kt)("h3",{id:"whats-unique-about-detox"},"What's unique about Detox"),(0,r.kt)("p",null,(0,r.kt)("strong",{parentName:"p"},"Synchronization")),(0,r.kt)("p",null,"One of the key features that makes Detox unique is that it synchronizes with app state, so it can know when to move to the next step of a test, instead of including manual sleep statements. ",(0,r.kt)("a",{parentName:"p",href:"https://wix.github.io/Detox/docs/articles/how-detox-works/#how-detox-automatically-synchronizes-with-your-app"},"See the documentation for more info"),"."),(0,r.kt)("p",null,"But this does create new kinds of errors to be aware of. For example, if you see a Detox test hanging, that's an indication that Detox might not be detecting that the app went into the idle state so that the test can continue. To debug, check out ",(0,r.kt)("a",{parentName:"p",href:"https://wix.github.io/Detox/docs/troubleshooting/synchronization"},"Detox's troubleshooting guide on sync issues"),"."),(0,r.kt)("p",null,(0,r.kt)("strong",{parentName:"p"}," Flaky Tests ")),(0,r.kt)("p",null,"The RNR team interviewed Rotem Meidan, former lead of Detox, about Detox stability, in ",(0,r.kt)("a",{parentName:"p",href:"https://reactnativeradio.com/episodes/rnr-189-reliable-detox-with-rotem-opBGVWSK"},"React-Native Radio 189"),"."),(0,r.kt)("p",null,"He wrote an article about that here: ",(0,r.kt)("a",{parentName:"p",href:"https://medium.com/wix-engineering/detox-writing-stable-test-suites-372c9d537184"},"Detox: Writing Stable Test Suites")))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[6275],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>m});var o=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function i(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=o.createContext({}),c=function(e){var t=o.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},u=function(e){var t=c(e.components);return o.createElement(l.Provider,{value:t},e.children)},p={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},d=o.forwardRef((function(e,t){var n=e.components,r=e.mdxType,a=e.originalType,l=e.parentName,u=s(e,["components","mdxType","originalType","parentName"]),d=c(n),m=r,f=d["".concat(l,".").concat(m)]||d[m]||p[m]||a;return n?o.createElement(f,i(i({ref:t},u),{},{components:n})):o.createElement(f,i({ref:t},u))}));function m(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var a=n.length,i=new Array(a);i[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:r,i[1]=s;for(var c=2;c{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>p,frontMatter:()=>a,metadata:()=>s,toc:()=>c});var o=n(7462),r=(n(7294),n(3905));const a={title:"Detox Intro",description:"A quick look at Detox and what makes it useful",tags:["Testing","Intro"],last_update:{author:"Jamon Holmgren"},publish_date:new Date("2022-10-09T00:00:00.000Z")},i=void 0,s={unversionedId:"recipes/DetoxIntro",id:"recipes/DetoxIntro",title:"Detox Intro",description:"A quick look at Detox and what makes it useful",source:"@site/docs/recipes/DetoxIntro.md",sourceDirName:"recipes",slug:"/recipes/DetoxIntro",permalink:"/docs/recipes/DetoxIntro",draft:!1,tags:[{label:"Testing",permalink:"/docs/tags/testing"},{label:"Intro",permalink:"/docs/tags/intro"}],version:"current",lastUpdatedBy:"Jamon Holmgren",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Detox Intro",description:"A quick look at Detox and what makes it useful",tags:["Testing","Intro"],last_update:{author:"Jamon Holmgren"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Creating a Good Experience for Screen Readers",permalink:"/docs/recipes/CreatingGreateExperienceForScreenReaders"},next:{title:"Distributing Auth Token to APIs",permalink:"/docs/recipes/DistributingAuthTokenToAPI"}},l={},c=[{value:"Installation",id:"installation",level:3},{value:"What's unique about Detox",id:"whats-unique-about-detox",level:3}],u={toc:c};function p(e){let{components:t,...n}=e;return(0,r.kt)("wrapper",(0,o.Z)({},u,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"Detox is a library for end-to-end testing of React Native apps. This wiki provides information on how to use Detox effectively."),(0,r.kt)("h3",{id:"installation"},"Installation"),(0,r.kt)("p",null,"Detox's ",(0,r.kt)("a",{parentName:"p",href:"https://wix.github.io/Detox/docs/introduction/getting-started/"},"documentation for installation"),"."),(0,r.kt)("p",null,"It's included ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/infinitered/ignite/tree/master/boilerplate/detox"},"by default in Ignite"),"."),(0,r.kt)("h3",{id:"whats-unique-about-detox"},"What's unique about Detox"),(0,r.kt)("p",null,(0,r.kt)("strong",{parentName:"p"},"Synchronization")),(0,r.kt)("p",null,"One of the key features that makes Detox unique is that it synchronizes with app state, so it can know when to move to the next step of a test, instead of including manual sleep statements. ",(0,r.kt)("a",{parentName:"p",href:"https://wix.github.io/Detox/docs/articles/how-detox-works/#how-detox-automatically-synchronizes-with-your-app"},"See the documentation for more info"),"."),(0,r.kt)("p",null,"But this does create new kinds of errors to be aware of. For example, if you see a Detox test hanging, that's an indication that Detox might not be detecting that the app went into the idle state so that the test can continue. To debug, check out ",(0,r.kt)("a",{parentName:"p",href:"https://wix.github.io/Detox/docs/troubleshooting/synchronization"},"Detox's troubleshooting guide on sync issues"),"."),(0,r.kt)("p",null,(0,r.kt)("strong",{parentName:"p"}," Flaky Tests ")),(0,r.kt)("p",null,"The RNR team interviewed Rotem Meidan, former lead of Detox, about Detox stability, in ",(0,r.kt)("a",{parentName:"p",href:"https://reactnativeradio.com/episodes/rnr-189-reliable-detox-with-rotem-opBGVWSK"},"React-Native Radio 189"),"."),(0,r.kt)("p",null,"He wrote an article about that here: ",(0,r.kt)("a",{parentName:"p",href:"https://medium.com/wix-engineering/detox-writing-stable-test-suites-372c9d537184"},"Detox: Writing Stable Test Suites")))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0e384e19.a342a1ac.js b/assets/js/0e384e19.8ac132be.js similarity index 99% rename from assets/js/0e384e19.a342a1ac.js rename to assets/js/0e384e19.8ac132be.js index cb611afc..22acb2b6 100644 --- a/assets/js/0e384e19.a342a1ac.js +++ b/assets/js/0e384e19.8ac132be.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[9671],{3905:(e,t,o)=>{o.d(t,{Zo:()=>l,kt:()=>h});var n=o(7294);function i(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}function r(e,t){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),o.push.apply(o,n)}return o}function a(e){for(var t=1;t=0||(i[o]=e[o]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(i[o]=e[o])}return i}var c=n.createContext({}),p=function(e){var t=n.useContext(c),o=t;return e&&(o="function"==typeof e?e(t):a(a({},t),e)),o},l=function(e){var t=p(e.components);return n.createElement(c.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},u=n.forwardRef((function(e,t){var o=e.components,i=e.mdxType,r=e.originalType,c=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),u=p(o),h=i,m=u["".concat(c,".").concat(h)]||u[h]||d[h]||r;return o?n.createElement(m,a(a({ref:t},l),{},{components:o})):n.createElement(m,a({ref:t},l))}));function h(e,t){var o=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=o.length,a=new Array(r);a[0]=u;var s={};for(var c in t)hasOwnProperty.call(t,c)&&(s[c]=t[c]);s.originalType=e,s.mdxType="string"==typeof e?e:i,a[1]=s;for(var p=2;p{o.r(t),o.d(t,{assets:()=>c,contentTitle:()=>a,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>p});var n=o(7462),i=(o(7294),o(3905));const r={sidebar_position:1,title:"Intro to Recipes",description:"Welcome to the Ignite Cookbook! This is a collection of recipes for common patterns in Ignite projects.",tags:["Intro"],last_update:{author:"Dan Edwards"}},a="Welcome to the Ignite Cookbook \ud83d\udc4b",s={unversionedId:"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.",source:"@site/docs/intro.md",sourceDirName:".",slug:"/intro",permalink:"/docs/intro",draft:!1,tags:[{label:"Intro",permalink:"/docs/tags/intro"}],version:"current",lastUpdatedBy:"Dan Edwards",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",sidebarPosition:1,frontMatter:{sidebar_position:1,title:"Intro to Recipes",description:"Welcome to the Ignite Cookbook! This is a collection of recipes for common patterns in Ignite projects.",tags:["Intro"],last_update:{author:"Dan Edwards"}},sidebar:"mainSidebar",next:{title:"Accessiblity Font Sizes",permalink:"/docs/recipes/AccessibilityFontSizes"}},c={},p=[{value:"What is a Recipe?",id:"what-is-a-recipe",level:2},{value:"Who is this for?",id:"who-is-this-for",level:2},{value:"Are these recipes tested and up-to-date?",id:"are-these-recipes-tested-and-up-to-date",level:2},{value:"Backed by the Ignite Team & Community",id:"backed-by-the-ignite-team--community",level:2},{value:"Have an idea for a recipe?",id:"have-an-idea-for-a-recipe",level:2}],l={toc:p};function d(e){let{components:t,...o}=e;return(0,i.kt)("wrapper",(0,n.Z)({},l,o,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"welcome-to-the-ignite-cookbook-"},"Welcome to the Ignite Cookbook \ud83d\udc4b"),(0,i.kt)("p",null,"This is a collection of recipes for common patterns that we use sometimes at Infinite Red or in the Ignite community but don't quite belong in the Ignite boilerplate directly. We'll be adding to this over time, so be sure to check back often!"),(0,i.kt)("h2",{id:"what-is-a-recipe"},"What is a Recipe?"),(0,i.kt)("p",null,"A recipe is a collection of steps that you can follow to accomplish a common task or pattern in your Ignite project. Recipes are written to be as simple as possible, and are meant to be a starting point for you to build upon. You can use them as-is, or you can use them as a reference to help you create your own solutions."),(0,i.kt)("h2",{id:"who-is-this-for"},"Who is this for?"),(0,i.kt)("p",null,"This is for anyone who wants to extend their Ignite-based React Native app with additional functionality, and how to use it to build their own apps. You don't need to be an expert in React Native or Ignite to follow along. If you're new to Ignite, we recommend you start with the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/infinitered/ignite/tree/master/docs/"},"new docs page to explore the boilerplate"),". You can also use these patterns if you're not using Ignite, as in the end Ignite is just React Native!"),(0,i.kt)("h2",{id:"are-these-recipes-tested-and-up-to-date"},"Are these recipes tested and up-to-date?"),(0,i.kt)("p",null,"All recipes are written or reviewed by the Ignite team, and are also reviewed by the community. If you find a recipe that is out of date, or doesn't work, please ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/infinitered/ignite-cookbook/issues"},"open an issue"),". It's our goal to keep these recipes up to date and working, so we appreciate your help!"),(0,i.kt)("h2",{id:"backed-by-the-ignite-team--community"},"Backed by the Ignite Team & Community"),(0,i.kt)("p",null,"The Ignite Cookbook is maintained by the ",(0,i.kt)("a",{parentName:"p",href:"https://infinite.red"},"Infinite Red")," team, and the community. If you have any questions, or want to contribute, please ",(0,i.kt)("a",{parentName:"p",href:"https://join.slack.com/t/infiniteredcommunity/shared_invite/zt-1e1gob8vn-pcFjKM~n1c~aXFsTnvHpdg"},"join our Slack")," and say hi!"),(0,i.kt)("h2",{id:"have-an-idea-for-a-recipe"},"Have an idea for a recipe?"),(0,i.kt)("p",null,"We'd love to hear it! Please ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/infinitered/ignite-cookbook/issues"},"open an issue")," and we'll review it. We're always looking for new ideas to add to the cookbook!"))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[9671],{3905:(e,t,o)=>{o.d(t,{Zo:()=>l,kt:()=>h});var n=o(7294);function i(e,t,o){return t in e?Object.defineProperty(e,t,{value:o,enumerable:!0,configurable:!0,writable:!0}):e[t]=o,e}function r(e,t){var o=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),o.push.apply(o,n)}return o}function a(e){for(var t=1;t=0||(i[o]=e[o]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,o)&&(i[o]=e[o])}return i}var c=n.createContext({}),p=function(e){var t=n.useContext(c),o=t;return e&&(o="function"==typeof e?e(t):a(a({},t),e)),o},l=function(e){var t=p(e.components);return n.createElement(c.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},u=n.forwardRef((function(e,t){var o=e.components,i=e.mdxType,r=e.originalType,c=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),u=p(o),h=i,m=u["".concat(c,".").concat(h)]||u[h]||d[h]||r;return o?n.createElement(m,a(a({ref:t},l),{},{components:o})):n.createElement(m,a({ref:t},l))}));function h(e,t){var o=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=o.length,a=new Array(r);a[0]=u;var s={};for(var c in t)hasOwnProperty.call(t,c)&&(s[c]=t[c]);s.originalType=e,s.mdxType="string"==typeof e?e:i,a[1]=s;for(var p=2;p{o.r(t),o.d(t,{assets:()=>c,contentTitle:()=>a,default:()=>d,frontMatter:()=>r,metadata:()=>s,toc:()=>p});var n=o(7462),i=(o(7294),o(3905));const r={sidebar_position:1,title:"Intro to Recipes",description:"Welcome to the Ignite Cookbook! This is a collection of recipes for common patterns in Ignite projects.",tags:["Intro"],last_update:{author:"Dan Edwards"}},a="Welcome to the Ignite Cookbook \ud83d\udc4b",s={unversionedId:"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.",source:"@site/docs/intro.md",sourceDirName:".",slug:"/intro",permalink:"/docs/intro",draft:!1,tags:[{label:"Intro",permalink:"/docs/tags/intro"}],version:"current",lastUpdatedBy:"Dan Edwards",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",sidebarPosition:1,frontMatter:{sidebar_position:1,title:"Intro to Recipes",description:"Welcome to the Ignite Cookbook! This is a collection of recipes for common patterns in Ignite projects.",tags:["Intro"],last_update:{author:"Dan Edwards"}},sidebar:"mainSidebar",next:{title:"Accessiblity Font Sizes",permalink:"/docs/recipes/AccessibilityFontSizes"}},c={},p=[{value:"What is a Recipe?",id:"what-is-a-recipe",level:2},{value:"Who is this for?",id:"who-is-this-for",level:2},{value:"Are these recipes tested and up-to-date?",id:"are-these-recipes-tested-and-up-to-date",level:2},{value:"Backed by the Ignite Team & Community",id:"backed-by-the-ignite-team--community",level:2},{value:"Have an idea for a recipe?",id:"have-an-idea-for-a-recipe",level:2}],l={toc:p};function d(e){let{components:t,...o}=e;return(0,i.kt)("wrapper",(0,n.Z)({},l,o,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"welcome-to-the-ignite-cookbook-"},"Welcome to the Ignite Cookbook \ud83d\udc4b"),(0,i.kt)("p",null,"This is a collection of recipes for common patterns that we use sometimes at Infinite Red or in the Ignite community but don't quite belong in the Ignite boilerplate directly. We'll be adding to this over time, so be sure to check back often!"),(0,i.kt)("h2",{id:"what-is-a-recipe"},"What is a Recipe?"),(0,i.kt)("p",null,"A recipe is a collection of steps that you can follow to accomplish a common task or pattern in your Ignite project. Recipes are written to be as simple as possible, and are meant to be a starting point for you to build upon. You can use them as-is, or you can use them as a reference to help you create your own solutions."),(0,i.kt)("h2",{id:"who-is-this-for"},"Who is this for?"),(0,i.kt)("p",null,"This is for anyone who wants to extend their Ignite-based React Native app with additional functionality, and how to use it to build their own apps. You don't need to be an expert in React Native or Ignite to follow along. If you're new to Ignite, we recommend you start with the ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/infinitered/ignite/tree/master/docs/"},"new docs page to explore the boilerplate"),". You can also use these patterns if you're not using Ignite, as in the end Ignite is just React Native!"),(0,i.kt)("h2",{id:"are-these-recipes-tested-and-up-to-date"},"Are these recipes tested and up-to-date?"),(0,i.kt)("p",null,"All recipes are written or reviewed by the Ignite team, and are also reviewed by the community. If you find a recipe that is out of date, or doesn't work, please ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/infinitered/ignite-cookbook/issues"},"open an issue"),". It's our goal to keep these recipes up to date and working, so we appreciate your help!"),(0,i.kt)("h2",{id:"backed-by-the-ignite-team--community"},"Backed by the Ignite Team & Community"),(0,i.kt)("p",null,"The Ignite Cookbook is maintained by the ",(0,i.kt)("a",{parentName:"p",href:"https://infinite.red"},"Infinite Red")," team, and the community. If you have any questions, or want to contribute, please ",(0,i.kt)("a",{parentName:"p",href:"https://join.slack.com/t/infiniteredcommunity/shared_invite/zt-1e1gob8vn-pcFjKM~n1c~aXFsTnvHpdg"},"join our Slack")," and say hi!"),(0,i.kt)("h2",{id:"have-an-idea-for-a-recipe"},"Have an idea for a recipe?"),(0,i.kt)("p",null,"We'd love to hear it! Please ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/infinitered/ignite-cookbook/issues"},"open an issue")," and we'll review it. We're always looking for new ideas to add to the cookbook!"))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/145425bc.7c0b0ab6.js b/assets/js/145425bc.b68924b5.js similarity index 98% rename from assets/js/145425bc.7c0b0ab6.js rename to assets/js/145425bc.b68924b5.js index 101ad435..10810036 100644 --- a/assets/js/145425bc.7c0b0ab6.js +++ b/assets/js/145425bc.b68924b5.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[3188],{3905:(t,e,n)=>{n.d(e,{Zo:()=>p,kt:()=>f});var o=n(7294);function a(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);e&&(o=o.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,o)}return n}function r(t){for(var e=1;e=0||(a[n]=t[n]);return a}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(a[n]=t[n])}return a}var l=o.createContext({}),c=function(t){var e=o.useContext(l),n=e;return t&&(n="function"==typeof t?t(e):r(r({},e),t)),n},p=function(t){var e=c(t.components);return o.createElement(l.Provider,{value:e},t.children)},u={inlineCode:"code",wrapper:function(t){var e=t.children;return o.createElement(o.Fragment,{},e)}},g=o.forwardRef((function(t,e){var n=t.components,a=t.mdxType,i=t.originalType,l=t.parentName,p=s(t,["components","mdxType","originalType","parentName"]),g=c(n),f=a,S=g["".concat(l,".").concat(f)]||g[f]||u[f]||i;return n?o.createElement(S,r(r({ref:e},p),{},{components:n})):o.createElement(S,r({ref:e},p))}));function f(t,e){var n=arguments,a=e&&e.mdxType;if("string"==typeof t||a){var i=n.length,r=new Array(i);r[0]=g;var s={};for(var l in e)hasOwnProperty.call(e,l)&&(s[l]=e[l]);s.originalType=t,s.mdxType="string"==typeof t?t:a,r[1]=s;for(var c=2;c{n.r(e),n.d(e,{assets:()=>l,contentTitle:()=>r,default:()=>u,frontMatter:()=>i,metadata:()=>s,toc:()=>c});var o=n(7462),a=(n(7294),n(3905));const i={title:"Accessiblity Font Sizes",description:"Dealing With Accessibility Font Sizes in React Native",tags:["Accessibility"],last_update:{author:"Mark Rickert"},publish_date:new Date("2022-10-09T00:00:00.000Z")},r="Dealing With Accessibility Font Sizes in React Native",s={unversionedId:"recipes/AccessibilityFontSizes",id:"recipes/AccessibilityFontSizes",title:"Accessiblity Font Sizes",description:"Dealing With Accessibility Font Sizes in React Native",source:"@site/docs/recipes/AccessibilityFontSizes.md",sourceDirName:"recipes",slug:"/recipes/AccessibilityFontSizes",permalink:"/docs/recipes/AccessibilityFontSizes",draft:!1,tags:[{label:"Accessibility",permalink:"/docs/tags/accessibility"}],version:"current",lastUpdatedBy:"Mark Rickert",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Accessiblity Font Sizes",description:"Dealing With Accessibility Font Sizes in React Native",tags:["Accessibility"],last_update:{author:"Mark Rickert"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Intro to Recipes",permalink:"/docs/intro"},next:{title:"CircleCI CD Setup - React Native",permalink:"/docs/recipes/CircleCIRNSetup"}},l={},c=[],p={toc:c};function u(t){let{components:e,...n}=t;return(0,a.kt)("wrapper",(0,o.Z)({},p,n,{components:e,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"dealing-with-accessibility-font-sizes-in-react-native"},"Dealing With Accessibility Font Sizes in React Native"),(0,a.kt)("p",null,"Modern phones have a lot of accessibility options. Users can make the font size on Android GIGANTIC. This is a way you can allow users to scale their fonts larger and smaller, but only to a certain point. We wanted the accessibility but not the extreme ends of it, just to keep things readable without turning off font scaling completely."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-jsx"},'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')))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[3188],{3905:(t,e,n)=>{n.d(e,{Zo:()=>p,kt:()=>f});var o=n(7294);function a(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(t);e&&(o=o.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,o)}return n}function r(t){for(var e=1;e=0||(a[n]=t[n]);return a}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(t,n)&&(a[n]=t[n])}return a}var l=o.createContext({}),c=function(t){var e=o.useContext(l),n=e;return t&&(n="function"==typeof t?t(e):r(r({},e),t)),n},p=function(t){var e=c(t.components);return o.createElement(l.Provider,{value:e},t.children)},u={inlineCode:"code",wrapper:function(t){var e=t.children;return o.createElement(o.Fragment,{},e)}},g=o.forwardRef((function(t,e){var n=t.components,a=t.mdxType,i=t.originalType,l=t.parentName,p=s(t,["components","mdxType","originalType","parentName"]),g=c(n),f=a,S=g["".concat(l,".").concat(f)]||g[f]||u[f]||i;return n?o.createElement(S,r(r({ref:e},p),{},{components:n})):o.createElement(S,r({ref:e},p))}));function f(t,e){var n=arguments,a=e&&e.mdxType;if("string"==typeof t||a){var i=n.length,r=new Array(i);r[0]=g;var s={};for(var l in e)hasOwnProperty.call(e,l)&&(s[l]=e[l]);s.originalType=t,s.mdxType="string"==typeof t?t:a,r[1]=s;for(var c=2;c{n.r(e),n.d(e,{assets:()=>l,contentTitle:()=>r,default:()=>u,frontMatter:()=>i,metadata:()=>s,toc:()=>c});var o=n(7462),a=(n(7294),n(3905));const i={title:"Accessiblity Font Sizes",description:"Dealing With Accessibility Font Sizes in React Native",tags:["Accessibility"],last_update:{author:"Mark Rickert"},publish_date:new Date("2022-10-09T00:00:00.000Z")},r="Dealing With Accessibility Font Sizes in React Native",s={unversionedId:"recipes/AccessibilityFontSizes",id:"recipes/AccessibilityFontSizes",title:"Accessiblity Font Sizes",description:"Dealing With Accessibility Font Sizes in React Native",source:"@site/docs/recipes/AccessibilityFontSizes.md",sourceDirName:"recipes",slug:"/recipes/AccessibilityFontSizes",permalink:"/docs/recipes/AccessibilityFontSizes",draft:!1,tags:[{label:"Accessibility",permalink:"/docs/tags/accessibility"}],version:"current",lastUpdatedBy:"Mark Rickert",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Accessiblity Font Sizes",description:"Dealing With Accessibility Font Sizes in React Native",tags:["Accessibility"],last_update:{author:"Mark Rickert"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Intro to Recipes",permalink:"/docs/intro"},next:{title:"CircleCI CD Setup - React Native",permalink:"/docs/recipes/CircleCIRNSetup"}},l={},c=[],p={toc:c};function u(t){let{components:e,...n}=t;return(0,a.kt)("wrapper",(0,o.Z)({},p,n,{components:e,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"dealing-with-accessibility-font-sizes-in-react-native"},"Dealing With Accessibility Font Sizes in React Native"),(0,a.kt)("p",null,"Modern phones have a lot of accessibility options. Users can make the font size on Android GIGANTIC. This is a way you can allow users to scale their fonts larger and smaller, but only to a certain point. We wanted the accessibility but not the extreme ends of it, just to keep things readable without turning off font scaling completely."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-jsx"},'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')))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/1c9ea255.7b236c52.js b/assets/js/1c9ea255.4a161e53.js similarity index 98% rename from assets/js/1c9ea255.7b236c52.js rename to assets/js/1c9ea255.4a161e53.js index cb51c8ce..2848f374 100644 --- a/assets/js/1c9ea255.7b236c52.js +++ b/assets/js/1c9ea255.4a161e53.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[420],{3905:(e,t,r)=>{r.d(t,{Zo:()=>c,kt:()=>d});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function i(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var l=n.createContext({}),s=function(e){var t=n.useContext(l),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},c=function(e){var t=s(e.components);return n.createElement(l.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,c=p(e,["components","mdxType","originalType","parentName"]),m=s(r),d=a,g=m["".concat(l,".").concat(d)]||m[d]||u[d]||o;return r?n.createElement(g,i(i({ref:t},c),{},{components:r})):n.createElement(g,i({ref:t},c))}));function d(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=r.length,i=new Array(o);i[0]=m;var p={};for(var l in t)hasOwnProperty.call(t,l)&&(p[l]=t[l]);p.originalType=e,p.mdxType="string"==typeof e?e:a,i[1]=p;for(var s=2;s{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>p,toc:()=>s});var n=r(7462),a=(r(7294),r(3905));const o={title:"TypeScript baseUrl Configuration",description:"How to configure TypeScript's baseUrl module for rewriting relative imports",tags:["TypeScript","Babel"],last_update:{author:"Frank Calise"},publish_date:new Date("2022-10-24T00:00:00.000Z")},i="TypeScript baseUrl Configuration",p={unversionedId:"recipes/TypeScriptBaseURL",id:"recipes/TypeScriptBaseURL",title:"TypeScript baseUrl Configuration",description:"How to configure TypeScript's baseUrl module for rewriting relative imports",source:"@site/docs/recipes/TypeScriptBaseURL.md",sourceDirName:"recipes",slug:"/recipes/TypeScriptBaseURL",permalink:"/docs/recipes/TypeScriptBaseURL",draft:!1,tags:[{label:"TypeScript",permalink:"/docs/tags/type-script"},{label:"Babel",permalink:"/docs/tags/babel"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"TypeScript baseUrl Configuration",description:"How to configure TypeScript's baseUrl module for rewriting relative imports",tags:["TypeScript","Babel"],last_update:{author:"Frank Calise"},publish_date:"2022-10-24T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Staying With Expo",permalink:"/docs/recipes/StayingWithExpo"},next:{title:"Scrolling to a location that hasn't been rendered using FlatList or SectionList",permalink:"/docs/recipes/UnrenderedItemInScrollView"}},l={},s=[{value:"Overview",id:"overview",level:2},{value:"Project Dependencies",id:"project-dependencies",level:2},{value:"TypeScript Configuration Changes",id:"typescript-configuration-changes",level:2},{value:"Babel Configuration Changes",id:"babel-configuration-changes",level:2},{value:"Taste Test in a Component!",id:"taste-test-in-a-component",level:2},{value:"Resources",id:"resources",level:2}],c={toc:s};function u(e){let{components:t,...r}=e;return(0,a.kt)("wrapper",(0,n.Z)({},c,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"typescript-baseurl-configuration"},"TypeScript baseUrl Configuration"),(0,a.kt)("h2",{id:"overview"},"Overview"),(0,a.kt)("p",null,"Depending on your project structure, sometimes you'll end up with longer relative imports like this:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'import { Thing } from "../../../../../components/thing";\n')),(0,a.kt)("p",null,"We can utilize TypeScript's ",(0,a.kt)("inlineCode",{parentName:"p"},"baseUrl")," module to help resolve non-absolute module names. Doing so will allow us to change the above to:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'import { Thing } from "~/components/thing";\n')),(0,a.kt)("h2",{id:"project-dependencies"},"Project Dependencies"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"yarn add -D babel-plugin-root-import\n")),(0,a.kt)("h2",{id:"typescript-configuration-changes"},"TypeScript Configuration Changes"),(0,a.kt)("p",null,"Open ",(0,a.kt)("inlineCode",{parentName:"p"},"tsconfig.json")," and add the ",(0,a.kt)("inlineCode",{parentName:"p"},"baseUrl")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"path")," properties:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-json"},'{\n // ...\n "baseUrl": "./",\n // the following assumes Ignite\'s app/ structure, however yours may differ\n "paths": { "~/*": ["app/*"] }\n}\n')),(0,a.kt)("h2",{id:"babel-configuration-changes"},"Babel Configuration Changes"),(0,a.kt)("p",null,"Open ",(0,a.kt)("inlineCode",{parentName:"p"},"babel.config.js")," and add the following plugin array object:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'[\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')),(0,a.kt)("h2",{id:"taste-test-in-a-component"},"Taste Test in a Component!"),(0,a.kt)("p",null,"Open up ",(0,a.kt)("inlineCode",{parentName:"p"},"./app/screens/DemoShowroomScreen.tsx")," and let's update the relative imports from:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'import { ListItem, Screen, Text } from "../../components";\nimport { isRTL } from "../../i18n";\nimport { DemoTabScreenProps } from "../../navigators/DemoNavigator";\nimport { colors, spacing } from "../../theme";\n')),(0,a.kt)("p",null,"to"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'import { ListItem, Screen, Text } from "~/components";\nimport { isRTL } from "~/i18n";\nimport { DemoTabScreenProps } from "~/navigators/DemoNavigator";\nimport { colors, spacing } from "~/theme";\n')),(0,a.kt)("p",null,"Fire up the app and make sure everything still works!"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"yarn expo:start\n")),(0,a.kt)("p",null,(0,a.kt)("em",{parentName:"p"},"Note: if you receive an error about not being able to resolve the components, you may have to clear your bundler cache via")),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"yarn expo:start --clear\n")),(0,a.kt)("h2",{id:"resources"},"Resources"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://www.typescriptlang.org/tsconfig#baseUrl"},"TypeScript baseUrl Documentation"))))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[420],{3905:(e,t,r)=>{r.d(t,{Zo:()=>c,kt:()=>d});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function i(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var l=n.createContext({}),s=function(e){var t=n.useContext(l),r=t;return e&&(r="function"==typeof e?e(t):i(i({},t),e)),r},c=function(e){var t=s(e.components);return n.createElement(l.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},m=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,c=p(e,["components","mdxType","originalType","parentName"]),m=s(r),d=a,g=m["".concat(l,".").concat(d)]||m[d]||u[d]||o;return r?n.createElement(g,i(i({ref:t},c),{},{components:r})):n.createElement(g,i({ref:t},c))}));function d(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=r.length,i=new Array(o);i[0]=m;var p={};for(var l in t)hasOwnProperty.call(t,l)&&(p[l]=t[l]);p.originalType=e,p.mdxType="string"==typeof e?e:a,i[1]=p;for(var s=2;s{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>p,toc:()=>s});var n=r(7462),a=(r(7294),r(3905));const o={title:"TypeScript baseUrl Configuration",description:"How to configure TypeScript's baseUrl module for rewriting relative imports",tags:["TypeScript","Babel"],last_update:{author:"Frank Calise"},publish_date:new Date("2022-10-24T00:00:00.000Z")},i="TypeScript baseUrl Configuration",p={unversionedId:"recipes/TypeScriptBaseURL",id:"recipes/TypeScriptBaseURL",title:"TypeScript baseUrl Configuration",description:"How to configure TypeScript's baseUrl module for rewriting relative imports",source:"@site/docs/recipes/TypeScriptBaseURL.md",sourceDirName:"recipes",slug:"/recipes/TypeScriptBaseURL",permalink:"/docs/recipes/TypeScriptBaseURL",draft:!1,tags:[{label:"TypeScript",permalink:"/docs/tags/type-script"},{label:"Babel",permalink:"/docs/tags/babel"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"TypeScript baseUrl Configuration",description:"How to configure TypeScript's baseUrl module for rewriting relative imports",tags:["TypeScript","Babel"],last_update:{author:"Frank Calise"},publish_date:"2022-10-24T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Staying With Expo",permalink:"/docs/recipes/StayingWithExpo"},next:{title:"Scrolling to a location that hasn't been rendered using FlatList or SectionList",permalink:"/docs/recipes/UnrenderedItemInScrollView"}},l={},s=[{value:"Overview",id:"overview",level:2},{value:"Project Dependencies",id:"project-dependencies",level:2},{value:"TypeScript Configuration Changes",id:"typescript-configuration-changes",level:2},{value:"Babel Configuration Changes",id:"babel-configuration-changes",level:2},{value:"Taste Test in a Component!",id:"taste-test-in-a-component",level:2},{value:"Resources",id:"resources",level:2}],c={toc:s};function u(e){let{components:t,...r}=e;return(0,a.kt)("wrapper",(0,n.Z)({},c,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"typescript-baseurl-configuration"},"TypeScript baseUrl Configuration"),(0,a.kt)("h2",{id:"overview"},"Overview"),(0,a.kt)("p",null,"Depending on your project structure, sometimes you'll end up with longer relative imports like this:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'import { Thing } from "../../../../../components/thing";\n')),(0,a.kt)("p",null,"We can utilize TypeScript's ",(0,a.kt)("inlineCode",{parentName:"p"},"baseUrl")," module to help resolve non-absolute module names. Doing so will allow us to change the above to:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'import { Thing } from "~/components/thing";\n')),(0,a.kt)("h2",{id:"project-dependencies"},"Project Dependencies"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"yarn add -D babel-plugin-root-import\n")),(0,a.kt)("h2",{id:"typescript-configuration-changes"},"TypeScript Configuration Changes"),(0,a.kt)("p",null,"Open ",(0,a.kt)("inlineCode",{parentName:"p"},"tsconfig.json")," and add the ",(0,a.kt)("inlineCode",{parentName:"p"},"baseUrl")," and ",(0,a.kt)("inlineCode",{parentName:"p"},"path")," properties:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-json"},'{\n // ...\n "baseUrl": "./",\n // the following assumes Ignite\'s app/ structure, however yours may differ\n "paths": { "~/*": ["app/*"] }\n}\n')),(0,a.kt)("h2",{id:"babel-configuration-changes"},"Babel Configuration Changes"),(0,a.kt)("p",null,"Open ",(0,a.kt)("inlineCode",{parentName:"p"},"babel.config.js")," and add the following plugin array object:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'[\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')),(0,a.kt)("h2",{id:"taste-test-in-a-component"},"Taste Test in a Component!"),(0,a.kt)("p",null,"Open up ",(0,a.kt)("inlineCode",{parentName:"p"},"./app/screens/DemoShowroomScreen.tsx")," and let's update the relative imports from:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'import { ListItem, Screen, Text } from "../../components";\nimport { isRTL } from "../../i18n";\nimport { DemoTabScreenProps } from "../../navigators/DemoNavigator";\nimport { colors, spacing } from "../../theme";\n')),(0,a.kt)("p",null,"to"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'import { ListItem, Screen, Text } from "~/components";\nimport { isRTL } from "~/i18n";\nimport { DemoTabScreenProps } from "~/navigators/DemoNavigator";\nimport { colors, spacing } from "~/theme";\n')),(0,a.kt)("p",null,"Fire up the app and make sure everything still works!"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"yarn expo:start\n")),(0,a.kt)("p",null,(0,a.kt)("em",{parentName:"p"},"Note: if you receive an error about not being able to resolve the components, you may have to clear your bundler cache via")),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"yarn expo:start --clear\n")),(0,a.kt)("h2",{id:"resources"},"Resources"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},(0,a.kt)("a",{parentName:"li",href:"https://www.typescriptlang.org/tsconfig#baseUrl"},"TypeScript baseUrl Documentation"))))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/24c3776a.54a91fdf.js b/assets/js/24c3776a.c9485138.js similarity index 99% rename from assets/js/24c3776a.54a91fdf.js rename to assets/js/24c3776a.c9485138.js index 53723ff3..097c31e8 100644 --- a/assets/js/24c3776a.54a91fdf.js +++ b/assets/js/24c3776a.c9485138.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[7357],{3905:(e,t,n)=>{n.d(t,{Zo:()=>l,kt:()=>d});var o=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function r(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var u=o.createContext({}),c=function(e){var t=o.useContext(u),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},l=function(e){var t=c(e.components);return o.createElement(u.Provider,{value:t},e.children)},p={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},h=o.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,u=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),h=c(n),d=i,f=h["".concat(u,".").concat(d)]||h[d]||p[d]||a;return n?o.createElement(f,r(r({ref:t},l),{},{components:n})):o.createElement(f,r({ref:t},l))}));function d(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,r=new Array(a);r[0]=h;var s={};for(var u in t)hasOwnProperty.call(t,u)&&(s[u]=t[u]);s.originalType=e,s.mdxType="string"==typeof e?e:i,r[1]=s;for(var c=2;c{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>r,default:()=>p,frontMatter:()=>a,metadata:()=>s,toc:()=>c});var o=n(7462),i=(n(7294),n(3905));const a={title:"Distributing Auth Token to APIs",description:"Use token stored in Authentication Store with API Sauce",tags:["Authentication","Apisauce","Guide"],last_update:{author:"Ellie Croce"},publish_date:new Date("2023-05-09T00:00:00.000Z")},r="Distributing Auth Token to APISauce",s={unversionedId:"recipes/DistributingAuthTokenToAPI",id:"recipes/DistributingAuthTokenToAPI",title:"Distributing Auth Token to APIs",description:"Use token stored in Authentication Store with API Sauce",source:"@site/docs/recipes/DistributingAuthTokenToAPI.md",sourceDirName:"recipes",slug:"/recipes/DistributingAuthTokenToAPI",permalink:"/docs/recipes/DistributingAuthTokenToAPI",draft:!1,tags:[{label:"Authentication",permalink:"/docs/tags/authentication"},{label:"Apisauce",permalink:"/docs/tags/apisauce"},{label:"Guide",permalink:"/docs/tags/guide"}],version:"current",lastUpdatedBy:"Ellie Croce",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Distributing Auth Token to APIs",description:"Use token stored in Authentication Store with API Sauce",tags:["Authentication","Apisauce","Guide"],last_update:{author:"Ellie Croce"},publish_date:"2023-05-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Detox Intro",permalink:"/docs/recipes/DetoxIntro"},next:{title:"EAS Update",permalink:"/docs/recipes/EASUpdate"}},u={},c=[{value:"Review of API Instance and Auth Store",id:"review-of-api-instance-and-auth-store",level:2},{value:"Distributing Auth Token to API Instance",id:"distributing-auth-token-to-api-instance",level:2},{value:"Other configuration",id:"other-configuration",level:3}],l={toc:c};function p(e){let{components:t,...n}=e;return(0,i.kt)("wrapper",(0,o.Z)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"distributing-auth-token-to-apisauce"},"Distributing Auth Token to APISauce"),(0,i.kt)("p",null,"Building off of the Ignite Boilerplate, this recipe will show you how to connect your Mobx State Tree Authentication Store with an Apisauce instance to make authenticating your API requests a breeze."),(0,i.kt)("h2",{id:"review-of-api-instance-and-auth-store"},"Review of API Instance and Auth Store"),(0,i.kt)("p",null,"To start off let's quickly review the boilerplate Auth Store and API Instance."),(0,i.kt)("p",null,"In ",(0,i.kt)("inlineCode",{parentName:"p"},"app/services/api/api.ts")," we have an example API class with an export of a singleton instance of that class at the end of the file. This api singleton is what we're going to use to update the apisauce configuration on the instance outside of this file."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-tsx"},'export const DEFAULT_API_CONFIG: ApiConfig = {\n url: Config.API_URL,\n timeout: 10000,\n}\n\n\nexport class Api {\n apisauce: ApisauceInstance\n config: ApiConfig\n\n constructor(config: ApiConfig = DEFAULT_API_CONFIG) {\n this.config = config\n this.apisauce = create({\n baseURL: this.config.url,\n timeout: this.config.timeout,\n headers: {\n Accept: "application/json",\n },\n })\n }\n\n async getEpisodes(): Promise<{ kind: "ok"; episodes: EpisodeSnapshotIn[] } | GeneralApiProblem> {\n ... // example API call that needs authenticating\n }\n\n}\n\n// Singleton instance of the API for convenience\nexport const api = new Api()\n')),(0,i.kt)("p",null,"Next in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/models/AuthenticationStore.ts")," we have an action to set the auth token in the store after retrieving it."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-tsx"},'import { 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 .actions((store) => ({\n setAuthToken(value?: string) {\n store.authToken = value;\n },\n }));\n\nexport interface AuthenticationStore\n extends Instance {}\nexport interface AuthenticationStoreSnapshot\n extends SnapshotOut {}\n')),(0,i.kt)("h2",{id:"distributing-auth-token-to-api-instance"},"Distributing Auth Token to API Instance"),(0,i.kt)("p",null,"Once we have an auth token and setAuthToken has been called, we need to hydrate the authentication header on all APIs that require requests to be authenticated. One of the easiest ways to do that is to grab the instance(s) in an action on the authStore and update the apiSauce configuration directly like so:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-tsx"},' // We will need to import the api from the service\n import { api } from "app/services/api"\n\n ...\n\n // then update the Authentication Model actions\n .actions((store) => ({\n setAuthToken(value?: string) {\n store.authToken = value;\n },\n distributeAuthToken(value?: string) {\n // optionally grab the store\'s authToken if not passing a value\n const token = value || store.authToken;\n api.apiSauce.setHeader("Authorization", `Bearer ${token}`);\n },\n })\n')),(0,i.kt)("p",null,"From there you have a couple options of where to trigger the distribution."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Add another action that calls both setAuthToken and distributeAuthToken and replace wherever you call setAuthToken with this new dual action"),(0,i.kt)("li",{parentName:"ul"},"Call distributeAuthToken immediately after wherever you are calling setAuthToken"),(0,i.kt)("li",{parentName:"ul"},"Add the distributeAuthToken call to the setAuthToken action (not recommended because it doesn't follow the SRP of the action)")),(0,i.kt)("p",null,"For example in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/screens/LoginScreen.tsx")," we can update the login function to distribute the auth token after setting it in the store."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-tsx"},' // We can update `authenticationStore` from the useStores hook to include `distributeAuthToken`\n const {\n authenticationStore: { authEmail, setAuthEmail, setAuthToken, distributeAuthToken, validationError },\n } = useStores()\n\n ...\n\n function login() {\n setIsSubmitted(true);\n setAttemptsCount(attemptsCount + 1);\n\n if (validationError) return;\n\n // Make a request to your server to get an authentication token.\n // If successful, reset the fields and set the token.\n setIsSubmitted(false);\n setAuthPassword("");\n setAuthEmail("");\n\n // We\'ll mock this with a fake token.\n const token = String(Date.now());\n setAuthToken(token);\n distributeAuthToken(token);\n }\n')),(0,i.kt)("p",null,"And that's it! Now all of your requests made using that API will include the authentication header, leveraging the auth token received on login."),(0,i.kt)("h3",{id:"other-configuration"},"Other configuration"),(0,i.kt)("p",null,"This method works for updating both the apisauce and config properties on the default API instance or any custom properties you have added to the API!"))}p.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[7357],{3905:(e,t,n)=>{n.d(t,{Zo:()=>l,kt:()=>d});var o=n(7294);function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function r(e){for(var t=1;t=0||(i[n]=e[n]);return i}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var u=o.createContext({}),c=function(e){var t=o.useContext(u),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},l=function(e){var t=c(e.components);return o.createElement(u.Provider,{value:t},e.children)},p={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},h=o.forwardRef((function(e,t){var n=e.components,i=e.mdxType,a=e.originalType,u=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),h=c(n),d=i,f=h["".concat(u,".").concat(d)]||h[d]||p[d]||a;return n?o.createElement(f,r(r({ref:t},l),{},{components:n})):o.createElement(f,r({ref:t},l))}));function d(e,t){var n=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var a=n.length,r=new Array(a);r[0]=h;var s={};for(var u in t)hasOwnProperty.call(t,u)&&(s[u]=t[u]);s.originalType=e,s.mdxType="string"==typeof e?e:i,r[1]=s;for(var c=2;c{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>r,default:()=>p,frontMatter:()=>a,metadata:()=>s,toc:()=>c});var o=n(7462),i=(n(7294),n(3905));const a={title:"Distributing Auth Token to APIs",description:"Use token stored in Authentication Store with API Sauce",tags:["Authentication","Apisauce","Guide"],last_update:{author:"Ellie Croce"},publish_date:new Date("2023-05-09T00:00:00.000Z")},r="Distributing Auth Token to APISauce",s={unversionedId:"recipes/DistributingAuthTokenToAPI",id:"recipes/DistributingAuthTokenToAPI",title:"Distributing Auth Token to APIs",description:"Use token stored in Authentication Store with API Sauce",source:"@site/docs/recipes/DistributingAuthTokenToAPI.md",sourceDirName:"recipes",slug:"/recipes/DistributingAuthTokenToAPI",permalink:"/docs/recipes/DistributingAuthTokenToAPI",draft:!1,tags:[{label:"Authentication",permalink:"/docs/tags/authentication"},{label:"Apisauce",permalink:"/docs/tags/apisauce"},{label:"Guide",permalink:"/docs/tags/guide"}],version:"current",lastUpdatedBy:"Ellie Croce",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Distributing Auth Token to APIs",description:"Use token stored in Authentication Store with API Sauce",tags:["Authentication","Apisauce","Guide"],last_update:{author:"Ellie Croce"},publish_date:"2023-05-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Detox Intro",permalink:"/docs/recipes/DetoxIntro"},next:{title:"EAS Update",permalink:"/docs/recipes/EASUpdate"}},u={},c=[{value:"Review of API Instance and Auth Store",id:"review-of-api-instance-and-auth-store",level:2},{value:"Distributing Auth Token to API Instance",id:"distributing-auth-token-to-api-instance",level:2},{value:"Other configuration",id:"other-configuration",level:3}],l={toc:c};function p(e){let{components:t,...n}=e;return(0,i.kt)("wrapper",(0,o.Z)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"distributing-auth-token-to-apisauce"},"Distributing Auth Token to APISauce"),(0,i.kt)("p",null,"Building off of the Ignite Boilerplate, this recipe will show you how to connect your Mobx State Tree Authentication Store with an Apisauce instance to make authenticating your API requests a breeze."),(0,i.kt)("h2",{id:"review-of-api-instance-and-auth-store"},"Review of API Instance and Auth Store"),(0,i.kt)("p",null,"To start off let's quickly review the boilerplate Auth Store and API Instance."),(0,i.kt)("p",null,"In ",(0,i.kt)("inlineCode",{parentName:"p"},"app/services/api/api.ts")," we have an example API class with an export of a singleton instance of that class at the end of the file. This api singleton is what we're going to use to update the apisauce configuration on the instance outside of this file."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-tsx"},'export const DEFAULT_API_CONFIG: ApiConfig = {\n url: Config.API_URL,\n timeout: 10000,\n}\n\n\nexport class Api {\n apisauce: ApisauceInstance\n config: ApiConfig\n\n constructor(config: ApiConfig = DEFAULT_API_CONFIG) {\n this.config = config\n this.apisauce = create({\n baseURL: this.config.url,\n timeout: this.config.timeout,\n headers: {\n Accept: "application/json",\n },\n })\n }\n\n async getEpisodes(): Promise<{ kind: "ok"; episodes: EpisodeSnapshotIn[] } | GeneralApiProblem> {\n ... // example API call that needs authenticating\n }\n\n}\n\n// Singleton instance of the API for convenience\nexport const api = new Api()\n')),(0,i.kt)("p",null,"Next in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/models/AuthenticationStore.ts")," we have an action to set the auth token in the store after retrieving it."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-tsx"},'import { 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 .actions((store) => ({\n setAuthToken(value?: string) {\n store.authToken = value;\n },\n }));\n\nexport interface AuthenticationStore\n extends Instance {}\nexport interface AuthenticationStoreSnapshot\n extends SnapshotOut {}\n')),(0,i.kt)("h2",{id:"distributing-auth-token-to-api-instance"},"Distributing Auth Token to API Instance"),(0,i.kt)("p",null,"Once we have an auth token and setAuthToken has been called, we need to hydrate the authentication header on all APIs that require requests to be authenticated. One of the easiest ways to do that is to grab the instance(s) in an action on the authStore and update the apiSauce configuration directly like so:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-tsx"},' // We will need to import the api from the service\n import { api } from "app/services/api"\n\n ...\n\n // then update the Authentication Model actions\n .actions((store) => ({\n setAuthToken(value?: string) {\n store.authToken = value;\n },\n distributeAuthToken(value?: string) {\n // optionally grab the store\'s authToken if not passing a value\n const token = value || store.authToken;\n api.apiSauce.setHeader("Authorization", `Bearer ${token}`);\n },\n })\n')),(0,i.kt)("p",null,"From there you have a couple options of where to trigger the distribution."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Add another action that calls both setAuthToken and distributeAuthToken and replace wherever you call setAuthToken with this new dual action"),(0,i.kt)("li",{parentName:"ul"},"Call distributeAuthToken immediately after wherever you are calling setAuthToken"),(0,i.kt)("li",{parentName:"ul"},"Add the distributeAuthToken call to the setAuthToken action (not recommended because it doesn't follow the SRP of the action)")),(0,i.kt)("p",null,"For example in ",(0,i.kt)("inlineCode",{parentName:"p"},"app/screens/LoginScreen.tsx")," we can update the login function to distribute the auth token after setting it in the store."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-tsx"},' // We can update `authenticationStore` from the useStores hook to include `distributeAuthToken`\n const {\n authenticationStore: { authEmail, setAuthEmail, setAuthToken, distributeAuthToken, validationError },\n } = useStores()\n\n ...\n\n function login() {\n setIsSubmitted(true);\n setAttemptsCount(attemptsCount + 1);\n\n if (validationError) return;\n\n // Make a request to your server to get an authentication token.\n // If successful, reset the fields and set the token.\n setIsSubmitted(false);\n setAuthPassword("");\n setAuthEmail("");\n\n // We\'ll mock this with a fake token.\n const token = String(Date.now());\n setAuthToken(token);\n distributeAuthToken(token);\n }\n')),(0,i.kt)("p",null,"And that's it! Now all of your requests made using that API will include the authentication header, leveraging the auth token received on login."),(0,i.kt)("h3",{id:"other-configuration"},"Other configuration"),(0,i.kt)("p",null,"This method works for updating both the apisauce and config properties on the default API instance or any custom properties you have added to the API!"))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2b5cb6b8.119655b1.js b/assets/js/2b5cb6b8.119655b1.js deleted file mode 100644 index 91135fb7..00000000 --- a/assets/js/2b5cb6b8.119655b1.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[4598],{3191:i=>{i.exports=JSON.parse('{"label":"FlashList","permalink":"/docs/tags/flash-list","allTagsPath":"/docs/tags","count":1,"items":[{"id":"recipes/MigratingToFlashList","title":"Migrating to FlashList","description":"How to migrate over to Shopify\'s FlashList in an Ignite project","permalink":"/docs/recipes/MigratingToFlashList"}]}')}}]); \ No newline at end of file diff --git a/assets/js/4e09609f.466c19fa.js b/assets/js/4e09609f.3abca2f0.js similarity index 97% rename from assets/js/4e09609f.466c19fa.js rename to assets/js/4e09609f.3abca2f0.js index 6a24cfa6..5f6122c2 100644 --- a/assets/js/4e09609f.466c19fa.js +++ b/assets/js/4e09609f.3abca2f0.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[8154],{3905:(e,n,t)=>{t.d(n,{Zo:()=>p,kt:()=>m});var r=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function i(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var s=r.createContext({}),l=function(e){var n=r.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},p=function(e){var n=l(e.components);return r.createElement(s.Provider,{value:n},e.children)},u={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},d=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,p=c(e,["components","mdxType","originalType","parentName"]),d=l(t),m=a,f=d["".concat(s,".").concat(m)]||d[m]||u[m]||o;return t?r.createElement(f,i(i({ref:n},p),{},{components:t})):r.createElement(f,i({ref:n},p))}));function m(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,i=new Array(o);i[0]=d;var c={};for(var s in n)hasOwnProperty.call(n,s)&&(c[s]=n[s]);c.originalType=e,c.mdxType="string"==typeof e?e:a,i[1]=c;for(var l=2;l{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>c,toc:()=>l});var r=t(7462),a=(t(7294),t(3905));const o={title:"Sample YAML for CircleCi for Ignite",description:"A Copy/Paste Sample YAML for your Ignite Project",tags:["CI/CD","Guide"],last_update:{author:"Robin Heinze"},publish_date:new Date("2022-10-09T00:00:00.000Z")},i=void 0,c={unversionedId:"recipes/SampleYAMLCircleCI",id:"recipes/SampleYAMLCircleCI",title:"Sample YAML for CircleCi for Ignite",description:"A Copy/Paste Sample YAML for your Ignite Project",source:"@site/docs/recipes/SampleYAMLCircleCI.md",sourceDirName:"recipes",slug:"/recipes/SampleYAMLCircleCI",permalink:"/docs/recipes/SampleYAMLCircleCI",draft:!1,tags:[{label:"CI/CD",permalink:"/docs/tags/ci-cd"},{label:"Guide",permalink:"/docs/tags/guide"}],version:"current",lastUpdatedBy:"Robin Heinze",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Sample YAML for CircleCi for Ignite",description:"A Copy/Paste Sample YAML for your Ignite Project",tags:["CI/CD","Guide"],last_update:{author:"Robin Heinze"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Pristine Expo Project",permalink:"/docs/recipes/PristineExpoProject"},next:{title:"SelectField using `react-native-bottom-sheet`",permalink:"/docs/recipes/SelectFieldWithBottomSheet"}},s={},l=[{value:"Sampl YAML File",id:"sampl-yaml-file",level:3}],p={toc:l};function u(e){let{components:n,...t}=e;return(0,a.kt)("wrapper",(0,r.Z)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h3",{id:"sampl-yaml-file"},"Sampl YAML File"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},'# 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')))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[8154],{3905:(e,n,t)=>{t.d(n,{Zo:()=>p,kt:()=>m});var r=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function i(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var s=r.createContext({}),l=function(e){var n=r.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},p=function(e){var n=l(e.components);return r.createElement(s.Provider,{value:n},e.children)},u={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},d=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,p=c(e,["components","mdxType","originalType","parentName"]),d=l(t),m=a,f=d["".concat(s,".").concat(m)]||d[m]||u[m]||o;return t?r.createElement(f,i(i({ref:n},p),{},{components:t})):r.createElement(f,i({ref:n},p))}));function m(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,i=new Array(o);i[0]=d;var c={};for(var s in n)hasOwnProperty.call(n,s)&&(c[s]=n[s]);c.originalType=e,c.mdxType="string"==typeof e?e:a,i[1]=c;for(var l=2;l{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>c,toc:()=>l});var r=t(7462),a=(t(7294),t(3905));const o={title:"Sample YAML for CircleCi for Ignite",description:"A Copy/Paste Sample YAML for your Ignite Project",tags:["CI/CD","Guide"],last_update:{author:"Robin Heinze"},publish_date:new Date("2022-10-09T00:00:00.000Z")},i=void 0,c={unversionedId:"recipes/SampleYAMLCircleCI",id:"recipes/SampleYAMLCircleCI",title:"Sample YAML for CircleCi for Ignite",description:"A Copy/Paste Sample YAML for your Ignite Project",source:"@site/docs/recipes/SampleYAMLCircleCI.md",sourceDirName:"recipes",slug:"/recipes/SampleYAMLCircleCI",permalink:"/docs/recipes/SampleYAMLCircleCI",draft:!1,tags:[{label:"CI/CD",permalink:"/docs/tags/ci-cd"},{label:"Guide",permalink:"/docs/tags/guide"}],version:"current",lastUpdatedBy:"Robin Heinze",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Sample YAML for CircleCi for Ignite",description:"A Copy/Paste Sample YAML for your Ignite Project",tags:["CI/CD","Guide"],last_update:{author:"Robin Heinze"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Pristine Expo Project",permalink:"/docs/recipes/PristineExpoProject"},next:{title:"SelectField using `react-native-bottom-sheet`",permalink:"/docs/recipes/SelectFieldWithBottomSheet"}},s={},l=[{value:"Sampl YAML File",id:"sampl-yaml-file",level:3}],p={toc:l};function u(e){let{components:n,...t}=e;return(0,a.kt)("wrapper",(0,r.Z)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h3",{id:"sampl-yaml-file"},"Sampl YAML File"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-yaml"},'# 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')))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/501cbb42.42ad5ff9.js b/assets/js/501cbb42.42ad5ff9.js deleted file mode 100644 index e2641ec0..00000000 --- a/assets/js/501cbb42.42ad5ff9.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[4930],{9492:i=>{i.exports=JSON.parse('{"label":"Shopify","permalink":"/docs/tags/shopify","allTagsPath":"/docs/tags","count":1,"items":[{"id":"recipes/MigratingToFlashList","title":"Migrating to FlashList","description":"How to migrate over to Shopify\'s FlashList in an Ignite project","permalink":"/docs/recipes/MigratingToFlashList"}]}')}}]); \ No newline at end of file diff --git a/assets/js/51e76fb5.37ec6d59.js b/assets/js/51e76fb5.37ec6d59.js new file mode 100644 index 00000000..9b5b3cd4 --- /dev/null +++ b/assets/js/51e76fb5.37ec6d59.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[7940],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>h});var a=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function r(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,o=e.mdxType,i=e.originalType,l=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),d=p(n),h=o,m=d["".concat(l,".").concat(h)]||d[h]||u[h]||i;return n?a.createElement(m,r(r({ref:t},c),{},{components:n})):a.createElement(m,r({ref:t},c))}));function h(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=n.length,r=new Array(i);r[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:o,r[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>u,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var a=n(7462),o=(n(7294),n(3905));const i={title:"Maestro Setup",description:"Setting up e2e testing with Maestro in Ignite",tags:["Maestro","testing"],last_update:{author:"Dan Edwards"},publish_date:new Date("2023-02-01T00:00:00.000Z")},r="Setting Up Maestro in Ignite",s={unversionedId:"recipes/MaestroSetup",id:"recipes/MaestroSetup",title:"Maestro Setup",description:"Setting up e2e testing with Maestro in Ignite",source:"@site/docs/recipes/MaestroSetup.md",sourceDirName:"recipes",slug:"/recipes/MaestroSetup",permalink:"/docs/recipes/MaestroSetup",draft:!1,tags:[{label:"Maestro",permalink:"/docs/tags/maestro"},{label:"testing",permalink:"/docs/tags/testing"}],version:"current",lastUpdatedBy:"Dan Edwards",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Maestro Setup",description:"Setting up e2e testing with Maestro in Ignite",tags:["Maestro","testing"],last_update:{author:"Dan Edwards"},publish_date:"2023-02-01T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Generator for Component Tests",permalink:"/docs/recipes/GeneratorComponentTests"},next:{title:"Migrating to MMKV",permalink:"/docs/recipes/MigratingToMMKV"}},l={},p=[{value:"Overview",id:"overview",level:2},{value:"Maestro Installation",id:"maestro-installation",level:2},{value:"Creating our first Maestro Flow",id:"creating-our-first-maestro-flow",level:2},{value:"Let's see what else Maestro can do",id:"lets-see-what-else-maestro-can-do",level:2},{value:"Conclusion",id:"conclusion",level:2}],c={toc:p};function u(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,a.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-maestro-in-ignite"},"Setting Up Maestro in Ignite"),(0,o.kt)("h2",{id:"overview"},"Overview"),(0,o.kt)("p",null,"End-to-end (e2e) testing is a critical part of any application but it can be difficult to set up and maintain. ",(0,o.kt)("a",{parentName:"p",href:"https://maestro.mobile.dev/"},"Maestro")," is a tool that promises to be easy to set up and maintain e2e tests. This recipe will walk you through setting up Maestro in your Ignite project."),(0,o.kt)("h2",{id:"maestro-installation"},"Maestro Installation"),(0,o.kt)("p",null,"We're going to start by installing Maestro via the terminal. To do this, we'll need to run the following command:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},'curl -Ls "https://get.maestro.mobile.dev" | bash\n')),(0,o.kt)("p",null,"If you haven't already, you'll also need to install Facebook's IDB Companion tool:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"brew tap facebook/fb\nbrew install idb-companion\n")),(0,o.kt)("p",null,"If you run into any issues, check out the ",(0,o.kt)("a",{parentName:"p",href:"https://maestro.mobile.dev/getting-started/installing-maestro#installing-the-cli"},"Maestro cli installation guide")," for more information."),(0,o.kt)("p",null,"Once the installation is complete, you'll be ready to create your first Maestro flow!"),(0,o.kt)("h2",{id:"creating-our-first-maestro-flow"},"Creating our first Maestro Flow"),(0,o.kt)("p",null,"To start out, we're going to create a folder to hold our Maestro flows. Let's do this by adding a folder in the root of our Ignite project called ",(0,o.kt)("inlineCode",{parentName:"p"},".maestro"),". Once that's done we can create our first flow in a file called ",(0,o.kt)("inlineCode",{parentName:"p"},"Login.yaml")),(0,o.kt)("p",null,"With this flow we want to open up our app and then login with the default credentials. We can do this by adding the following to our ",(0,o.kt)("inlineCode",{parentName:"p"},"Login.yaml")," file:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-yaml"},'#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')),(0,o.kt)("p",null,"We're using a few different actions and assertions in this flow. Let's take a look at what they do:"),(0,o.kt)("p",null,(0,o.kt)("inlineCode",{parentName:"p"},"clearState")," - This action clears the state of our app. This is useful if we want to start from a clean slate.\n",(0,o.kt)("inlineCode",{parentName:"p"},"launchApp")," - This action launches our app specified with the ",(0,o.kt)("inlineCode",{parentName:"p"},"appId")," in our flow.\n",(0,o.kt)("inlineCode",{parentName:"p"},"assertVisible")," - This assertion checks to see if the text we pass in is visible on the screen.\n",(0,o.kt)("inlineCode",{parentName:"p"},"tapOn")," - This action taps on the specified element. In our case, we're tapping on the text we pass in."),(0,o.kt)("p",null,"To run our flow, first make sure the app is loaded on the simulator (or running via metro through ",(0,o.kt)("inlineCode",{parentName:"p"},"yarn ios"),", for example). Then execute maestro from its test directory with the following command:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"cd .maestro\nmaestro test Login.yaml\n")),(0,o.kt)("p",null,"And that's it! We've successfully created our first Maestro flow and ran it. You should see something like this in your terminal after running the test:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},' \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')),(0,o.kt)("p",null,"Let's add another flow to see what else we can do with Maestro!"),(0,o.kt)("h2",{id:"lets-see-what-else-maestro-can-do"},"Let's see what else Maestro can do"),(0,o.kt)("p",null,"Let's create a more advanded flow that spans across multiple screens, we'll want to accomplish the following:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Use environment variables"),(0,o.kt)("li",{parentName:"ol"},"Run the login flow"),(0,o.kt)("li",{parentName:"ol"},"Navigate to the demo podcast list screen"),(0,o.kt)("li",{parentName:"ol"},"Favorite a podcast"),(0,o.kt)("li",{parentName:"ol"},"Switch the list to only be favorites"),(0,o.kt)("li",{parentName:"ol"},"Use accessibility labels to find elements")),(0,o.kt)("p",null,"Go ahead and create a flow called ",(0,o.kt)("inlineCode",{parentName:"p"},"FavoritePodcast.yaml")," and add the following to it:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-yaml"},'# 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')),(0,o.kt)("p",null,"We did a few things new here. Let's take a look at what they are:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"We added an ",(0,o.kt)("inlineCode",{parentName:"li"},"env")," section to our flow. This allows us to set environment variables that we can use in our flow. In this case, we're setting the title of the podcast we want to favorite and the favorites toggle label text."),(0,o.kt)("li",{parentName:"ol"},"We added a ",(0,o.kt)("inlineCode",{parentName:"li"},"runFlow")," action. This action allows us to run another flow from within our flow. In this case, we're running the ",(0,o.kt)("inlineCode",{parentName:"li"},"Login.yaml")," flow before we run the rest of our flow."),(0,o.kt)("li",{parentName:"ol"},"We added a ",(0,o.kt)("inlineCode",{parentName:"li"},"scrollUntilVisible")," action. This will help us find the episode we are looking for because it won't always be available in the first visible content as new episodes are released. This action is also used to scroll back up to toggle the ",(0,o.kt)("inlineCode",{parentName:"li"},"Only Show Favorites")," switch."),(0,o.kt)("li",{parentName:"ol"},"We added a ",(0,o.kt)("inlineCode",{parentName:"li"},"longPressOn")," action. This action allows us to long press on an element. In this case, we're long pressing on the podcast we want to favorite, we're able to do this because of the accessability action that's associated with the Podcast Card."),(0,o.kt)("li",{parentName:"ol"},'The text "Switch on to only show favorites" (or env var ',(0,o.kt)("inlineCode",{parentName:"li"},"${FAVOREITES_TEXT}"),") is the accessibility label passed to the Toggle component. Maestro identifies accessibility labels as text as long as that element does not have any text children.")),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},"If you're running these tests on an iOS simulator, you may need to add ",(0,o.kt)("inlineCode",{parentName:"p"},"accessibilityLabel: episode.title,")," to line 180 of ",(0,o.kt)("inlineCode",{parentName:"p"},"DemoPodcastListScreen.tsx")," in your Ignite project.")),(0,o.kt)("h2",{id:"conclusion"},"Conclusion"),(0,o.kt)("p",null,"Maestro is a great tool for e2e testing. It's easy to set up and maintain. It's also easy to add to your Ignite project. If you want to check out how to use their other features, like Maestro cloud & Maestro Studio, check out their ",(0,o.kt)("a",{parentName:"p",href:"https://maestro.mobile.dev/"},"documentation"),"."),(0,o.kt)("blockquote",null,(0,o.kt)("h2",{parentName:"blockquote",id:"notes"},"Notes"),(0,o.kt)("p",{parentName:"blockquote"},"Detox is the default e2e testing tool in Ignite. Should you choose to use Maestro, remove Detox from your project.")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/51e76fb5.ef2bc3a3.js b/assets/js/51e76fb5.ef2bc3a3.js deleted file mode 100644 index f0a54fcb..00000000 --- a/assets/js/51e76fb5.ef2bc3a3.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[7940],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>h});var a=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function r(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,o=e.mdxType,i=e.originalType,l=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),d=p(n),h=o,m=d["".concat(l,".").concat(h)]||d[h]||u[h]||i;return n?a.createElement(m,r(r({ref:t},c),{},{components:n})):a.createElement(m,r({ref:t},c))}));function h(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=n.length,r=new Array(i);r[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:o,r[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>u,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var a=n(7462),o=(n(7294),n(3905));const i={title:"Maestro Setup",description:"Setting up e2e testing with Maestro in Ignite",tags:["Maestro","testing"],last_update:{author:"Dan Edwards"},publish_date:new Date("2023-02-01T00:00:00.000Z")},r="Setting Up Maestro in Ignite",s={unversionedId:"recipes/MaestroSetup",id:"recipes/MaestroSetup",title:"Maestro Setup",description:"Setting up e2e testing with Maestro in Ignite",source:"@site/docs/recipes/MaestroSetup.md",sourceDirName:"recipes",slug:"/recipes/MaestroSetup",permalink:"/docs/recipes/MaestroSetup",draft:!1,tags:[{label:"Maestro",permalink:"/docs/tags/maestro"},{label:"testing",permalink:"/docs/tags/testing"}],version:"current",lastUpdatedBy:"Dan Edwards",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Maestro Setup",description:"Setting up e2e testing with Maestro in Ignite",tags:["Maestro","testing"],last_update:{author:"Dan Edwards"},publish_date:"2023-02-01T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Generator for Component Tests",permalink:"/docs/recipes/GeneratorComponentTests"},next:{title:"Migrating to FlashList",permalink:"/docs/recipes/MigratingToFlashList"}},l={},p=[{value:"Overview",id:"overview",level:2},{value:"Maestro Installation",id:"maestro-installation",level:2},{value:"Creating our first Maestro Flow",id:"creating-our-first-maestro-flow",level:2},{value:"Let's see what else Maestro can do",id:"lets-see-what-else-maestro-can-do",level:2},{value:"Conclusion",id:"conclusion",level:2}],c={toc:p};function u(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,a.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-maestro-in-ignite"},"Setting Up Maestro in Ignite"),(0,o.kt)("h2",{id:"overview"},"Overview"),(0,o.kt)("p",null,"End-to-end (e2e) testing is a critical part of any application but it can be difficult to set up and maintain. ",(0,o.kt)("a",{parentName:"p",href:"https://maestro.mobile.dev/"},"Maestro")," is a tool that promises to be easy to set up and maintain e2e tests. This recipe will walk you through setting up Maestro in your Ignite project."),(0,o.kt)("h2",{id:"maestro-installation"},"Maestro Installation"),(0,o.kt)("p",null,"We're going to start by installing Maestro via the terminal. To do this, we'll need to run the following command:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},'curl -Ls "https://get.maestro.mobile.dev" | bash\n')),(0,o.kt)("p",null,"If you haven't already, you'll also need to install Facebook's IDB Companion tool:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"brew tap facebook/fb\nbrew install idb-companion\n")),(0,o.kt)("p",null,"If you run into any issues, check out the ",(0,o.kt)("a",{parentName:"p",href:"https://maestro.mobile.dev/getting-started/installing-maestro#installing-the-cli"},"Maestro cli installation guide")," for more information."),(0,o.kt)("p",null,"Once the installation is complete, you'll be ready to create your first Maestro flow!"),(0,o.kt)("h2",{id:"creating-our-first-maestro-flow"},"Creating our first Maestro Flow"),(0,o.kt)("p",null,"To start out, we're going to create a folder to hold our Maestro flows. Let's do this by adding a folder in the root of our Ignite project called ",(0,o.kt)("inlineCode",{parentName:"p"},".maestro"),". Once that's done we can create our first flow in a file called ",(0,o.kt)("inlineCode",{parentName:"p"},"Login.yaml")),(0,o.kt)("p",null,"With this flow we want to open up our app and then login with the default credentials. We can do this by adding the following to our ",(0,o.kt)("inlineCode",{parentName:"p"},"Login.yaml")," file:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-yaml"},'#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')),(0,o.kt)("p",null,"We're using a few different actions and assertions in this flow. Let's take a look at what they do:"),(0,o.kt)("p",null,(0,o.kt)("inlineCode",{parentName:"p"},"clearState")," - This action clears the state of our app. This is useful if we want to start from a clean slate.\n",(0,o.kt)("inlineCode",{parentName:"p"},"launchApp")," - This action launches our app specified with the ",(0,o.kt)("inlineCode",{parentName:"p"},"appId")," in our flow.\n",(0,o.kt)("inlineCode",{parentName:"p"},"assertVisible")," - This assertion checks to see if the text we pass in is visible on the screen.\n",(0,o.kt)("inlineCode",{parentName:"p"},"tapOn")," - This action taps on the specified element. In our case, we're tapping on the text we pass in."),(0,o.kt)("p",null,"To run our flow, first make sure the app is loaded on the simulator (or running via metro through ",(0,o.kt)("inlineCode",{parentName:"p"},"yarn ios"),", for example). Then execute maestro from its test directory with the following command:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"cd .maestro\nmaestro test Login.yaml\n")),(0,o.kt)("p",null,"And that's it! We've successfully created our first Maestro flow and ran it. You should see something like this in your terminal after running the test:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},' \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')),(0,o.kt)("p",null,"Let's add another flow to see what else we can do with Maestro!"),(0,o.kt)("h2",{id:"lets-see-what-else-maestro-can-do"},"Let's see what else Maestro can do"),(0,o.kt)("p",null,"Let's create a more advanded flow that spans across multiple screens, we'll want to accomplish the following:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Use environment variables"),(0,o.kt)("li",{parentName:"ol"},"Run the login flow"),(0,o.kt)("li",{parentName:"ol"},"Navigate to the demo podcast list screen"),(0,o.kt)("li",{parentName:"ol"},"Favorite a podcast"),(0,o.kt)("li",{parentName:"ol"},"Switch the list to only be favorites"),(0,o.kt)("li",{parentName:"ol"},"Use accessibility labels to find elements")),(0,o.kt)("p",null,"Go ahead and create a flow called ",(0,o.kt)("inlineCode",{parentName:"p"},"FavoritePodcast.yaml")," and add the following to it:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-yaml"},'# 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')),(0,o.kt)("p",null,"We did a few things new here. Let's take a look at what they are:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"We added an ",(0,o.kt)("inlineCode",{parentName:"li"},"env")," section to our flow. This allows us to set environment variables that we can use in our flow. In this case, we're setting the title of the podcast we want to favorite and the favorites toggle label text."),(0,o.kt)("li",{parentName:"ol"},"We added a ",(0,o.kt)("inlineCode",{parentName:"li"},"runFlow")," action. This action allows us to run another flow from within our flow. In this case, we're running the ",(0,o.kt)("inlineCode",{parentName:"li"},"Login.yaml")," flow before we run the rest of our flow."),(0,o.kt)("li",{parentName:"ol"},"We added a ",(0,o.kt)("inlineCode",{parentName:"li"},"scrollUntilVisible")," action. This will help us find the episode we are looking for because it won't always be available in the first visible content as new episodes are released. This action is also used to scroll back up to toggle the ",(0,o.kt)("inlineCode",{parentName:"li"},"Only Show Favorites")," switch."),(0,o.kt)("li",{parentName:"ol"},"We added a ",(0,o.kt)("inlineCode",{parentName:"li"},"longPressOn")," action. This action allows us to long press on an element. In this case, we're long pressing on the podcast we want to favorite, we're able to do this because of the accessability action that's associated with the Podcast Card."),(0,o.kt)("li",{parentName:"ol"},'The text "Switch on to only show favorites" (or env var ',(0,o.kt)("inlineCode",{parentName:"li"},"${FAVOREITES_TEXT}"),") is the accessibility label passed to the Toggle component. Maestro identifies accessibility labels as text as long as that element does not have any text children.")),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},"If you're running these tests on an iOS simulator, you may need to add ",(0,o.kt)("inlineCode",{parentName:"p"},"accessibilityLabel: episode.title,")," to line 180 of ",(0,o.kt)("inlineCode",{parentName:"p"},"DemoPodcastListScreen.tsx")," in your Ignite project.")),(0,o.kt)("h2",{id:"conclusion"},"Conclusion"),(0,o.kt)("p",null,"Maestro is a great tool for e2e testing. It's easy to set up and maintain. It's also easy to add to your Ignite project. If you want to check out how to use their other features, like Maestro cloud & Maestro Studio, check out their ",(0,o.kt)("a",{parentName:"p",href:"https://maestro.mobile.dev/"},"documentation"),"."),(0,o.kt)("blockquote",null,(0,o.kt)("h2",{parentName:"blockquote",id:"notes"},"Notes"),(0,o.kt)("p",{parentName:"blockquote"},"Detox is the default e2e testing tool in Ignite. Should you choose to use Maestro, remove Detox from your project.")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/55960ee5.be230c73.js b/assets/js/55960ee5.be230c73.js deleted file mode 100644 index 7eec5728..00000000 --- a/assets/js/55960ee5.be230c73.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[4121],{8070:e=>{e.exports=JSON.parse('[{"label":"Intro","permalink":"/docs/tags/intro","count":2},{"label":"Accessibility","permalink":"/docs/tags/accessibility","count":3},{"label":"Guide","permalink":"/docs/tags/guide","count":6},{"label":"CI/CD","permalink":"/docs/tags/ci-cd","count":2},{"label":"iOS","permalink":"/docs/tags/i-os","count":2},{"label":"Android","permalink":"/docs/tags/android","count":3},{"label":"Testing","permalink":"/docs/tags/testing","count":2},{"label":"Authentication","permalink":"/docs/tags/authentication","count":1},{"label":"Apisauce","permalink":"/docs/tags/apisauce","count":1},{"label":"Expo","permalink":"/docs/tags/expo","count":3},{"label":"expo-updates","permalink":"/docs/tags/expo-updates","count":1},{"label":"EAS Update","permalink":"/docs/tags/eas-update","count":1},{"label":"Environment Variables","permalink":"/docs/tags/environment-variables","count":1},{"label":"Generator","permalink":"/docs/tags/generator","count":1},{"label":"Maestro","permalink":"/docs/tags/maestro","count":1},{"label":"Shopify","permalink":"/docs/tags/shopify","count":1},{"label":"FlashList","permalink":"/docs/tags/flash-list","count":1},{"label":"MMKV","permalink":"/docs/tags/mmkv","count":1},{"label":"AsyncStorage","permalink":"/docs/tags/async-storage","count":1},{"label":"Debug","permalink":"/docs/tags/debug","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":"expo-dev-client","permalink":"/docs/tags/expo-dev-client","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":"Yarn","permalink":"/docs/tags/yarn","count":1},{"label":"Dependencies","permalink":"/docs/tags/dependencies","count":1}]')}}]); \ No newline at end of file diff --git a/assets/js/55960ee5.dfa63dbd.js b/assets/js/55960ee5.dfa63dbd.js new file mode 100644 index 00000000..419c0fcb --- /dev/null +++ b/assets/js/55960ee5.dfa63dbd.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[4121],{8070:e=>{e.exports=JSON.parse('[{"label":"Intro","permalink":"/docs/tags/intro","count":2},{"label":"Accessibility","permalink":"/docs/tags/accessibility","count":3},{"label":"Guide","permalink":"/docs/tags/guide","count":6},{"label":"CI/CD","permalink":"/docs/tags/ci-cd","count":2},{"label":"iOS","permalink":"/docs/tags/i-os","count":2},{"label":"Android","permalink":"/docs/tags/android","count":3},{"label":"Testing","permalink":"/docs/tags/testing","count":2},{"label":"Authentication","permalink":"/docs/tags/authentication","count":1},{"label":"Apisauce","permalink":"/docs/tags/apisauce","count":1},{"label":"Expo","permalink":"/docs/tags/expo","count":3},{"label":"expo-updates","permalink":"/docs/tags/expo-updates","count":1},{"label":"EAS Update","permalink":"/docs/tags/eas-update","count":1},{"label":"Environment Variables","permalink":"/docs/tags/environment-variables","count":1},{"label":"Generator","permalink":"/docs/tags/generator","count":1},{"label":"Maestro","permalink":"/docs/tags/maestro","count":1},{"label":"MMKV","permalink":"/docs/tags/mmkv","count":1},{"label":"AsyncStorage","permalink":"/docs/tags/async-storage","count":1},{"label":"Debug","permalink":"/docs/tags/debug","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":"expo-dev-client","permalink":"/docs/tags/expo-dev-client","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":"Yarn","permalink":"/docs/tags/yarn","count":1},{"label":"Dependencies","permalink":"/docs/tags/dependencies","count":1}]')}}]); \ No newline at end of file diff --git a/assets/js/63181745.37a9fbcc.js b/assets/js/63181745.56a659f6.js similarity index 98% rename from assets/js/63181745.37a9fbcc.js rename to assets/js/63181745.56a659f6.js index 1e7efa08..54c80aff 100644 --- a/assets/js/63181745.37a9fbcc.js +++ b/assets/js/63181745.56a659f6.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[3441],{3905:(e,t,a)=>{a.d(t,{Zo:()=>p,kt:()=>h});var n=a(7294);function i(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function r(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(i[a]=e[a]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(i[a]=e[a])}return i}var l=n.createContext({}),c=function(e){var t=n.useContext(l),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},p=function(e){var t=c(e.components);return n.createElement(l.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var a=e.components,i=e.mdxType,r=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),d=c(a),h=i,m=d["".concat(l,".").concat(h)]||d[h]||u[h]||r;return a?n.createElement(m,o(o({ref:t},p),{},{components:a})):n.createElement(m,o({ref:t},p))}));function h(e,t){var a=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=a.length,o=new Array(r);o[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:i,o[1]=s;for(var c=2;c{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var n=a(7462),i=(a(7294),a(3905));const r={title:"Using Screen Readers",description:"Learn how to use a screen reader to improve accesibility!",tags:["Accessibility","iOS","Android"],last_update:{author:"Lizzi Lindboe"},publish_date:new Date("2022-10-09T00:00:00.000Z")},o="Using Screen Readers",s={unversionedId:"recipes/UsingScreenReaders",id:"recipes/UsingScreenReaders",title:"Using Screen Readers",description:"Learn how to use a screen reader to improve accesibility!",source:"@site/docs/recipes/UsingScreenReaders.md",sourceDirName:"recipes",slug:"/recipes/UsingScreenReaders",permalink:"/docs/recipes/UsingScreenReaders",draft:!1,tags:[{label:"Accessibility",permalink:"/docs/tags/accessibility"},{label:"iOS",permalink:"/docs/tags/i-os"},{label:"Android",permalink:"/docs/tags/android"}],version:"current",lastUpdatedBy:"Lizzi Lindboe",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Using Screen Readers",description:"Learn how to use a screen reader to improve accesibility!",tags:["Accessibility","iOS","Android"],last_update:{author:"Lizzi Lindboe"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Updating Dependencies with Yarn Audit, Outdated and Upgrade",permalink:"/docs/recipes/UpdatingDependencies"}},l={},c=[{value:"iOS",id:"ios",level:2},{value:"On a simulator",id:"on-a-simulator",level:3},{value:"On a device",id:"on-a-device",level:3},{value:"Android",id:"android",level:2},{value:"Devices with TalkBack installed",id:"devices-with-talkback-installed",level:3},{value:"Devices with Voice Assistant and not TalkBack",id:"devices-with-voice-assistant-and-not-talkback",level:3}],p={toc:c};function u(e){let{components:t,...a}=e;return(0,i.kt)("wrapper",(0,n.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"using-screen-readers"},"Using Screen Readers"),(0,i.kt)("h2",{id:"ios"},"iOS"),(0,i.kt)("h3",{id:"on-a-simulator"},"On a simulator"),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Setting it up"),"\nSimulators don't have VoiceOver, so you'll have to use the Accessibility Inspector."),(0,i.kt)("p",null,"From XCode, go to Xcode menu > Developer Tools > Accessibility Inspector, and make sure to change the laptop icon in the top left of the inspector window to the simulator instead."),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Operation"),"\nFor operation, the WWDC video on the inspector is more helpful than the documentation: ",(0,i.kt)("a",{parentName:"p",href:"https://developer.apple.com/videos/play/wwdc2019/257/"},"https://developer.apple.com/videos/play/wwdc2019/257/"),"."),(0,i.kt)("h3",{id:"on-a-device"},"On a device"),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Setting it up"),"\nGo to Settings > Accessibility > VoiceOver and toggle VoiceOver on.\nYou can also toggle VoiceOver a few other ways:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://support.apple.com/guide/iphone/aside/summon_siri/15.0/ios/15.0"},"Activate Siri"),' and ssay "Turn on VoiceOver" or "Turn off VoiceOver."'),(0,i.kt)("li",{parentName:"ul"},"Set up an accessibility shortcut to open VoiceOver when you:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://support.apple.com/guide/iphone/accessibility-shortcuts-iph3e2e31a5/15.0/ios/15.0#iph3ce566f26"},"Triple-click the side button")," (on an iPhone with Face ID)."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://support.apple.com/guide/iphone/accessibility-shortcuts-iph3e2e31a5/15.0/ios/15.0#iphe66e6ee36"},"Triple-click the Home button")," (on an iPhone with a Home button).")))),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Operation"),"\nBasic navigation for beginners:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Tap items on the screen to hear them read aloud or interact with them"),(0,i.kt)("li",{parentName:"ul"},"To activate items you have selected, double tap quickly anywhere on the screen"),(0,i.kt)("li",{parentName:"ul"},"To scroll, select an item within the scrollable area and use a three-finger swipe")),(0,i.kt)("p",null,"Other useful navigation tips:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"To hear the screen read aloud from the beginning, swipe upward with two fingers"),(0,i.kt)("li",{parentName:"ul"},'If you hear "Actions Available," you can access extra actions using the Rotor.',(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},'Make sure the rotor is set to "Actions" by rotating two fingers in a circle, as if turning a dial'),(0,i.kt)("li",{parentName:"ul"},"To navigate through the list of actions, single-finger swipe up or down, and double-tap to use"))),(0,i.kt)("li",{parentName:"ul"},"Gestures documented here:\n",(0,i.kt)("a",{parentName:"li",href:"https://support.apple.com/guide/iphone/learn-voiceover-gestures-iph3e2e2281/ios"},"https://support.apple.com/guide/iphone/learn-voiceover-gestures-iph3e2e2281/ios")," - Helpful cheatsheet:\n",(0,i.kt)("a",{parentName:"li",href:"https://dequeuniversity.com/screenreaders/voiceover-ios-shortcuts"},"https://dequeuniversity.com/screenreaders/voiceover-ios-shortcuts"))),(0,i.kt)("h2",{id:"android"},"Android"),(0,i.kt)("h3",{id:"devices-with-talkback-installed"},"Devices with TalkBack installed"),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Setup"),"\nGo to Settings > Accessibility > TalkBack and toggle it on."),(0,i.kt)("p",null,"You can also:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://support.google.com/accessibility/android/answer/7650693"},"Set up a shortcut")," to toggle TalkBack by pressing both volume keys for 3 seconds"),(0,i.kt)("li",{parentName:"ul"},"Configure through adb:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-bash"},"# 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")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Operation"),"\nBasic navigation:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},'TalkBack follows a "drag your finger to explore the screen" paradigm. As you drag your finger around, you\'ll hear items read out. Pause over items you want to hear.'),(0,i.kt)("li",{parentName:"ul"},"You can also swipe left and right with one finger to explore items linearly."),(0,i.kt)("li",{parentName:"ul"},"Double-tap to activate an element."),(0,i.kt)("li",{parentName:"ul"},"Use two-finger swipes to scroll or pull down the notification shade."),(0,i.kt)("li",{parentName:"ul"},'Typing: By default, TalkBack tries to follow the "drag your finger to explore the screen" pattern on the keyboard too. Drag your finger over the keys. Whichever you hovered on last and then lifted is typed.',(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},'This does not appear to always work with the default Samsung keyboard. You can install "GBoard" from the Google Play store and use that instead.')))),(0,i.kt)("p",null,"The rest:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"The TalkBack UI can vary depending on the device you're on. Reference these articles as you're learning:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"Using the TalkBack menu and reading controls:\n",(0,i.kt)("a",{parentName:"li",href:"https://support.google.com/accessibility/android/answer/6007066?hl=en&ref_topic=10601570"},"https://support.google.com/accessibility/android/answer/6007066?hl=en&ref_topic=10601570")),(0,i.kt)("li",{parentName:"ul"},"Gestures:\n",(0,i.kt)("a",{parentName:"li",href:"https://support.google.com/accessibility/android/answer/6151827"},"https://support.google.com/accessibility/android/answer/6151827")))),(0,i.kt)("li",{parentName:"ul"},"Access custom actions by opening the TalkBack menu (depending on the device, either a three-finger tap, or, in one motion, swipe down then right).")),(0,i.kt)("h3",{id:"devices-with-voice-assistant-and-not-talkback"},"Devices with Voice Assistant and not TalkBack"),(0,i.kt)("p",null,"(e.g., Galaxy S models less than 20)"),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Setup"),"\nTODO: Voice Assistant instructions"),(0,i.kt)("p",null,'You can also probably install TalkBack on your device. Install "Android Accessibility Suite" from the Play store, and then enable it from Settings > Accessibility > Installed Accessibility Services. Note that if you have multiple profiles on this device, this service must be installed on the primary (personal) profile.'),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Operation"),"\nCheatsheet:\n",(0,i.kt)("a",{parentName:"p",href:"https://dequeuniversity.com/screenreaders/talkback-shortcuts"},"https://dequeuniversity.com/screenreaders/talkback-shortcuts")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[3441],{3905:(e,t,a)=>{a.d(t,{Zo:()=>p,kt:()=>h});var n=a(7294);function i(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function r(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(i[a]=e[a]);return i}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(i[a]=e[a])}return i}var l=n.createContext({}),c=function(e){var t=n.useContext(l),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},p=function(e){var t=c(e.components);return n.createElement(l.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var a=e.components,i=e.mdxType,r=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),d=c(a),h=i,m=d["".concat(l,".").concat(h)]||d[h]||u[h]||r;return a?n.createElement(m,o(o({ref:t},p),{},{components:a})):n.createElement(m,o({ref:t},p))}));function h(e,t){var a=arguments,i=t&&t.mdxType;if("string"==typeof e||i){var r=a.length,o=new Array(r);o[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:i,o[1]=s;for(var c=2;c{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>r,metadata:()=>s,toc:()=>c});var n=a(7462),i=(a(7294),a(3905));const r={title:"Using Screen Readers",description:"Learn how to use a screen reader to improve accesibility!",tags:["Accessibility","iOS","Android"],last_update:{author:"Lizzi Lindboe"},publish_date:new Date("2022-10-09T00:00:00.000Z")},o="Using Screen Readers",s={unversionedId:"recipes/UsingScreenReaders",id:"recipes/UsingScreenReaders",title:"Using Screen Readers",description:"Learn how to use a screen reader to improve accesibility!",source:"@site/docs/recipes/UsingScreenReaders.md",sourceDirName:"recipes",slug:"/recipes/UsingScreenReaders",permalink:"/docs/recipes/UsingScreenReaders",draft:!1,tags:[{label:"Accessibility",permalink:"/docs/tags/accessibility"},{label:"iOS",permalink:"/docs/tags/i-os"},{label:"Android",permalink:"/docs/tags/android"}],version:"current",lastUpdatedBy:"Lizzi Lindboe",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Using Screen Readers",description:"Learn how to use a screen reader to improve accesibility!",tags:["Accessibility","iOS","Android"],last_update:{author:"Lizzi Lindboe"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Updating Dependencies with Yarn Audit, Outdated and Upgrade",permalink:"/docs/recipes/UpdatingDependencies"}},l={},c=[{value:"iOS",id:"ios",level:2},{value:"On a simulator",id:"on-a-simulator",level:3},{value:"On a device",id:"on-a-device",level:3},{value:"Android",id:"android",level:2},{value:"Devices with TalkBack installed",id:"devices-with-talkback-installed",level:3},{value:"Devices with Voice Assistant and not TalkBack",id:"devices-with-voice-assistant-and-not-talkback",level:3}],p={toc:c};function u(e){let{components:t,...a}=e;return(0,i.kt)("wrapper",(0,n.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"using-screen-readers"},"Using Screen Readers"),(0,i.kt)("h2",{id:"ios"},"iOS"),(0,i.kt)("h3",{id:"on-a-simulator"},"On a simulator"),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Setting it up"),"\nSimulators don't have VoiceOver, so you'll have to use the Accessibility Inspector."),(0,i.kt)("p",null,"From XCode, go to Xcode menu > Developer Tools > Accessibility Inspector, and make sure to change the laptop icon in the top left of the inspector window to the simulator instead."),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Operation"),"\nFor operation, the WWDC video on the inspector is more helpful than the documentation: ",(0,i.kt)("a",{parentName:"p",href:"https://developer.apple.com/videos/play/wwdc2019/257/"},"https://developer.apple.com/videos/play/wwdc2019/257/"),"."),(0,i.kt)("h3",{id:"on-a-device"},"On a device"),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Setting it up"),"\nGo to Settings > Accessibility > VoiceOver and toggle VoiceOver on.\nYou can also toggle VoiceOver a few other ways:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://support.apple.com/guide/iphone/aside/summon_siri/15.0/ios/15.0"},"Activate Siri"),' and ssay "Turn on VoiceOver" or "Turn off VoiceOver."'),(0,i.kt)("li",{parentName:"ul"},"Set up an accessibility shortcut to open VoiceOver when you:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://support.apple.com/guide/iphone/accessibility-shortcuts-iph3e2e31a5/15.0/ios/15.0#iph3ce566f26"},"Triple-click the side button")," (on an iPhone with Face ID)."),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://support.apple.com/guide/iphone/accessibility-shortcuts-iph3e2e31a5/15.0/ios/15.0#iphe66e6ee36"},"Triple-click the Home button")," (on an iPhone with a Home button).")))),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Operation"),"\nBasic navigation for beginners:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Tap items on the screen to hear them read aloud or interact with them"),(0,i.kt)("li",{parentName:"ul"},"To activate items you have selected, double tap quickly anywhere on the screen"),(0,i.kt)("li",{parentName:"ul"},"To scroll, select an item within the scrollable area and use a three-finger swipe")),(0,i.kt)("p",null,"Other useful navigation tips:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"To hear the screen read aloud from the beginning, swipe upward with two fingers"),(0,i.kt)("li",{parentName:"ul"},'If you hear "Actions Available," you can access extra actions using the Rotor.',(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},'Make sure the rotor is set to "Actions" by rotating two fingers in a circle, as if turning a dial'),(0,i.kt)("li",{parentName:"ul"},"To navigate through the list of actions, single-finger swipe up or down, and double-tap to use"))),(0,i.kt)("li",{parentName:"ul"},"Gestures documented here:\n",(0,i.kt)("a",{parentName:"li",href:"https://support.apple.com/guide/iphone/learn-voiceover-gestures-iph3e2e2281/ios"},"https://support.apple.com/guide/iphone/learn-voiceover-gestures-iph3e2e2281/ios")," - Helpful cheatsheet:\n",(0,i.kt)("a",{parentName:"li",href:"https://dequeuniversity.com/screenreaders/voiceover-ios-shortcuts"},"https://dequeuniversity.com/screenreaders/voiceover-ios-shortcuts"))),(0,i.kt)("h2",{id:"android"},"Android"),(0,i.kt)("h3",{id:"devices-with-talkback-installed"},"Devices with TalkBack installed"),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Setup"),"\nGo to Settings > Accessibility > TalkBack and toggle it on."),(0,i.kt)("p",null,"You can also:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://support.google.com/accessibility/android/answer/7650693"},"Set up a shortcut")," to toggle TalkBack by pressing both volume keys for 3 seconds"),(0,i.kt)("li",{parentName:"ul"},"Configure through adb:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-bash"},"# 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")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Operation"),"\nBasic navigation:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},'TalkBack follows a "drag your finger to explore the screen" paradigm. As you drag your finger around, you\'ll hear items read out. Pause over items you want to hear.'),(0,i.kt)("li",{parentName:"ul"},"You can also swipe left and right with one finger to explore items linearly."),(0,i.kt)("li",{parentName:"ul"},"Double-tap to activate an element."),(0,i.kt)("li",{parentName:"ul"},"Use two-finger swipes to scroll or pull down the notification shade."),(0,i.kt)("li",{parentName:"ul"},'Typing: By default, TalkBack tries to follow the "drag your finger to explore the screen" pattern on the keyboard too. Drag your finger over the keys. Whichever you hovered on last and then lifted is typed.',(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},'This does not appear to always work with the default Samsung keyboard. You can install "GBoard" from the Google Play store and use that instead.')))),(0,i.kt)("p",null,"The rest:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"The TalkBack UI can vary depending on the device you're on. Reference these articles as you're learning:",(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"Using the TalkBack menu and reading controls:\n",(0,i.kt)("a",{parentName:"li",href:"https://support.google.com/accessibility/android/answer/6007066?hl=en&ref_topic=10601570"},"https://support.google.com/accessibility/android/answer/6007066?hl=en&ref_topic=10601570")),(0,i.kt)("li",{parentName:"ul"},"Gestures:\n",(0,i.kt)("a",{parentName:"li",href:"https://support.google.com/accessibility/android/answer/6151827"},"https://support.google.com/accessibility/android/answer/6151827")))),(0,i.kt)("li",{parentName:"ul"},"Access custom actions by opening the TalkBack menu (depending on the device, either a three-finger tap, or, in one motion, swipe down then right).")),(0,i.kt)("h3",{id:"devices-with-voice-assistant-and-not-talkback"},"Devices with Voice Assistant and not TalkBack"),(0,i.kt)("p",null,"(e.g., Galaxy S models less than 20)"),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Setup"),"\nTODO: Voice Assistant instructions"),(0,i.kt)("p",null,'You can also probably install TalkBack on your device. Install "Android Accessibility Suite" from the Play store, and then enable it from Settings > Accessibility > Installed Accessibility Services. Note that if you have multiple profiles on this device, this service must be installed on the primary (personal) profile.'),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Operation"),"\nCheatsheet:\n",(0,i.kt)("a",{parentName:"p",href:"https://dequeuniversity.com/screenreaders/talkback-shortcuts"},"https://dequeuniversity.com/screenreaders/talkback-shortcuts")))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/657027a7.64b4ba19.js b/assets/js/657027a7.64b4ba19.js deleted file mode 100644 index a4f98a0c..00000000 --- a/assets/js/657027a7.64b4ba19.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[5893],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>m});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=r.createContext({}),c=function(e){var t=r.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},p=function(e){var t=c(e.components);return r.createElement(l.Provider,{value:t},e.children)},g={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},u=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),u=c(n),m=a,d=u["".concat(l,".").concat(m)]||u[m]||g[m]||o;return n?r.createElement(d,i(i({ref:t},p),{},{components:n})):r.createElement(d,i({ref:t},p))}));function m(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,i=new Array(o);i[0]=u;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:a,i[1]=s;for(var c=2;c{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>g,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var r=n(7462),a=(n(7294),n(3905));const o={title:"Migrating to MMKV",description:"How to migrate from React Native's AsyncStorage to MMKV",tags:["MMKV","AsyncStorage"],last_update:{author:"Frank Calise"},publish_date:new Date("2022-10-28T00:00:00.000Z")},i="Migrating to MMKV",s={unversionedId:"recipes/MigratingToMMKV",id:"recipes/MigratingToMMKV",title:"Migrating to MMKV",description:"How to migrate from React Native's AsyncStorage to MMKV",source:"@site/docs/recipes/MigratingToMMKV.md",sourceDirName:"recipes",slug:"/recipes/MigratingToMMKV",permalink:"/docs/recipes/MigratingToMMKV",draft:!1,tags:[{label:"MMKV",permalink:"/docs/tags/mmkv"},{label:"AsyncStorage",permalink:"/docs/tags/async-storage"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Migrating to MMKV",description:"How to migrate from React Native's AsyncStorage to MMKV",tags:["MMKV","AsyncStorage"],last_update:{author:"Frank Calise"},publish_date:"2022-10-28T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Migrating to FlashList",permalink:"/docs/recipes/MigratingToFlashList"},next:{title:"Patching/Building Android .aar From Source",permalink:"/docs/recipes/PatchingBuildingAndroid"}},l={},c=[{value:"Overview",id:"overview",level:2},{value:"Project Dependencies",id:"project-dependencies",level:2},{value:"Code Changes",id:"code-changes",level:2}],p={toc:c};function g(e){let{components:t,...n}=e;return(0,a.kt)("wrapper",(0,r.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"migrating-to-mmkv"},"Migrating to MMKV"),(0,a.kt)("h2",{id:"overview"},"Overview"),(0,a.kt)("p",null,(0,a.kt)("a",{parentName:"p",href:"https://github.com/mrousavy/react-native-mmkv"},"MMKV")," is said to be the fastest key/value storage for React Native. It has encryption support for secure local storage and also uses synchronous storage to simplify your application code."),(0,a.kt)("p",null,"In this recipe, we'll convert our the Ignite demo project from using ",(0,a.kt)("inlineCode",{parentName:"p"},"AsyncStorage")," to ",(0,a.kt)("inlineCode",{parentName:"p"},"MMKV"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-nodejs"},"npx ignite-cli new PizzaApp --yes\ncd PizzaApp\n")),(0,a.kt)("h2",{id:"project-dependencies"},"Project Dependencies"),(0,a.kt)("p",null,"Install the ",(0,a.kt)("inlineCode",{parentName:"p"},"react-native-mmkv")," dependency into the project"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-nodejs"},"yarn add react-native-mmkv\n")),(0,a.kt)("p",null,(0,a.kt)("em",{parentName:"p"},"Note: No ",(0,a.kt)("inlineCode",{parentName:"em"},"pod install")," was run here because the scripts set up in an Ignite project take care of that for you!")),(0,a.kt)("h2",{id:"code-changes"},"Code Changes"),(0,a.kt)("p",null,"Open ",(0,a.kt)("inlineCode",{parentName:"p"},"app/utils/storage.tsx")," and modify the imports:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'// error-line\nimport AsyncStorage from "@react-native-async-storage/async-storage";\nimport { MMKV } from "react-native-mmkv";\nconst storage = new MMKV();\n')),(0,a.kt)("p",null,"Now we'll remove any reference to ",(0,a.kt)("inlineCode",{parentName:"p"},"AsyncStorage")," and replace it with the proper API from ",(0,a.kt)("inlineCode",{parentName:"p"},"MMKV")),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},"/**\n * Loads a string from storage.\n *\n * @param key The key to fetch.\n */\n// error-line\nexport async function loadString(key: string): Promise {\nexport function loadString(key: string): string | null {\n try {\n // error-line\n return await AsyncStorage.getItem(key)\n return storage.getString(key);\n } catch {\n // not sure why this would fail... even reading the RN docs I'm unclear\n return null;\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 */\n// error-line\nexport async function saveString(key: string, value: string): Promise {\nexport function saveString(key: string, value: string): boolean {\n try {\n // error-line\n await AsyncStorage.setItem(key, value)\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 */\n// error-line\nexport async function load(key: string): Promise {\nexport function load(key: string): any | null {\n try {\n // error-line\n const almostThere = await AsyncStorage.getItem(key)\n const almostThere = storage.getString(key);\n return JSON.parse(almostThere);\n } catch {\n return null;\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 */\n// error-line\nexport async function save(key: string, value: any): Promise {\nexport function save(key: string, value: any): boolean {\n try {\n // error-line\n await AsyncStorage.setItem(key, JSON.stringify(value))\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 */\n// error-line\nexport async function remove(key: string): Promise {\nexport function remove(key: string): void {\n try {\n // error-line\n await AsyncStorage.removeItem(key)\n storage.delete(key);\n } catch {}\n}\n\n/**\n * Burn it all to the ground.\n */\n// error-line\nexport async function clear(): Promise {\nexport function clear(): void {\n try {\n // error-line\n await AsyncStorage.clear()\n storage.clearAll();\n } catch {}\n}\n")),(0,a.kt)("p",null,"Run the app in the iOS simulator to test the changes with ",(0,a.kt)("inlineCode",{parentName:"p"},"yarn ios"),". Navigate to the Podcast List screen:"),(0,a.kt)("ol",null,(0,a.kt)("li",{parentName:"ol"},'Press "Tap to sign in!"'),(0,a.kt)("li",{parentName:"ol"},'Press "Let\'s go!"'),(0,a.kt)("li",{parentName:"ol"},'Tap on the "Podcast"')),(0,a.kt)("p",null,"Now let's swipe the app away to close it. Re-open the app to see if the navigation picks up where we left off (which shows our storage is working to remember the navigation key we were last on)."),(0,a.kt)("p",null,"And that's it! Ignite is now configured with ",(0,a.kt)("inlineCode",{parentName:"p"},"react-native-mmkv")," over ",(0,a.kt)("inlineCode",{parentName:"p"},"AsyncStorage"),"."))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/657027a7.e13c45f1.js b/assets/js/657027a7.e13c45f1.js new file mode 100644 index 00000000..ec42c00f --- /dev/null +++ b/assets/js/657027a7.e13c45f1.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[5893],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>m});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var l=r.createContext({}),c=function(e){var t=r.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},p=function(e){var t=c(e.components);return r.createElement(l.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},g=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),g=c(n),m=a,d=g["".concat(l,".").concat(m)]||g[m]||u[m]||o;return n?r.createElement(d,i(i({ref:t},p),{},{components:n})):r.createElement(d,i({ref:t},p))}));function m(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,i=new Array(o);i[0]=g;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:a,i[1]=s;for(var c=2;c{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>s,toc:()=>c});var r=n(7462),a=(n(7294),n(3905));const o={title:"Migrating to MMKV",description:"How to migrate from React Native's AsyncStorage to MMKV",tags:["MMKV","AsyncStorage"],last_update:{author:"Frank Calise"},publish_date:new Date("2022-10-28T00:00:00.000Z")},i="Migrating to MMKV",s={unversionedId:"recipes/MigratingToMMKV",id:"recipes/MigratingToMMKV",title:"Migrating to MMKV",description:"How to migrate from React Native's AsyncStorage to MMKV",source:"@site/docs/recipes/MigratingToMMKV.md",sourceDirName:"recipes",slug:"/recipes/MigratingToMMKV",permalink:"/docs/recipes/MigratingToMMKV",draft:!1,tags:[{label:"MMKV",permalink:"/docs/tags/mmkv"},{label:"AsyncStorage",permalink:"/docs/tags/async-storage"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Migrating to MMKV",description:"How to migrate from React Native's AsyncStorage to MMKV",tags:["MMKV","AsyncStorage"],last_update:{author:"Frank Calise"},publish_date:"2022-10-28T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Maestro Setup",permalink:"/docs/recipes/MaestroSetup"},next:{title:"Patching/Building Android .aar From Source",permalink:"/docs/recipes/PatchingBuildingAndroid"}},l={},c=[{value:"Overview",id:"overview",level:2},{value:"Project Dependencies",id:"project-dependencies",level:2},{value:"Code Changes",id:"code-changes",level:2}],p={toc:c};function u(e){let{components:t,...n}=e;return(0,a.kt)("wrapper",(0,r.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"migrating-to-mmkv"},"Migrating to MMKV"),(0,a.kt)("h2",{id:"overview"},"Overview"),(0,a.kt)("p",null,(0,a.kt)("a",{parentName:"p",href:"https://github.com/mrousavy/react-native-mmkv"},"MMKV")," is said to be the fastest key/value storage for React Native. It has encryption support for secure local storage and also uses synchronous storage to simplify your application code."),(0,a.kt)("p",null,"In this recipe, we'll convert our the Ignite demo project from using ",(0,a.kt)("inlineCode",{parentName:"p"},"AsyncStorage")," to ",(0,a.kt)("inlineCode",{parentName:"p"},"MMKV"),":"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-nodejs"},"npx ignite-cli new PizzaApp --yes\ncd PizzaApp\n")),(0,a.kt)("h2",{id:"project-dependencies"},"Project Dependencies"),(0,a.kt)("p",null,"Install the ",(0,a.kt)("inlineCode",{parentName:"p"},"react-native-mmkv")," dependency into the project"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-nodejs"},"yarn add react-native-mmkv\n")),(0,a.kt)("p",null,(0,a.kt)("em",{parentName:"p"},"Note: No ",(0,a.kt)("inlineCode",{parentName:"em"},"pod install")," was run here because the scripts set up in an Ignite project take care of that for you!")),(0,a.kt)("h2",{id:"code-changes"},"Code Changes"),(0,a.kt)("p",null,"Open ",(0,a.kt)("inlineCode",{parentName:"p"},"app/utils/storage.tsx")," and modify the imports:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},'// error-line\nimport AsyncStorage from "@react-native-async-storage/async-storage";\nimport { MMKV } from "react-native-mmkv";\nconst storage = new MMKV();\n')),(0,a.kt)("p",null,"Now we'll remove any reference to ",(0,a.kt)("inlineCode",{parentName:"p"},"AsyncStorage")," and replace it with the proper API from ",(0,a.kt)("inlineCode",{parentName:"p"},"MMKV")),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-tsx"},"/**\n * Loads a string from storage.\n *\n * @param key The key to fetch.\n */\n// error-line\nexport async function loadString(key: string): Promise {\nexport function loadString(key: string): string | null {\n try {\n // error-line\n return await AsyncStorage.getItem(key)\n return storage.getString(key);\n } catch {\n // not sure why this would fail... even reading the RN docs I'm unclear\n return null;\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 */\n// error-line\nexport async function saveString(key: string, value: string): Promise {\nexport function saveString(key: string, value: string): boolean {\n try {\n // error-line\n await AsyncStorage.setItem(key, value)\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 */\n// error-line\nexport async function load(key: string): Promise {\nexport function load(key: string): any | null {\n try {\n // error-line\n const almostThere = await AsyncStorage.getItem(key)\n const almostThere = storage.getString(key);\n return JSON.parse(almostThere);\n } catch {\n return null;\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 */\n// error-line\nexport async function save(key: string, value: any): Promise {\nexport function save(key: string, value: any): boolean {\n try {\n // error-line\n await AsyncStorage.setItem(key, JSON.stringify(value))\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 */\n// error-line\nexport async function remove(key: string): Promise {\nexport function remove(key: string): void {\n try {\n // error-line\n await AsyncStorage.removeItem(key)\n storage.delete(key);\n } catch {}\n}\n\n/**\n * Burn it all to the ground.\n */\n// error-line\nexport async function clear(): Promise {\nexport function clear(): void {\n try {\n // error-line\n await AsyncStorage.clear()\n storage.clearAll();\n } catch {}\n}\n")),(0,a.kt)("p",null,"Run the app in the iOS simulator to test the changes with ",(0,a.kt)("inlineCode",{parentName:"p"},"yarn ios"),". Navigate to the Podcast List screen:"),(0,a.kt)("ol",null,(0,a.kt)("li",{parentName:"ol"},'Press "Tap to sign in!"'),(0,a.kt)("li",{parentName:"ol"},'Press "Let\'s go!"'),(0,a.kt)("li",{parentName:"ol"},'Tap on the "Podcast"')),(0,a.kt)("p",null,"Now let's swipe the app away to close it. Re-open the app to see if the navigation picks up where we left off (which shows our storage is working to remember the navigation key we were last on)."),(0,a.kt)("p",null,"And that's it! Ignite is now configured with ",(0,a.kt)("inlineCode",{parentName:"p"},"react-native-mmkv")," over ",(0,a.kt)("inlineCode",{parentName:"p"},"AsyncStorage"),"."))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7b45617e.9dd15233.js b/assets/js/7b45617e.c3b5a82d.js similarity index 99% rename from assets/js/7b45617e.9dd15233.js rename to assets/js/7b45617e.c3b5a82d.js index 56e3a487..580c14f7 100644 --- a/assets/js/7b45617e.9dd15233.js +++ b/assets/js/7b45617e.c3b5a82d.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[1833],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>d});var r=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=r.createContext({}),l=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},c=function(e){var t=l(e.components);return r.createElement(s.Provider,{value:t},e.children)},m={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},u=r.forwardRef((function(e,t){var n=e.components,o=e.mdxType,a=e.originalType,s=e.parentName,c=p(e,["components","mdxType","originalType","parentName"]),u=l(n),d=o,g=u["".concat(s,".").concat(d)]||u[d]||m[d]||a;return n?r.createElement(g,i(i({ref:t},c),{},{components:n})):r.createElement(g,i({ref:t},c))}));function d(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var a=n.length,i=new Array(a);i[0]=u;var p={};for(var s in t)hasOwnProperty.call(t,s)&&(p[s]=t[s]);p.originalType=e,p.mdxType="string"==typeof e?e:o,i[1]=p;for(var l=2;l{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>m,frontMatter:()=>a,metadata:()=>p,toc:()=>l});var r=n(7462),o=(n(7294),n(3905));const a={title:"Generator for Component Tests",description:"Customize `npx ignite-cli generate component` to add test files for each component generated",tags:["Generator"],last_update:{author:"Joshua Yoes"},publish_date:new Date("2022-10-10T00:00:00.000Z")},i="Add component tests to `npx ignite-cli generate component`",p={unversionedId:"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",source:"@site/docs/recipes/GeneratorComponentTests.md",sourceDirName:"recipes",slug:"/recipes/GeneratorComponentTests",permalink:"/docs/recipes/GeneratorComponentTests",draft:!1,tags:[{label:"Generator",permalink:"/docs/tags/generator"}],version:"current",lastUpdatedBy:"Joshua Yoes",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Generator for Component Tests",description:"Customize `npx ignite-cli generate component` to add test files for each component generated",tags:["Generator"],last_update:{author:"Joshua Yoes"},publish_date:"2022-10-10T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Environment Variables",permalink:"/docs/recipes/EnvironmentVariables"},next:{title:"Maestro Setup",permalink:"/docs/recipes/MaestroSetup"}},s={},l=[{value:"Setup @testing-library/react-native",id:"setup-testing-libraryreact-native",level:2},{value:"Component Generators",id:"component-generators",level:2},{value:"Customizing Component Generators",id:"customizing-component-generators",level:2},{value:"Testing",id:"testing",level:2}],c={toc:l};function m(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,r.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"add-component-tests-to-npx-ignite-cli-generate-component"},"Add component tests to ",(0,o.kt)("inlineCode",{parentName:"h1"},"npx ignite-cli generate component")),(0,o.kt)("p",null,"Did you know that Ignite automatically generates files for you? And that you can customize those generators?"),(0,o.kt)("p",null,"Here is how to automatically generate components and tests for them using ",(0,o.kt)("inlineCode",{parentName:"p"},"@testing-library/react-native")),(0,o.kt)("h2",{id:"setup-testing-libraryreact-native"},"Setup ",(0,o.kt)("inlineCode",{parentName:"h2"},"@testing-library/react-native")),(0,o.kt)("p",null,"First, we will want to add ",(0,o.kt)("inlineCode",{parentName:"p"},"@testing-library/react-native")," to our Ignite project. ",(0,o.kt)("a",{parentName:"p",href:"https://reactnativetesting.io/component/setup"},"Josh Justice has an excellent setup guide to follow along at reactnativetesting.io")),(0,o.kt)("h2",{id:"component-generators"},"Component Generators"),(0,o.kt)("p",null,"There are a variety of generators, but today we are going to focus on ",(0,o.kt)("inlineCode",{parentName:"p"},"npx ignite-cli generate component")),(0,o.kt)("p",null,"Ignite will look in the ",(0,o.kt)("inlineCode",{parentName:"p"},"ignite/templates/*")," directory for what templates to run on each command."),(0,o.kt)("p",null,"By default, Ignite provides a ",(0,o.kt)("inlineCode",{parentName:"p"},"ignite/templates/component/NAME.tsx.ejs")," template for creating a component file."),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"We use ejs and frontmatter to write our templates, you can read more about syntax ",(0,o.kt)("a",{parentName:"li",href:"https://github.com/infinitered/ignite/blob/master/docs/Generator-Templates.md"},"in the Ignite docs"),"."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"NAME")," in ",(0,o.kt)("inlineCode",{parentName:"li"},"ignite/templates/component/NAME.tsx.ejs")," is replaced with the first argument passed to our generator. So ",(0,o.kt)("inlineCode",{parentName:"li"},"npx ignite-cli generate component Profile")," would create ",(0,o.kt)("inlineCode",{parentName:"li"},"app/components/Profile.tsx"))),(0,o.kt)("h2",{id:"customizing-component-generators"},"Customizing Component Generators"),(0,o.kt)("p",null,"Add the following file to ",(0,o.kt)("inlineCode",{parentName:"p"},"ignite/templates/component/NAME.spec.tsx.ejs")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-ejs"},'---\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')),(0,o.kt)("p",null,"Now, when we run ",(0,o.kt)("inlineCode",{parentName:"p"},"npx ignite-cli generate component Profile"),", it will create both ",(0,o.kt)("inlineCode",{parentName:"p"},"app/components/Profile.tsx")," ",(0,o.kt)("em",{parentName:"p"},"and")," ",(0,o.kt)("inlineCode",{parentName:"p"},"app/components/specs/Profile.spec.tsx")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"ignite/templates/component/NAME.spec.tsx.ejs")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"ignite/templates/component/NAME.tsx.ejs"))),(0,o.kt)("h2",{id:"testing"},"Testing"),(0,o.kt)("p",null,"Now, all we need to do is run ",(0,o.kt)("inlineCode",{parentName:"p"},"yarn test"),"! If everything was set up properly, you should have a new suite of passing tests!"))}m.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[1833],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>d});var r=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function a(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=r.createContext({}),l=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},c=function(e){var t=l(e.components);return r.createElement(s.Provider,{value:t},e.children)},m={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},u=r.forwardRef((function(e,t){var n=e.components,o=e.mdxType,a=e.originalType,s=e.parentName,c=p(e,["components","mdxType","originalType","parentName"]),u=l(n),d=o,g=u["".concat(s,".").concat(d)]||u[d]||m[d]||a;return n?r.createElement(g,i(i({ref:t},c),{},{components:n})):r.createElement(g,i({ref:t},c))}));function d(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var a=n.length,i=new Array(a);i[0]=u;var p={};for(var s in t)hasOwnProperty.call(t,s)&&(p[s]=t[s]);p.originalType=e,p.mdxType="string"==typeof e?e:o,i[1]=p;for(var l=2;l{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>m,frontMatter:()=>a,metadata:()=>p,toc:()=>l});var r=n(7462),o=(n(7294),n(3905));const a={title:"Generator for Component Tests",description:"Customize `npx ignite-cli generate component` to add test files for each component generated",tags:["Generator"],last_update:{author:"Joshua Yoes"},publish_date:new Date("2022-10-10T00:00:00.000Z")},i="Add component tests to `npx ignite-cli generate component`",p={unversionedId:"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",source:"@site/docs/recipes/GeneratorComponentTests.md",sourceDirName:"recipes",slug:"/recipes/GeneratorComponentTests",permalink:"/docs/recipes/GeneratorComponentTests",draft:!1,tags:[{label:"Generator",permalink:"/docs/tags/generator"}],version:"current",lastUpdatedBy:"Joshua Yoes",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Generator for Component Tests",description:"Customize `npx ignite-cli generate component` to add test files for each component generated",tags:["Generator"],last_update:{author:"Joshua Yoes"},publish_date:"2022-10-10T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Environment Variables",permalink:"/docs/recipes/EnvironmentVariables"},next:{title:"Maestro Setup",permalink:"/docs/recipes/MaestroSetup"}},s={},l=[{value:"Setup @testing-library/react-native",id:"setup-testing-libraryreact-native",level:2},{value:"Component Generators",id:"component-generators",level:2},{value:"Customizing Component Generators",id:"customizing-component-generators",level:2},{value:"Testing",id:"testing",level:2}],c={toc:l};function m(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,r.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"add-component-tests-to-npx-ignite-cli-generate-component"},"Add component tests to ",(0,o.kt)("inlineCode",{parentName:"h1"},"npx ignite-cli generate component")),(0,o.kt)("p",null,"Did you know that Ignite automatically generates files for you? And that you can customize those generators?"),(0,o.kt)("p",null,"Here is how to automatically generate components and tests for them using ",(0,o.kt)("inlineCode",{parentName:"p"},"@testing-library/react-native")),(0,o.kt)("h2",{id:"setup-testing-libraryreact-native"},"Setup ",(0,o.kt)("inlineCode",{parentName:"h2"},"@testing-library/react-native")),(0,o.kt)("p",null,"First, we will want to add ",(0,o.kt)("inlineCode",{parentName:"p"},"@testing-library/react-native")," to our Ignite project. ",(0,o.kt)("a",{parentName:"p",href:"https://reactnativetesting.io/component/setup"},"Josh Justice has an excellent setup guide to follow along at reactnativetesting.io")),(0,o.kt)("h2",{id:"component-generators"},"Component Generators"),(0,o.kt)("p",null,"There are a variety of generators, but today we are going to focus on ",(0,o.kt)("inlineCode",{parentName:"p"},"npx ignite-cli generate component")),(0,o.kt)("p",null,"Ignite will look in the ",(0,o.kt)("inlineCode",{parentName:"p"},"ignite/templates/*")," directory for what templates to run on each command."),(0,o.kt)("p",null,"By default, Ignite provides a ",(0,o.kt)("inlineCode",{parentName:"p"},"ignite/templates/component/NAME.tsx.ejs")," template for creating a component file."),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"We use ejs and frontmatter to write our templates, you can read more about syntax ",(0,o.kt)("a",{parentName:"li",href:"https://github.com/infinitered/ignite/blob/master/docs/Generator-Templates.md"},"in the Ignite docs"),"."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"NAME")," in ",(0,o.kt)("inlineCode",{parentName:"li"},"ignite/templates/component/NAME.tsx.ejs")," is replaced with the first argument passed to our generator. So ",(0,o.kt)("inlineCode",{parentName:"li"},"npx ignite-cli generate component Profile")," would create ",(0,o.kt)("inlineCode",{parentName:"li"},"app/components/Profile.tsx"))),(0,o.kt)("h2",{id:"customizing-component-generators"},"Customizing Component Generators"),(0,o.kt)("p",null,"Add the following file to ",(0,o.kt)("inlineCode",{parentName:"p"},"ignite/templates/component/NAME.spec.tsx.ejs")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-ejs"},'---\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')),(0,o.kt)("p",null,"Now, when we run ",(0,o.kt)("inlineCode",{parentName:"p"},"npx ignite-cli generate component Profile"),", it will create both ",(0,o.kt)("inlineCode",{parentName:"p"},"app/components/Profile.tsx")," ",(0,o.kt)("em",{parentName:"p"},"and")," ",(0,o.kt)("inlineCode",{parentName:"p"},"app/components/specs/Profile.spec.tsx")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"ignite/templates/component/NAME.spec.tsx.ejs")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"ignite/templates/component/NAME.tsx.ejs"))),(0,o.kt)("h2",{id:"testing"},"Testing"),(0,o.kt)("p",null,"Now, all we need to do is run ",(0,o.kt)("inlineCode",{parentName:"p"},"yarn test"),"! If everything was set up properly, you should have a new suite of passing tests!"))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/861d4b97.1b41b340.js b/assets/js/861d4b97.687c90f2.js similarity index 99% rename from assets/js/861d4b97.1b41b340.js rename to assets/js/861d4b97.687c90f2.js index 15628d6f..fe589b10 100644 --- a/assets/js/861d4b97.1b41b340.js +++ b/assets/js/861d4b97.687c90f2.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[4011],{3905:(e,t,n)=>{n.d(t,{Zo:()=>l,kt:()=>m});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),c=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},l=function(e){var t=c(e.components);return r.createElement(s.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},u=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,l=p(e,["components","mdxType","originalType","parentName"]),u=c(n),m=a,g=u["".concat(s,".").concat(m)]||u[m]||d[m]||o;return n?r.createElement(g,i(i({ref:t},l),{},{components:n})):r.createElement(g,i({ref:t},l))}));function m(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,i=new Array(o);i[0]=u;var p={};for(var s in t)hasOwnProperty.call(t,s)&&(p[s]=t[s]);p.originalType=e,p.mdxType="string"==typeof e?e:a,i[1]=p;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>d,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var r=n(7462),a=(n(7294),n(3905));const o={title:"Pristine Expo Project",description:"How to remove native related code from a unified Ignite project",tags:["Expo"],last_update:{author:"Frank Calise"},publish_date:new Date("2022-10-11T00:00:00.000Z")},i="Pristine Expo Project",p={unversionedId:"recipes/PristineExpoProject",id:"recipes/PristineExpoProject",title:"Pristine Expo Project",description:"How to remove native related code from a unified Ignite project",source:"@site/docs/recipes/PristineExpoProject.md",sourceDirName:"recipes",slug:"/recipes/PristineExpoProject",permalink:"/docs/recipes/PristineExpoProject",draft:!1,tags:[{label:"Expo",permalink:"/docs/tags/expo"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Pristine Expo Project",description:"How to remove native related code from a unified Ignite project",tags:["Expo"],last_update:{author:"Frank Calise"},publish_date:"2022-10-11T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Patching/Building Android .aar From Source",permalink:"/docs/recipes/PatchingBuildingAndroid"},next:{title:"Sample YAML for CircleCi for Ignite",permalink:"/docs/recipes/SampleYAMLCircleCI"}},s={},c=[{value:"Notes",id:"notes",level:2},{value:"Steps",id:"steps",level:2},{value:"Project Initialization",id:"project-initialization",level:3},{value:"Filesystem Changes",id:"filesystem-changes",level:3},{value:"Package Changes",id:"package-changes",level:3},{value:"package.json Script Updates",id:"packagejson-script-updates",level:3}],l={toc:c};function d(e){let{components:t,...n}=e;return(0,a.kt)("wrapper",(0,r.Z)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"pristine-expo-project"},"Pristine Expo Project"),(0,a.kt)("p",null,"Ignite sets your project up ready to run both a bare React Native project or with Expo."),(0,a.kt)("p",null,"However, if you don't want to manage any of the native files going forward, you can follow these steps to get to an Expo only project structure."),(0,a.kt)("h2",{id:"notes"},"Notes"),(0,a.kt)("p",null,"Keep in mind you may have to adopt the following steps for a different package manager or OS. The following are compatible for the ",(0,a.kt)("inlineCode",{parentName:"p"},"yarn")," package manager while running on MacOS."),(0,a.kt)("h2",{id:"steps"},"Steps"),(0,a.kt)("h3",{id:"project-initialization"},"Project Initialization"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-terminal"},"npx ignite-cli new PizzaApp --yes\ncd PizzaApp\n")),(0,a.kt)("h3",{id:"filesystem-changes"},"Filesystem Changes"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-terminal"},"rm -rf android\nrm -rf ios\nrm index.js # Expo's entry point is App.js\nrm metro.config.js # Expo will use the default\n")),(0,a.kt)("h3",{id:"package-changes"},"Package Changes"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-terminal"},"yarn remove react-native-bootsplash\n")),(0,a.kt)("p",null,"Removes a native library pertaining to the splash screen (this will be handled via ",(0,a.kt)("inlineCode",{parentName:"p"},"expo-splash-screen"),")"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-terminal"},"yarn remove expo-modules-core\n")),(0,a.kt)("h3",{id:"packagejson-script-updates"},(0,a.kt)("inlineCode",{parentName:"h3"},"package.json")," Script Updates"),(0,a.kt)("p",null,"These changes are optional as you can continue to use the prefixed ",(0,a.kt)("inlineCode",{parentName:"p"},"expo:")," commands, however you might just want a cleaned up ",(0,a.kt)("inlineCode",{parentName:"p"},"scripts")," section of your ",(0,a.kt)("inlineCode",{parentName:"p"},"package.json"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-json"},'{\n "name": "ignite-eas",\n "version": "0.0.1",\n "private": true,\n "main": "node_modules/expo/AppEntry.js",\n "scripts": {\n "compile": "tsc --noEmit -p . --pretty",\n "format": "prettier --write \\"app/**/*.{js,jsx,json,md,ts,tsx}\\"",\n "lint": "eslint App.js app test --fix --ext .js,.ts,.tsx && npm run format",\n "patch": "patch-package",\n "test": "jest",\n "test:watch": "jest --watch",\n "adb": "adb reverse tcp:9090 tcp:9090 && adb reverse tcp:3000 tcp:3000 && adb reverse tcp:9001 tcp:9001 && adb reverse tcp:8081 tcp:8081",\n "postinstall": "node ./bin/postInstall",\n "clean": "npx react-native-clean-project",\n "clean-all": "npx react-native clean-project-auto",\n "start": "expo start",\n "android": "expo start --android",\n "ios": "expo start --ios",\n "web": "expo start --web",\n "build:detox": "detox build -c ios.sim.expo",\n "test:detox": "./bin/downloadExpoApp.sh && detox test --configuration ios.sim.expo"\n },\n // ... more config ...\n "detox": {\n "test-runner": "jest",\n "runnerConfig": "./detox/config.json",\n "specs": "detox",\n "configurations": {\n "ios.sim.expo": {\n "binaryPath": "bin/Exponent.app",\n "type": "ios.simulator",\n "name": "iPhone 14"\n }\n }\n }\n // ... more config ...\n}\n')))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[4011],{3905:(e,t,n)=>{n.d(t,{Zo:()=>l,kt:()=>m});var r=n(7294);function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function i(e){for(var t=1;t=0||(a[n]=e[n]);return a}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(a[n]=e[n])}return a}var s=r.createContext({}),c=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):i(i({},t),e)),n},l=function(e){var t=c(e.components);return r.createElement(s.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},u=r.forwardRef((function(e,t){var n=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,l=p(e,["components","mdxType","originalType","parentName"]),u=c(n),m=a,g=u["".concat(s,".").concat(m)]||u[m]||d[m]||o;return n?r.createElement(g,i(i({ref:t},l),{},{components:n})):r.createElement(g,i({ref:t},l))}));function m(e,t){var n=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var o=n.length,i=new Array(o);i[0]=u;var p={};for(var s in t)hasOwnProperty.call(t,s)&&(p[s]=t[s]);p.originalType=e,p.mdxType="string"==typeof e?e:a,i[1]=p;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>i,default:()=>d,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var r=n(7462),a=(n(7294),n(3905));const o={title:"Pristine Expo Project",description:"How to remove native related code from a unified Ignite project",tags:["Expo"],last_update:{author:"Frank Calise"},publish_date:new Date("2022-10-11T00:00:00.000Z")},i="Pristine Expo Project",p={unversionedId:"recipes/PristineExpoProject",id:"recipes/PristineExpoProject",title:"Pristine Expo Project",description:"How to remove native related code from a unified Ignite project",source:"@site/docs/recipes/PristineExpoProject.md",sourceDirName:"recipes",slug:"/recipes/PristineExpoProject",permalink:"/docs/recipes/PristineExpoProject",draft:!1,tags:[{label:"Expo",permalink:"/docs/tags/expo"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Pristine Expo Project",description:"How to remove native related code from a unified Ignite project",tags:["Expo"],last_update:{author:"Frank Calise"},publish_date:"2022-10-11T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Patching/Building Android .aar From Source",permalink:"/docs/recipes/PatchingBuildingAndroid"},next:{title:"Sample YAML for CircleCi for Ignite",permalink:"/docs/recipes/SampleYAMLCircleCI"}},s={},c=[{value:"Notes",id:"notes",level:2},{value:"Steps",id:"steps",level:2},{value:"Project Initialization",id:"project-initialization",level:3},{value:"Filesystem Changes",id:"filesystem-changes",level:3},{value:"Package Changes",id:"package-changes",level:3},{value:"package.json Script Updates",id:"packagejson-script-updates",level:3}],l={toc:c};function d(e){let{components:t,...n}=e;return(0,a.kt)("wrapper",(0,r.Z)({},l,n,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"pristine-expo-project"},"Pristine Expo Project"),(0,a.kt)("p",null,"Ignite sets your project up ready to run both a bare React Native project or with Expo."),(0,a.kt)("p",null,"However, if you don't want to manage any of the native files going forward, you can follow these steps to get to an Expo only project structure."),(0,a.kt)("h2",{id:"notes"},"Notes"),(0,a.kt)("p",null,"Keep in mind you may have to adopt the following steps for a different package manager or OS. The following are compatible for the ",(0,a.kt)("inlineCode",{parentName:"p"},"yarn")," package manager while running on MacOS."),(0,a.kt)("h2",{id:"steps"},"Steps"),(0,a.kt)("h3",{id:"project-initialization"},"Project Initialization"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-terminal"},"npx ignite-cli new PizzaApp --yes\ncd PizzaApp\n")),(0,a.kt)("h3",{id:"filesystem-changes"},"Filesystem Changes"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-terminal"},"rm -rf android\nrm -rf ios\nrm index.js # Expo's entry point is App.js\nrm metro.config.js # Expo will use the default\n")),(0,a.kt)("h3",{id:"package-changes"},"Package Changes"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-terminal"},"yarn remove react-native-bootsplash\n")),(0,a.kt)("p",null,"Removes a native library pertaining to the splash screen (this will be handled via ",(0,a.kt)("inlineCode",{parentName:"p"},"expo-splash-screen"),")"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-terminal"},"yarn remove expo-modules-core\n")),(0,a.kt)("h3",{id:"packagejson-script-updates"},(0,a.kt)("inlineCode",{parentName:"h3"},"package.json")," Script Updates"),(0,a.kt)("p",null,"These changes are optional as you can continue to use the prefixed ",(0,a.kt)("inlineCode",{parentName:"p"},"expo:")," commands, however you might just want a cleaned up ",(0,a.kt)("inlineCode",{parentName:"p"},"scripts")," section of your ",(0,a.kt)("inlineCode",{parentName:"p"},"package.json"),"."),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-json"},'{\n "name": "ignite-eas",\n "version": "0.0.1",\n "private": true,\n "main": "node_modules/expo/AppEntry.js",\n "scripts": {\n "compile": "tsc --noEmit -p . --pretty",\n "format": "prettier --write \\"app/**/*.{js,jsx,json,md,ts,tsx}\\"",\n "lint": "eslint App.js app test --fix --ext .js,.ts,.tsx && npm run format",\n "patch": "patch-package",\n "test": "jest",\n "test:watch": "jest --watch",\n "adb": "adb reverse tcp:9090 tcp:9090 && adb reverse tcp:3000 tcp:3000 && adb reverse tcp:9001 tcp:9001 && adb reverse tcp:8081 tcp:8081",\n "postinstall": "node ./bin/postInstall",\n "clean": "npx react-native-clean-project",\n "clean-all": "npx react-native clean-project-auto",\n "start": "expo start",\n "android": "expo start --android",\n "ios": "expo start --ios",\n "web": "expo start --web",\n "build:detox": "detox build -c ios.sim.expo",\n "test:detox": "./bin/downloadExpoApp.sh && detox test --configuration ios.sim.expo"\n },\n // ... more config ...\n "detox": {\n "test-runner": "jest",\n "runnerConfig": "./detox/config.json",\n "specs": "detox",\n "configurations": {\n "ios.sim.expo": {\n "binaryPath": "bin/Exponent.app",\n "type": "ios.simulator",\n "name": "iPhone 14"\n }\n }\n }\n // ... more config ...\n}\n')))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.0cb9a2ae.js b/assets/js/935f2afb.0cb9a2ae.js deleted file mode 100644 index c11255e5..00000000 --- a/assets/js/935f2afb.0cb9a2ae.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"mainSidebar":[{"type":"link","label":"Intro to Recipes","href":"/docs/intro","docId":"intro"},{"type":"link","label":"Browse By Tag","href":"/docs/tags"},{"type":"category","label":"Recipes","collapsed":false,"items":[{"type":"link","label":"Accessiblity Font Sizes","href":"/docs/recipes/AccessibilityFontSizes","docId":"recipes/AccessibilityFontSizes"},{"type":"link","label":"CircleCI CD Setup - React Native","href":"/docs/recipes/CircleCIRNSetup","docId":"recipes/CircleCIRNSetup"},{"type":"link","label":"Creating a Good Experience for Screen Readers","href":"/docs/recipes/CreatingGreateExperienceForScreenReaders","docId":"recipes/CreatingGreateExperienceForScreenReaders"},{"type":"link","label":"Detox Intro","href":"/docs/recipes/DetoxIntro","docId":"recipes/DetoxIntro"},{"type":"link","label":"Distributing Auth Token to APIs","href":"/docs/recipes/DistributingAuthTokenToAPI","docId":"recipes/DistributingAuthTokenToAPI"},{"type":"link","label":"EAS Update","href":"/docs/recipes/EASUpdate","docId":"recipes/EASUpdate"},{"type":"link","label":"Environment Variables","href":"/docs/recipes/EnvironmentVariables","docId":"recipes/EnvironmentVariables"},{"type":"link","label":"Generator for Component Tests","href":"/docs/recipes/GeneratorComponentTests","docId":"recipes/GeneratorComponentTests"},{"type":"link","label":"Maestro Setup","href":"/docs/recipes/MaestroSetup","docId":"recipes/MaestroSetup"},{"type":"link","label":"Migrating to FlashList","href":"/docs/recipes/MigratingToFlashList","docId":"recipes/MigratingToFlashList"},{"type":"link","label":"Migrating to MMKV","href":"/docs/recipes/MigratingToMMKV","docId":"recipes/MigratingToMMKV"},{"type":"link","label":"Patching/Building Android .aar From Source","href":"/docs/recipes/PatchingBuildingAndroid","docId":"recipes/PatchingBuildingAndroid"},{"type":"link","label":"Pristine Expo Project","href":"/docs/recipes/PristineExpoProject","docId":"recipes/PristineExpoProject"},{"type":"link","label":"Sample YAML for CircleCi for Ignite","href":"/docs/recipes/SampleYAMLCircleCI","docId":"recipes/SampleYAMLCircleCI"},{"type":"link","label":"SelectField using `react-native-bottom-sheet`","href":"/docs/recipes/SelectFieldWithBottomSheet","docId":"recipes/SelectFieldWithBottomSheet"},{"type":"link","label":"Staying With Expo","href":"/docs/recipes/StayingWithExpo","docId":"recipes/StayingWithExpo"},{"type":"link","label":"TypeScript baseUrl Configuration","href":"/docs/recipes/TypeScriptBaseURL","docId":"recipes/TypeScriptBaseURL"},{"type":"link","label":"Scrolling to a location that hasn\'t been rendered using FlatList or SectionList","href":"/docs/recipes/UnrenderedItemInScrollView","docId":"recipes/UnrenderedItemInScrollView"},{"type":"link","label":"Updating Dependencies with Yarn Audit, Outdated and Upgrade","href":"/docs/recipes/UpdatingDependencies","docId":"recipes/UpdatingDependencies"},{"type":"link","label":"Using Screen Readers","href":"/docs/recipes/UsingScreenReaders","docId":"recipes/UsingScreenReaders"}],"collapsible":true}]},"docs":{"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/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/EnvironmentVariables":{"id":"recipes/EnvironmentVariables","title":"Environment Variables","description":"A universal way to set up environment variables for bare and Expo projects","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/MaestroSetup":{"id":"recipes/MaestroSetup","title":"Maestro Setup","description":"Setting up e2e testing with Maestro in Ignite","sidebar":"mainSidebar"},"recipes/MigratingToFlashList":{"id":"recipes/MigratingToFlashList","title":"Migrating to FlashList","description":"How to migrate over to Shopify\'s FlashList in an Ignite project","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/PatchingBuildingAndroid":{"id":"recipes/PatchingBuildingAndroid","title":"Patching/Building Android .aar From Source","description":"Instructions for updating the RN Android source code","sidebar":"mainSidebar"},"recipes/PristineExpoProject":{"id":"recipes/PristineExpoProject","title":"Pristine Expo Project","description":"How to remove native related code from a unified Ignite project","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/StayingWithExpo":{"id":"recipes/StayingWithExpo","title":"Staying With Expo","description":"Setting up Ignite to build a custom Expo development client for use with Config Plugins","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/UsingScreenReaders":{"id":"recipes/UsingScreenReaders","title":"Using Screen Readers","description":"Learn how to use a screen reader to improve accesibility!","sidebar":"mainSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.556c5c16.js b/assets/js/935f2afb.556c5c16.js new file mode 100644 index 00000000..8779b8ec --- /dev/null +++ b/assets/js/935f2afb.556c5c16.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"mainSidebar":[{"type":"link","label":"Intro to Recipes","href":"/docs/intro","docId":"intro"},{"type":"link","label":"Browse By Tag","href":"/docs/tags"},{"type":"category","label":"Recipes","collapsed":false,"items":[{"type":"link","label":"Accessiblity Font Sizes","href":"/docs/recipes/AccessibilityFontSizes","docId":"recipes/AccessibilityFontSizes"},{"type":"link","label":"CircleCI CD Setup - React Native","href":"/docs/recipes/CircleCIRNSetup","docId":"recipes/CircleCIRNSetup"},{"type":"link","label":"Creating a Good Experience for Screen Readers","href":"/docs/recipes/CreatingGreateExperienceForScreenReaders","docId":"recipes/CreatingGreateExperienceForScreenReaders"},{"type":"link","label":"Detox Intro","href":"/docs/recipes/DetoxIntro","docId":"recipes/DetoxIntro"},{"type":"link","label":"Distributing Auth Token to APIs","href":"/docs/recipes/DistributingAuthTokenToAPI","docId":"recipes/DistributingAuthTokenToAPI"},{"type":"link","label":"EAS Update","href":"/docs/recipes/EASUpdate","docId":"recipes/EASUpdate"},{"type":"link","label":"Environment Variables","href":"/docs/recipes/EnvironmentVariables","docId":"recipes/EnvironmentVariables"},{"type":"link","label":"Generator for Component Tests","href":"/docs/recipes/GeneratorComponentTests","docId":"recipes/GeneratorComponentTests"},{"type":"link","label":"Maestro Setup","href":"/docs/recipes/MaestroSetup","docId":"recipes/MaestroSetup"},{"type":"link","label":"Migrating to MMKV","href":"/docs/recipes/MigratingToMMKV","docId":"recipes/MigratingToMMKV"},{"type":"link","label":"Patching/Building Android .aar From Source","href":"/docs/recipes/PatchingBuildingAndroid","docId":"recipes/PatchingBuildingAndroid"},{"type":"link","label":"Pristine Expo Project","href":"/docs/recipes/PristineExpoProject","docId":"recipes/PristineExpoProject"},{"type":"link","label":"Sample YAML for CircleCi for Ignite","href":"/docs/recipes/SampleYAMLCircleCI","docId":"recipes/SampleYAMLCircleCI"},{"type":"link","label":"SelectField using `react-native-bottom-sheet`","href":"/docs/recipes/SelectFieldWithBottomSheet","docId":"recipes/SelectFieldWithBottomSheet"},{"type":"link","label":"Staying With Expo","href":"/docs/recipes/StayingWithExpo","docId":"recipes/StayingWithExpo"},{"type":"link","label":"TypeScript baseUrl Configuration","href":"/docs/recipes/TypeScriptBaseURL","docId":"recipes/TypeScriptBaseURL"},{"type":"link","label":"Scrolling to a location that hasn\'t been rendered using FlatList or SectionList","href":"/docs/recipes/UnrenderedItemInScrollView","docId":"recipes/UnrenderedItemInScrollView"},{"type":"link","label":"Updating Dependencies with Yarn Audit, Outdated and Upgrade","href":"/docs/recipes/UpdatingDependencies","docId":"recipes/UpdatingDependencies"},{"type":"link","label":"Using Screen Readers","href":"/docs/recipes/UsingScreenReaders","docId":"recipes/UsingScreenReaders"}],"collapsible":true}]},"docs":{"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/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/EnvironmentVariables":{"id":"recipes/EnvironmentVariables","title":"Environment Variables","description":"A universal way to set up environment variables for bare and Expo projects","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/MaestroSetup":{"id":"recipes/MaestroSetup","title":"Maestro Setup","description":"Setting up e2e testing with Maestro in Ignite","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/PatchingBuildingAndroid":{"id":"recipes/PatchingBuildingAndroid","title":"Patching/Building Android .aar From Source","description":"Instructions for updating the RN Android source code","sidebar":"mainSidebar"},"recipes/PristineExpoProject":{"id":"recipes/PristineExpoProject","title":"Pristine Expo Project","description":"How to remove native related code from a unified Ignite project","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/StayingWithExpo":{"id":"recipes/StayingWithExpo","title":"Staying With Expo","description":"Setting up Ignite to build a custom Expo development client for use with Config Plugins","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/UsingScreenReaders":{"id":"recipes/UsingScreenReaders","title":"Using Screen Readers","description":"Learn how to use a screen reader to improve accesibility!","sidebar":"mainSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/9fc76e3d.5bff49b0.js b/assets/js/9fc76e3d.c5fe907f.js similarity index 99% rename from assets/js/9fc76e3d.5bff49b0.js rename to assets/js/9fc76e3d.c5fe907f.js index a324b17e..a5a74282 100644 --- a/assets/js/9fc76e3d.5bff49b0.js +++ b/assets/js/9fc76e3d.c5fe907f.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[6247],{3905:(e,t,a)=>{a.d(t,{Zo:()=>d,kt:()=>h});var n=a(7294);function o(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function r(e){for(var t=1;t=0||(o[a]=e[a]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(o[a]=e[a])}return o}var p=n.createContext({}),s=function(e){var t=n.useContext(p),a=t;return e&&(a="function"==typeof e?e(t):r(r({},t),e)),a},d=function(e){var t=s(e.components);return n.createElement(p.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},c=n.forwardRef((function(e,t){var a=e.components,o=e.mdxType,i=e.originalType,p=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),c=s(a),h=o,m=c["".concat(p,".").concat(h)]||c[h]||u[h]||i;return a?n.createElement(m,r(r({ref:t},d),{},{components:a})):n.createElement(m,r({ref:t},d))}));function h(e,t){var a=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=a.length,r=new Array(i);r[0]=c;var l={};for(var p in t)hasOwnProperty.call(t,p)&&(l[p]=t[p]);l.originalType=e,l.mdxType="string"==typeof e?e:o,r[1]=l;for(var s=2;s{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>r,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var n=a(7462),o=(a(7294),a(3905));const i={title:"EAS Update",description:"Setting up Ignite to deploy over-the-air (OTA) updates via EAS",tags:["Expo","expo-updates","EAS Update"],last_update:{author:"Frank Calise"},publish_date:new Date("2023-01-06T00:00:00.000Z")},r="EAS Update",l={unversionedId:"recipes/EASUpdate",id:"recipes/EASUpdate",title:"EAS Update",description:"Setting up Ignite to deploy over-the-air (OTA) updates via EAS",source:"@site/docs/recipes/EASUpdate.md",sourceDirName:"recipes",slug:"/recipes/EASUpdate",permalink:"/docs/recipes/EASUpdate",draft:!1,tags:[{label:"Expo",permalink:"/docs/tags/expo"},{label:"expo-updates",permalink:"/docs/tags/expo-updates"},{label:"EAS Update",permalink:"/docs/tags/eas-update"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"EAS Update",description:"Setting up Ignite to deploy over-the-air (OTA) updates via EAS",tags:["Expo","expo-updates","EAS Update"],last_update:{author:"Frank Calise"},publish_date:"2023-01-06T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Distributing Auth Token to APIs",permalink:"/docs/recipes/DistributingAuthTokenToAPI"},next:{title:"Environment Variables",permalink:"/docs/recipes/EnvironmentVariables"}},p={},s=[{value:"Appetizer",id:"appetizer",level:2},{value:"Steps",id:"steps",level:2},{value:"1. Project Setup",id:"1-project-setup",level:3},{value:"2. Edit Build Profiles",id:"2-edit-build-profiles",level:3},{value:"3. Create the Build",id:"3-create-the-build",level:3},{value:"4. Making a Project Update",id:"4-making-a-project-update",level:3},{value:"5. Publishing OTA",id:"5-publishing-ota",level:3},{value:"Create the Update Channel",id:"create-the-update-channel",level:4},{value:"Upload Changes to EAS",id:"upload-changes-to-eas",level:4},{value:"Download the Updates OTA",id:"download-the-updates-ota",level:4},{value:"Customized Update Flow",id:"customized-update-flow",level:2},{value:"Notes",id:"notes",level:2},{value:"More Resources",id:"more-resources",level:2}],d={toc:s};function u(e){let{components:t,...a}=e;return(0,o.kt)("wrapper",(0,n.Z)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"eas-update"},"EAS Update"),(0,o.kt)("p",null,"This guide will teach you how to set up over-the-air (OTA) updates with Expo and EAS Update within an Ignite project."),(0,o.kt)("h2",{id:"appetizer"},"Appetizer"),(0,o.kt)("p",null,"Follow the ",(0,o.kt)("a",{parentName:"p",href:"/docs/recipes/PristineExpoProject"},"Pristine Expo Project")," recipe first to make sure you're starting with an Expo only project."),(0,o.kt)("p",null,"You'll also need ",(0,o.kt)("inlineCode",{parentName:"p"},"eas-cli")," globally installed and and an ",(0,o.kt)("a",{parentName:"p",href:"https://expo.dev/signup"},"Expo account")," if you don't already have one."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-console"},"npm install -g eas-cli\n")),(0,o.kt)("h2",{id:"steps"},"Steps"),(0,o.kt)("h3",{id:"1-project-setup"},"1. Project Setup"),(0,o.kt)("p",null,"From within your project directory, run the following:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"npx expo install expo-updates\neas build:configure\n")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Answer yes to setting up the EAS project prompt and you can configure it for both platforms (",(0,o.kt)("strong",{parentName:"li"},"note:")," for the purposes of this guide we'll be using Android)"),(0,o.kt)("li",{parentName:"ul"},"This will make some edits to your ",(0,o.kt)("inlineCode",{parentName:"li"},"app.json")," now that the project has an EAS identifier, as well as add a new ",(0,o.kt)("inlineCode",{parentName:"li"},"eas.json")," configuration file.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"eas update:configure\n")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Some more ",(0,o.kt)("inlineCode",{parentName:"li"},"app.json")," changes will be made adding an ",(0,o.kt)("inlineCode",{parentName:"li"},"updates")," key to your configuration file.")),(0,o.kt)("h3",{id:"2-edit-build-profiles"},"2. Edit Build Profiles"),(0,o.kt)("p",null,"Modify the newly generated ",(0,o.kt)("inlineCode",{parentName:"p"},"eas.json")," to configure a ",(0,o.kt)("inlineCode",{parentName:"p"},"preview")," build profile. We'll be using a ",(0,o.kt)("inlineCode",{parentName:"p"},"buildType")," of ",(0,o.kt)("inlineCode",{parentName:"p"},"apk")," for the Android build to keep things easy for testing, your production build can still use an Android App Bundle."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-json"},'{\n "cli": {\n "version": ">= 3.0.0"\n },\n "build": {\n "development": {\n "developmentClient": true,\n "distribution": "internal"\n },\n "preview": {\n "channel": "preview",\n "android": { "buildType": "apk" },\n "ios": { "simulator": false }\n },\n "production": {}\n },\n "submit": {\n "production": {}\n }\n}\n')),(0,o.kt)("h3",{id:"3-create-the-build"},"3. Create the Build"),(0,o.kt)("p",null,"Next, we'll create a build that we can test with on either an Android emulator or device. You may do this via EAS or ",(0,o.kt)("a",{parentName:"p",href:"https://docs.expo.dev/build-reference/local-builds/"},"locally")," if you the added queue time for an unpaid EAS account is getting in your way."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"# via EAS\neas build --profile preview -p android\n\n# locally\neas build --profile preview -p android --local\n")),(0,o.kt)("p",null,"Accept the prompts for generating the new Android Keystore. Once that is completed (~7-10 minutes if you do it locally, add some extra time if using the EAS servers due to the queue time), drop it on your emulator or device and fire it up! \ud83d\udd25"),(0,o.kt)("h3",{id:"4-making-a-project-update"},"4. Making a Project Update"),(0,o.kt)("p",null,"Right now you should be staring at the ",(0,o.kt)("inlineCode",{parentName:"p"},"")," of the Ignite demo code. Let's go ahead and make an update to that so we can deploy it OTA. Open up ",(0,o.kt)("inlineCode",{parentName:"p"},"./app/screens/LoginScreen.tsx"),". Feel free to make any modification, but for an example, let's change the header text:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'// error-line\n\n// success-line\n\n')),(0,o.kt)("p",null,"Observe that currently there are no changes to your application (as metro is not running), there are no hot reloads going on and the header text remains ",(0,o.kt)("inlineCode",{parentName:"p"},"Sign in"),"."),(0,o.kt)("h3",{id:"5-publishing-ota"},"5. Publishing OTA"),(0,o.kt)("h4",{id:"create-the-update-channel"},"Create the Update Channel"),(0,o.kt)("p",null,"To publish this out to our app which is living on the ",(0,o.kt)("inlineCode",{parentName:"p"},"preview")," channel (via the build profile in ",(0,o.kt)("inlineCode",{parentName:"p"},"eas.json"),"), we'll set up the channel:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"eas channel:create preview\n")),(0,o.kt)("p",null,"This only has to be done once if the channel doesn't exist (which you can confirm via ",(0,o.kt)("inlineCode",{parentName:"p"},"eas channel:view [name]"),")."),(0,o.kt)("h4",{id:"upload-changes-to-eas"},"Upload Changes to EAS"),(0,o.kt)("p",null,"Next, tell EAS about the update package to send out with our new changes:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},'eas update --branch preview --message "update login screen"\n')),(0,o.kt)("h4",{id:"download-the-updates-ota"},"Download the Updates OTA"),(0,o.kt)("p",null,"Now that the updates are on the server, we can look to update our app on our emulator/device. If the app is still in the foreground, swipe the app away to close it."),(0,o.kt)("p",null,"Reopen it from the app drawer. At this point in time, the default Expo update flow is to check for new updates on the update channel (formerly release channel if you're coming over from ",(0,o.kt)("inlineCode",{parentName:"p"},"expo publish"),"). If there is a pending compatible update, it will now be downloaded."),(0,o.kt)("p",null,"Close the app again and reopen it. Now the update will actually be applied. Note the change in the heading at the top of the sign in screen. It should match ",(0,o.kt)("inlineCode",{parentName:"p"},"Welcome! Please sign in")," or whatever modification you made in Step 4."),(0,o.kt)("p",null,"You've now successfully sent an update over-the-air! Waiting on the app store review has been skipped and your critical bug fix is out there to the masses."),(0,o.kt)("h2",{id:"customized-update-flow"},"Customized Update Flow"),(0,o.kt)("p",null,"You'll note in Step 5 that it was kind of magical / lucky that we rebooted the app twice to get the update. This can be difficult to communicate that kind of behavior to users. You can build on the foundation from this guide and add a more customized approach, providing the user with a better experience in a few ways. Here is one example flow:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Check for updates"),(0,o.kt)("li",{parentName:"ol"},"If one exists, alert the user"),(0,o.kt)("li",{parentName:"ol"},"Should they choose to update, keep them updated that it is in progress"),(0,o.kt)("li",{parentName:"ol"},"After successfully getting the update, offer to restart the app for the user"),(0,o.kt)("li",{parentName:"ol"},"On any errors, communicate that it could not be completed at this time.")),(0,o.kt)("p",null,"To achieve this functionality, you'll have to make some modifications to the ",(0,o.kt)("inlineCode",{parentName:"p"},"app.json")," for more manual control over the flow, in addition to your UI changes."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-json"},'{\n // ...\n "updates": {\n "checkAutomatically": "ON_ERROR_RECOVERY"\n // ...\n }\n}\n')),(0,o.kt)("p",null,"This will tell Expo to skip checking for updates on load and instead only do it when we request a check. Look into the following methods to achieve this:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/versions/latest/sdk/updates/#updatescheckforupdateasync"},"checkForUpdateAsync()")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/versions/latest/sdk/updates/#updatesfetchupdateasync"},"fetchUpdateAsync()")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/versions/latest/sdk/updates/#updatesreloadasync"},"reloadAsync()"))),(0,o.kt)("h2",{id:"notes"},"Notes"),(0,o.kt)("p",null,"Upgrading to the next Expo SDK version, native code changes and ",(0,o.kt)("inlineCode",{parentName:"p"},"app.json")," or ",(0,o.kt)("inlineCode",{parentName:"p"},"app.config.js")," changes behave differently, so make sure to read the additional documentation below if that is your goal."),(0,o.kt)("h2",{id:"more-resources"},"More Resources"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/eas-update/introduction/"},"EAS Update Introduction")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/build/updates/"},"Using EAS Update with EAS Build")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/versions/latest/sdk/updates/"},"Expo Updates Library Docs")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/build-reference/local-builds/"},"Running builds on your own infrastructure"))))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[6247],{3905:(e,t,a)=>{a.d(t,{Zo:()=>d,kt:()=>h});var n=a(7294);function o(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function r(e){for(var t=1;t=0||(o[a]=e[a]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(o[a]=e[a])}return o}var p=n.createContext({}),s=function(e){var t=n.useContext(p),a=t;return e&&(a="function"==typeof e?e(t):r(r({},t),e)),a},d=function(e){var t=s(e.components);return n.createElement(p.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},c=n.forwardRef((function(e,t){var a=e.components,o=e.mdxType,i=e.originalType,p=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),c=s(a),h=o,m=c["".concat(p,".").concat(h)]||c[h]||u[h]||i;return a?n.createElement(m,r(r({ref:t},d),{},{components:a})):n.createElement(m,r({ref:t},d))}));function h(e,t){var a=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=a.length,r=new Array(i);r[0]=c;var l={};for(var p in t)hasOwnProperty.call(t,p)&&(l[p]=t[p]);l.originalType=e,l.mdxType="string"==typeof e?e:o,r[1]=l;for(var s=2;s{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>r,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>s});var n=a(7462),o=(a(7294),a(3905));const i={title:"EAS Update",description:"Setting up Ignite to deploy over-the-air (OTA) updates via EAS",tags:["Expo","expo-updates","EAS Update"],last_update:{author:"Frank Calise"},publish_date:new Date("2023-01-06T00:00:00.000Z")},r="EAS Update",l={unversionedId:"recipes/EASUpdate",id:"recipes/EASUpdate",title:"EAS Update",description:"Setting up Ignite to deploy over-the-air (OTA) updates via EAS",source:"@site/docs/recipes/EASUpdate.md",sourceDirName:"recipes",slug:"/recipes/EASUpdate",permalink:"/docs/recipes/EASUpdate",draft:!1,tags:[{label:"Expo",permalink:"/docs/tags/expo"},{label:"expo-updates",permalink:"/docs/tags/expo-updates"},{label:"EAS Update",permalink:"/docs/tags/eas-update"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"EAS Update",description:"Setting up Ignite to deploy over-the-air (OTA) updates via EAS",tags:["Expo","expo-updates","EAS Update"],last_update:{author:"Frank Calise"},publish_date:"2023-01-06T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Distributing Auth Token to APIs",permalink:"/docs/recipes/DistributingAuthTokenToAPI"},next:{title:"Environment Variables",permalink:"/docs/recipes/EnvironmentVariables"}},p={},s=[{value:"Appetizer",id:"appetizer",level:2},{value:"Steps",id:"steps",level:2},{value:"1. Project Setup",id:"1-project-setup",level:3},{value:"2. Edit Build Profiles",id:"2-edit-build-profiles",level:3},{value:"3. Create the Build",id:"3-create-the-build",level:3},{value:"4. Making a Project Update",id:"4-making-a-project-update",level:3},{value:"5. Publishing OTA",id:"5-publishing-ota",level:3},{value:"Create the Update Channel",id:"create-the-update-channel",level:4},{value:"Upload Changes to EAS",id:"upload-changes-to-eas",level:4},{value:"Download the Updates OTA",id:"download-the-updates-ota",level:4},{value:"Customized Update Flow",id:"customized-update-flow",level:2},{value:"Notes",id:"notes",level:2},{value:"More Resources",id:"more-resources",level:2}],d={toc:s};function u(e){let{components:t,...a}=e;return(0,o.kt)("wrapper",(0,n.Z)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"eas-update"},"EAS Update"),(0,o.kt)("p",null,"This guide will teach you how to set up over-the-air (OTA) updates with Expo and EAS Update within an Ignite project."),(0,o.kt)("h2",{id:"appetizer"},"Appetizer"),(0,o.kt)("p",null,"Follow the ",(0,o.kt)("a",{parentName:"p",href:"/docs/recipes/PristineExpoProject"},"Pristine Expo Project")," recipe first to make sure you're starting with an Expo only project."),(0,o.kt)("p",null,"You'll also need ",(0,o.kt)("inlineCode",{parentName:"p"},"eas-cli")," globally installed and and an ",(0,o.kt)("a",{parentName:"p",href:"https://expo.dev/signup"},"Expo account")," if you don't already have one."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-console"},"npm install -g eas-cli\n")),(0,o.kt)("h2",{id:"steps"},"Steps"),(0,o.kt)("h3",{id:"1-project-setup"},"1. Project Setup"),(0,o.kt)("p",null,"From within your project directory, run the following:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"npx expo install expo-updates\neas build:configure\n")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Answer yes to setting up the EAS project prompt and you can configure it for both platforms (",(0,o.kt)("strong",{parentName:"li"},"note:")," for the purposes of this guide we'll be using Android)"),(0,o.kt)("li",{parentName:"ul"},"This will make some edits to your ",(0,o.kt)("inlineCode",{parentName:"li"},"app.json")," now that the project has an EAS identifier, as well as add a new ",(0,o.kt)("inlineCode",{parentName:"li"},"eas.json")," configuration file.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"eas update:configure\n")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Some more ",(0,o.kt)("inlineCode",{parentName:"li"},"app.json")," changes will be made adding an ",(0,o.kt)("inlineCode",{parentName:"li"},"updates")," key to your configuration file.")),(0,o.kt)("h3",{id:"2-edit-build-profiles"},"2. Edit Build Profiles"),(0,o.kt)("p",null,"Modify the newly generated ",(0,o.kt)("inlineCode",{parentName:"p"},"eas.json")," to configure a ",(0,o.kt)("inlineCode",{parentName:"p"},"preview")," build profile. We'll be using a ",(0,o.kt)("inlineCode",{parentName:"p"},"buildType")," of ",(0,o.kt)("inlineCode",{parentName:"p"},"apk")," for the Android build to keep things easy for testing, your production build can still use an Android App Bundle."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-json"},'{\n "cli": {\n "version": ">= 3.0.0"\n },\n "build": {\n "development": {\n "developmentClient": true,\n "distribution": "internal"\n },\n "preview": {\n "channel": "preview",\n "android": { "buildType": "apk" },\n "ios": { "simulator": false }\n },\n "production": {}\n },\n "submit": {\n "production": {}\n }\n}\n')),(0,o.kt)("h3",{id:"3-create-the-build"},"3. Create the Build"),(0,o.kt)("p",null,"Next, we'll create a build that we can test with on either an Android emulator or device. You may do this via EAS or ",(0,o.kt)("a",{parentName:"p",href:"https://docs.expo.dev/build-reference/local-builds/"},"locally")," if you the added queue time for an unpaid EAS account is getting in your way."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"# via EAS\neas build --profile preview -p android\n\n# locally\neas build --profile preview -p android --local\n")),(0,o.kt)("p",null,"Accept the prompts for generating the new Android Keystore. Once that is completed (~7-10 minutes if you do it locally, add some extra time if using the EAS servers due to the queue time), drop it on your emulator or device and fire it up! \ud83d\udd25"),(0,o.kt)("h3",{id:"4-making-a-project-update"},"4. Making a Project Update"),(0,o.kt)("p",null,"Right now you should be staring at the ",(0,o.kt)("inlineCode",{parentName:"p"},"")," of the Ignite demo code. Let's go ahead and make an update to that so we can deploy it OTA. Open up ",(0,o.kt)("inlineCode",{parentName:"p"},"./app/screens/LoginScreen.tsx"),". Feel free to make any modification, but for an example, let's change the header text:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'// error-line\n\n// success-line\n\n')),(0,o.kt)("p",null,"Observe that currently there are no changes to your application (as metro is not running), there are no hot reloads going on and the header text remains ",(0,o.kt)("inlineCode",{parentName:"p"},"Sign in"),"."),(0,o.kt)("h3",{id:"5-publishing-ota"},"5. Publishing OTA"),(0,o.kt)("h4",{id:"create-the-update-channel"},"Create the Update Channel"),(0,o.kt)("p",null,"To publish this out to our app which is living on the ",(0,o.kt)("inlineCode",{parentName:"p"},"preview")," channel (via the build profile in ",(0,o.kt)("inlineCode",{parentName:"p"},"eas.json"),"), we'll set up the channel:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"eas channel:create preview\n")),(0,o.kt)("p",null,"This only has to be done once if the channel doesn't exist (which you can confirm via ",(0,o.kt)("inlineCode",{parentName:"p"},"eas channel:view [name]"),")."),(0,o.kt)("h4",{id:"upload-changes-to-eas"},"Upload Changes to EAS"),(0,o.kt)("p",null,"Next, tell EAS about the update package to send out with our new changes:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},'eas update --branch preview --message "update login screen"\n')),(0,o.kt)("h4",{id:"download-the-updates-ota"},"Download the Updates OTA"),(0,o.kt)("p",null,"Now that the updates are on the server, we can look to update our app on our emulator/device. If the app is still in the foreground, swipe the app away to close it."),(0,o.kt)("p",null,"Reopen it from the app drawer. At this point in time, the default Expo update flow is to check for new updates on the update channel (formerly release channel if you're coming over from ",(0,o.kt)("inlineCode",{parentName:"p"},"expo publish"),"). If there is a pending compatible update, it will now be downloaded."),(0,o.kt)("p",null,"Close the app again and reopen it. Now the update will actually be applied. Note the change in the heading at the top of the sign in screen. It should match ",(0,o.kt)("inlineCode",{parentName:"p"},"Welcome! Please sign in")," or whatever modification you made in Step 4."),(0,o.kt)("p",null,"You've now successfully sent an update over-the-air! Waiting on the app store review has been skipped and your critical bug fix is out there to the masses."),(0,o.kt)("h2",{id:"customized-update-flow"},"Customized Update Flow"),(0,o.kt)("p",null,"You'll note in Step 5 that it was kind of magical / lucky that we rebooted the app twice to get the update. This can be difficult to communicate that kind of behavior to users. You can build on the foundation from this guide and add a more customized approach, providing the user with a better experience in a few ways. Here is one example flow:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Check for updates"),(0,o.kt)("li",{parentName:"ol"},"If one exists, alert the user"),(0,o.kt)("li",{parentName:"ol"},"Should they choose to update, keep them updated that it is in progress"),(0,o.kt)("li",{parentName:"ol"},"After successfully getting the update, offer to restart the app for the user"),(0,o.kt)("li",{parentName:"ol"},"On any errors, communicate that it could not be completed at this time.")),(0,o.kt)("p",null,"To achieve this functionality, you'll have to make some modifications to the ",(0,o.kt)("inlineCode",{parentName:"p"},"app.json")," for more manual control over the flow, in addition to your UI changes."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-json"},'{\n // ...\n "updates": {\n "checkAutomatically": "ON_ERROR_RECOVERY"\n // ...\n }\n}\n')),(0,o.kt)("p",null,"This will tell Expo to skip checking for updates on load and instead only do it when we request a check. Look into the following methods to achieve this:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/versions/latest/sdk/updates/#updatescheckforupdateasync"},"checkForUpdateAsync()")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/versions/latest/sdk/updates/#updatesfetchupdateasync"},"fetchUpdateAsync()")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/versions/latest/sdk/updates/#updatesreloadasync"},"reloadAsync()"))),(0,o.kt)("h2",{id:"notes"},"Notes"),(0,o.kt)("p",null,"Upgrading to the next Expo SDK version, native code changes and ",(0,o.kt)("inlineCode",{parentName:"p"},"app.json")," or ",(0,o.kt)("inlineCode",{parentName:"p"},"app.config.js")," changes behave differently, so make sure to read the additional documentation below if that is your goal."),(0,o.kt)("h2",{id:"more-resources"},"More Resources"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/eas-update/introduction/"},"EAS Update Introduction")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/build/updates/"},"Using EAS Update with EAS Build")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/versions/latest/sdk/updates/"},"Expo Updates Library Docs")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://docs.expo.dev/build-reference/local-builds/"},"Running builds on your own infrastructure"))))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/9ff7e1f8.d7e66644.js b/assets/js/9ff7e1f8.d7e66644.js deleted file mode 100644 index c21057e0..00000000 --- a/assets/js/9ff7e1f8.d7e66644.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[2181],{3905:(e,t,n)=>{n.d(t,{Zo:()=>c,kt:()=>u});var a=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var l=a.createContext({}),p=function(e){var t=a.useContext(l),n=t;return e&&(n="function"==typeof e?e(t):o(o({},t),e)),n},c=function(e){var t=p(e.components);return a.createElement(l.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},h=a.forwardRef((function(e,t){var n=e.components,r=e.mdxType,i=e.originalType,l=e.parentName,c=s(e,["components","mdxType","originalType","parentName"]),h=p(n),u=r,g=h["".concat(l,".").concat(u)]||h[u]||d[u]||i;return n?a.createElement(g,o(o({ref:t},c),{},{components:n})):a.createElement(g,o({ref:t},c))}));function u(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=n.length,o=new Array(i);o[0]=h;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:r,o[1]=s;for(var p=2;p{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>p});var a=n(7462),r=(n(7294),n(3905));const i={title:"Migrating to FlashList",description:"How to migrate over to Shopify's FlashList in an Ignite project",tags:["Shopify","FlashList"],last_update:{author:"Frank Calise"},publish_date:new Date("2022-10-13T00:00:00.000Z")},o="Migrating to FlashList",s={unversionedId:"recipes/MigratingToFlashList",id:"recipes/MigratingToFlashList",title:"Migrating to FlashList",description:"How to migrate over to Shopify's FlashList in an Ignite project",source:"@site/docs/recipes/MigratingToFlashList.md",sourceDirName:"recipes",slug:"/recipes/MigratingToFlashList",permalink:"/docs/recipes/MigratingToFlashList",draft:!1,tags:[{label:"Shopify",permalink:"/docs/tags/shopify"},{label:"FlashList",permalink:"/docs/tags/flash-list"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Migrating to FlashList",description:"How to migrate over to Shopify's FlashList in an Ignite project",tags:["Shopify","FlashList"],last_update:{author:"Frank Calise"},publish_date:"2022-10-13T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Maestro Setup",permalink:"/docs/recipes/MaestroSetup"},next:{title:"Migrating to MMKV",permalink:"/docs/recipes/MigratingToMMKV"}},l={},p=[{value:"Overview",id:"overview",level:2},{value:"Project Dependencies",id:"project-dependencies",level:2},{value:"Code Changes",id:"code-changes",level:2}],c={toc:p};function d(e){let{components:t,...n}=e;return(0,r.kt)("wrapper",(0,a.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"migrating-to-flashlist"},"Migrating to FlashList"),(0,r.kt)("h2",{id:"overview"},"Overview"),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://shopify.github.io/flash-list/"},"Shopify's FlashList")," provides a drop-in replacement for React Native's FlatList component. It's an easy refactor and your lists will perform better within your app!"),(0,r.kt)("p",null,"We'll start with the demo project provided by Ignite, so if you need a new one fire away with:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-nodejs"},"npx ignite-cli new PizzaApp --yes\ncd PizzaApp\n")),(0,r.kt)("h2",{id:"project-dependencies"},"Project Dependencies"),(0,r.kt)("p",null,"Whether you're sticking with Expo or running with bare React Native workflow, our install steps are the same:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-nodejs"},"npx expo install @shopify/flash-list\n")),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"Note: No ",(0,r.kt)("inlineCode",{parentName:"em"},"pod install")," was run here because the scripts set up in an Ignite project take care of that for you!")),(0,r.kt)("h2",{id:"code-changes"},"Code Changes"),(0,r.kt)("p",null,"Open ",(0,r.kt)("inlineCode",{parentName:"p"},"DemoPodcastListScreen.tsx")," and add the new import:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},'import { FlashList } from "@shopify/flash-list";\n')),(0,r.kt)("p",null,"Find the ",(0,r.kt)("inlineCode",{parentName:"p"},"FlatList")," being used in the returned JSX and swap it out for ",(0,r.kt)("inlineCode",{parentName:"p"},"FlashList")),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},'return (\n \n // highlight-next-line\n \n data={episodeStore.episodesForList}\n extraData={episodeStore.favorites.length + episodeStore.episodes.length}\n contentContainerStyle={$flatListContentContainer}\n refreshing={refreshing}\n onRefresh={manualRefresh}\n // ...\n />\n // ...\n \n);\n')),(0,r.kt)("p",null,"Run the app in the iOS simulator to test the changes, either ",(0,r.kt)("inlineCode",{parentName:"p"},"yarn expo:ios")," or ",(0,r.kt)("inlineCode",{parentName:"p"},"yarn ios"),". Navigate to the Podcast List screen:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},'Press "Tap to sign in!"'),(0,r.kt)("li",{parentName:"ol"},'Press "Let\'s go!"'),(0,r.kt)("li",{parentName:"ol"},'Tap on the "Podcast"')),(0,r.kt)("p",null,"You'll get a warning out in the terminal, something similar to:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"}," WARN estimatedItemSize FlashList prop is not defined - based on current configuration you can set\n it to 187 to optimize list performance. Refer to FlashList documentation for more details.\n")),(0,r.kt)("p",null,"Simply add that prop to the ",(0,r.kt)("inlineCode",{parentName:"p"},"FlashList")," component with the suggested values:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-tsx"},"\n data={episodeStore.episodesForList}\n // highlight-next-line\n estimatedItemSize={187}\n // ...\n/>\n")),(0,r.kt)("p",null,"Reload the app and take note that the warning message has cleared!"),(0,r.kt)("p",null,"Now everything looks like it did before, while also gaining all of the performance boosts from FlashList! It's a pretty straight forward approach and Shopify has done a good job helping the developer along with the useful console warnings as a guide."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/c3e2f4d4.375c07d5.js b/assets/js/c3e2f4d4.e5442792.js similarity index 99% rename from assets/js/c3e2f4d4.375c07d5.js rename to assets/js/c3e2f4d4.e5442792.js index 1cc10378..8a34b713 100644 --- a/assets/js/c3e2f4d4.375c07d5.js +++ b/assets/js/c3e2f4d4.e5442792.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[3135],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>f});var r=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function a(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=r.createContext({}),c=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):a(a({},t),e)),n},p=function(e){var t=c(e.components);return r.createElement(s.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},u=r.forwardRef((function(e,t){var n=e.components,o=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=c(n),f=o,m=u["".concat(s,".").concat(f)]||u[f]||d[f]||i;return n?r.createElement(m,a(a({ref:t},p),{},{components:n})):r.createElement(m,a({ref:t},p))}));function f(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=n.length,a=new Array(i);a[0]=u;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l.mdxType="string"==typeof e?e:o,a[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>a,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>c});var r=n(7462),o=(n(7294),n(3905));const i={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",tags:["UI","FlatList","SectionList","scrollTo"],last_update:{author:"Mark Rickert"},publish_date:new Date("2022-10-09T00:00:00.000Z")},a=void 0,l={unversionedId:"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",source:"@site/docs/recipes/UnrenderedItemInScrollView.md",sourceDirName:"recipes",slug:"/recipes/UnrenderedItemInScrollView",permalink:"/docs/recipes/UnrenderedItemInScrollView",draft:!1,tags:[{label:"UI",permalink:"/docs/tags/ui"},{label:"FlatList",permalink:"/docs/tags/flat-list"},{label:"SectionList",permalink:"/docs/tags/section-list"},{label:"scrollTo",permalink:"/docs/tags/scroll-to"}],version:"current",lastUpdatedBy:"Mark Rickert",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{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",tags:["UI","FlatList","SectionList","scrollTo"],last_update:{author:"Mark Rickert"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"TypeScript baseUrl Configuration",permalink:"/docs/recipes/TypeScriptBaseURL"},next:{title:"Updating Dependencies with Yarn Audit, Outdated and Upgrade",permalink:"/docs/recipes/UpdatingDependencies"}},s={},c=[],p={toc:c};function d(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,r.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("p",null,"Calling ",(0,o.kt)("inlineCode",{parentName:"p"},"scrollViewRef.current.scrollToLocation()")," on a React Native ",(0,o.kt)("inlineCode",{parentName:"p"},"FlatList")," or ",(0,o.kt)("inlineCode",{parentName:"p"},"SectionList")," will fail on occasion because it can't scroll to a location that hasn't been rendered yet."),(0,o.kt)("p",null,"The solution to this is implementing ",(0,o.kt)("inlineCode",{parentName:"p"},"onScrollToIndexFailed")," with some sort of recovery functionality to keep trying the scroll. This is a Higher Order Component (HOC) for ",(0,o.kt)("inlineCode",{parentName:"p"},"SectionList")," that handles this for us."),(0,o.kt)("p",null,"This component basically tries over and over to scroll to the requested location until it gets it right and no longer calls ",(0,o.kt)("inlineCode",{parentName:"p"},"onScrollToIndexFailed"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-jsx"},"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")))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[3135],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>f});var r=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function a(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=r.createContext({}),c=function(e){var t=r.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):a(a({},t),e)),n},p=function(e){var t=c(e.components);return r.createElement(s.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return r.createElement(r.Fragment,{},t)}},u=r.forwardRef((function(e,t){var n=e.components,o=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),u=c(n),f=o,m=u["".concat(s,".").concat(f)]||u[f]||d[f]||i;return n?r.createElement(m,a(a({ref:t},p),{},{components:n})):r.createElement(m,a({ref:t},p))}));function f(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=n.length,a=new Array(i);a[0]=u;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l.mdxType="string"==typeof e?e:o,a[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>a,default:()=>d,frontMatter:()=>i,metadata:()=>l,toc:()=>c});var r=n(7462),o=(n(7294),n(3905));const i={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",tags:["UI","FlatList","SectionList","scrollTo"],last_update:{author:"Mark Rickert"},publish_date:new Date("2022-10-09T00:00:00.000Z")},a=void 0,l={unversionedId:"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",source:"@site/docs/recipes/UnrenderedItemInScrollView.md",sourceDirName:"recipes",slug:"/recipes/UnrenderedItemInScrollView",permalink:"/docs/recipes/UnrenderedItemInScrollView",draft:!1,tags:[{label:"UI",permalink:"/docs/tags/ui"},{label:"FlatList",permalink:"/docs/tags/flat-list"},{label:"SectionList",permalink:"/docs/tags/section-list"},{label:"scrollTo",permalink:"/docs/tags/scroll-to"}],version:"current",lastUpdatedBy:"Mark Rickert",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{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",tags:["UI","FlatList","SectionList","scrollTo"],last_update:{author:"Mark Rickert"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"TypeScript baseUrl Configuration",permalink:"/docs/recipes/TypeScriptBaseURL"},next:{title:"Updating Dependencies with Yarn Audit, Outdated and Upgrade",permalink:"/docs/recipes/UpdatingDependencies"}},s={},c=[],p={toc:c};function d(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,r.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("p",null,"Calling ",(0,o.kt)("inlineCode",{parentName:"p"},"scrollViewRef.current.scrollToLocation()")," on a React Native ",(0,o.kt)("inlineCode",{parentName:"p"},"FlatList")," or ",(0,o.kt)("inlineCode",{parentName:"p"},"SectionList")," will fail on occasion because it can't scroll to a location that hasn't been rendered yet."),(0,o.kt)("p",null,"The solution to this is implementing ",(0,o.kt)("inlineCode",{parentName:"p"},"onScrollToIndexFailed")," with some sort of recovery functionality to keep trying the scroll. This is a Higher Order Component (HOC) for ",(0,o.kt)("inlineCode",{parentName:"p"},"SectionList")," that handles this for us."),(0,o.kt)("p",null,"This component basically tries over and over to scroll to the requested location until it gets it right and no longer calls ",(0,o.kt)("inlineCode",{parentName:"p"},"onScrollToIndexFailed"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-jsx"},"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")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/d0e08e4a.b8c0db8a.js b/assets/js/d0e08e4a.f31c8019.js similarity index 99% rename from assets/js/d0e08e4a.b8c0db8a.js rename to assets/js/d0e08e4a.f31c8019.js index 9be70628..3b6c8de0 100644 --- a/assets/js/d0e08e4a.b8c0db8a.js +++ b/assets/js/d0e08e4a.f31c8019.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[6862],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var i=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function a(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var d=i.createContext({}),s=function(e){var t=i.useContext(d),n=t;return e&&(n="function"==typeof e?e(t):a(a({},t),e)),n},u=function(e){var t=s(e.components);return i.createElement(d.Provider,{value:t},e.children)},p={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},c=i.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,d=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),c=s(n),h=r,m=c["".concat(d,".").concat(h)]||c[h]||p[h]||o;return n?i.createElement(m,a(a({ref:t},u),{},{components:n})):i.createElement(m,a({ref:t},u))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,a=new Array(o);a[0]=c;var l={};for(var d in t)hasOwnProperty.call(t,d)&&(l[d]=t[d]);l.originalType=e,l.mdxType="string"==typeof e?e:r,a[1]=l;for(var s=2;s{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>a,default:()=>p,frontMatter:()=>o,metadata:()=>l,toc:()=>s});var i=n(7462),r=(n(7294),n(3905));const o={title:"Patching/Building Android .aar From Source",description:"Instructions for updating the RN Android source code",tags:["Debug","Guide","Android"],last_update:{author:"Yulian Glukhenko"},publish_date:new Date("2022-10-09T00:00:00.000Z")},a=void 0,l={unversionedId:"recipes/PatchingBuildingAndroid",id:"recipes/PatchingBuildingAndroid",title:"Patching/Building Android .aar From Source",description:"Instructions for updating the RN Android source code",source:"@site/docs/recipes/PatchingBuildingAndroid.md",sourceDirName:"recipes",slug:"/recipes/PatchingBuildingAndroid",permalink:"/docs/recipes/PatchingBuildingAndroid",draft:!1,tags:[{label:"Debug",permalink:"/docs/tags/debug"},{label:"Guide",permalink:"/docs/tags/guide"},{label:"Android",permalink:"/docs/tags/android"}],version:"current",lastUpdatedBy:"Yulian Glukhenko",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Patching/Building Android .aar From Source",description:"Instructions for updating the RN Android source code",tags:["Debug","Guide","Android"],last_update:{author:"Yulian Glukhenko"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Migrating to MMKV",permalink:"/docs/recipes/MigratingToMMKV"},next:{title:"Pristine Expo Project",permalink:"/docs/recipes/PristineExpoProject"}},d={},s=[{value:"Why?",id:"why",level:3},{value:"Official Guides",id:"official-guides",level:3},{value:"Steps",id:"steps",level:3}],u={toc:s};function p(e){let{components:t,...o}=e;return(0,r.kt)("wrapper",(0,i.Z)({},u,o,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h3",{id:"why"},"Why?"),(0,r.kt)("p",null,"Sometimes, a situation arises when you might want to update the react-native Android source code without upgrading react-native itself. For example, there's a ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/facebook/react-native/issues/33375"},"new bug")," on Android 12 where the application crashes due to some bug with the animation queue. The potential fix is available on the ",(0,r.kt)("inlineCode",{parentName:"p"},"main"),' (unreleased) branch, but your app version is a few patches behind. Another situation is when you simply can\'t upgrade your react-native version yet, but need a fix from future version. In these cases, you can use this approach to "patch" your Android source files and build new .aar binary and use that for your app.'),(0,r.kt)("h3",{id:"official-guides"},"Official Guides"),(0,r.kt)("p",null,"The official steps to build from source are provided by react-native and you can find them ",(0,r.kt)("a",{parentName:"p",href:"https://reactnative.dev/contributing/how-to-build-from-source"},"here.")),(0,r.kt)("p",null,'The guid is fairly generic and redundant in some cases if you already have a sufficient react-native development environment setup. The steps below describe what has "worked for me" and they may or may not apply to everyone.'),(0,r.kt)("h3",{id:"steps"},"Steps"),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"1 - Fork React-Native And Clone")),(0,r.kt)("p",null,"Go to ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/facebook/react-native"},"Github")," and fork react-native. Pull the forked code down to your system."),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note: The official instructions tell you to clone react-native into your project's ",(0,r.kt)("inlineCode",{parentName:"p"},"node_modules"),". Don't do this. Just pull it down into your favorite development directory.")),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"2 - Checkout the Correct Commit")),(0,r.kt)("p",null,"If you are following this guide, you are most likely trying to patch react-native Android source files at a specific older version. The easiest way to do this is to checkout the commit specified in the respective version's git tag.\n",(0,r.kt)("img",{alt:"Branch and Commit History",src:n(7252).Z,width:"1221",height:"783"})),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"3 - Install Dependencies")),(0,r.kt)("p",null,"Just type ",(0,r.kt)("inlineCode",{parentName:"p"},"yarn"),"."),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"4 - Configure the SDK")),(0,r.kt)("p",null,"You will need the version of the SDk specifice in the ",(0,r.kt)("inlineCode",{parentName:"p"},"./ReactAndroid/build.gradle")," for ",(0,r.kt)("inlineCode",{parentName:"p"},"compileSdkVersion"),". You can install it via Android Studio.\n",(0,r.kt)("img",{alt:"Android SDK Configuration",src:n(34).Z,width:"1141",height:"141"})),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"5 - Configure the NDK")),(0,r.kt)("p",null,"Check the ",(0,r.kt)("inlineCode",{parentName:"p"},"./gradle.properties")," file, ",(0,r.kt)("inlineCode",{parentName:"p"},"ANDROID_NDK_VERSION")," key, for the version needed. This can be installed from Android Studio as well.\n",(0,r.kt)("img",{alt:"Android NDK Configuration",src:n(6332).Z,width:"1057",height:"224"})),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note: The official docs provide links to NDK archives. Installing through Android Studio is probably easer. One caveat is that I didn't find arm architecture NDKs in Android Studio. It's not a big deal though to use those since you won't do this often.")),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"6 - Configure Paths")),(0,r.kt)("p",null,"Create a ",(0,r.kt)("inlineCode",{parentName:"p"},"local.properties")," file in the root. This file is gitignored and should be kept as so."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"sdk.dir=/Users/path/to/sdk\nndk.dir=/Users/path/to/ndk\n")),(0,r.kt)("p",null,"Your SDK path is in your Library files (if installed through Android Studio).Your NDK path is inside the SDK path. This is what mine looks like:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"sdk.dir=/Users/~Usernamehere~/Library/Android/sdk\nndk.dir=/Users/~Usernamehere~/Library/Android/sdk/ndk/21.4.7075529\n")),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note: The official guides also have you setup shell paths with the same values. Not sure if this is needed. It wasn't for me.")),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"7 - Make the Necessary Changes")),(0,r.kt)("p",null,"Now, you can make any changes in the ",(0,r.kt)("inlineCode",{parentName:"p"},"./ReactAndroid")," folder."),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"8 - Build")),(0,r.kt)("p",null,"Run the following command in the root:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"arch -x86_64 ./gradlew :ReactAndroid:installArchives --no-daemon\n")),(0,r.kt)("p",null,"The first time you run this, it'll take some time. It will also report any syntax/type errors. It will also report any configuration errors."),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note: If you installed an arm compatible NDK, omit the ",(0,r.kt)("inlineCode",{parentName:"p"},"arch -x86_64")," from the command")),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"9 - Commit and Push")),(0,r.kt)("p",null,"In your ",(0,r.kt)("inlineCode",{parentName:"p"},".gitignore"),", remove the line which ignores the ",(0,r.kt)("inlineCode",{parentName:"p"},"/android/")," directory. We'll want to commit this build output from step 7. Commit and push your fork."),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"10 - Update Project's React-Native")),(0,r.kt)("p",null,"Now that you have build and pushed your changes, you can reference that in your application's ",(0,r.kt)("inlineCode",{parentName:"p"},"package.json"),".\n","![package json]","(<../../static//img/PatchingBuildingAndroid"),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note: You'll most likely need to delete/re-install your node_modules as well as run ",(0,r.kt)("inlineCode",{parentName:"p"},"./android/gradlew clean"),".")))}p.isMDXComponent=!0},7252:(e,t,n)=>{n.d(t,{Z:()=>i});const i=n.p+"assets/images/PatchingBuildingAndroid(1)-68d9226be54d8c740b0c14cf4f3679e0.jpg"},34:(e,t,n)=>{n.d(t,{Z:()=>i});const i=n.p+"assets/images/PatchingBuildingAndroid(2)-13515d97cfab79a43688abf9c36d6216.jpg"},6332:(e,t,n)=>{n.d(t,{Z:()=>i});const i=n.p+"assets/images/PatchingBuildingAndroid(3)-cf31a6c6fe16cd2a2b16e76fac43081f.jpg"}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[6862],{3905:(e,t,n)=>{n.d(t,{Zo:()=>u,kt:()=>h});var i=n(7294);function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,i)}return n}function a(e){for(var t=1;t=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(i=0;i=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}var d=i.createContext({}),s=function(e){var t=i.useContext(d),n=t;return e&&(n="function"==typeof e?e(t):a(a({},t),e)),n},u=function(e){var t=s(e.components);return i.createElement(d.Provider,{value:t},e.children)},p={inlineCode:"code",wrapper:function(e){var t=e.children;return i.createElement(i.Fragment,{},t)}},c=i.forwardRef((function(e,t){var n=e.components,r=e.mdxType,o=e.originalType,d=e.parentName,u=l(e,["components","mdxType","originalType","parentName"]),c=s(n),h=r,m=c["".concat(d,".").concat(h)]||c[h]||p[h]||o;return n?i.createElement(m,a(a({ref:t},u),{},{components:n})):i.createElement(m,a({ref:t},u))}));function h(e,t){var n=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var o=n.length,a=new Array(o);a[0]=c;var l={};for(var d in t)hasOwnProperty.call(t,d)&&(l[d]=t[d]);l.originalType=e,l.mdxType="string"==typeof e?e:r,a[1]=l;for(var s=2;s{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>a,default:()=>p,frontMatter:()=>o,metadata:()=>l,toc:()=>s});var i=n(7462),r=(n(7294),n(3905));const o={title:"Patching/Building Android .aar From Source",description:"Instructions for updating the RN Android source code",tags:["Debug","Guide","Android"],last_update:{author:"Yulian Glukhenko"},publish_date:new Date("2022-10-09T00:00:00.000Z")},a=void 0,l={unversionedId:"recipes/PatchingBuildingAndroid",id:"recipes/PatchingBuildingAndroid",title:"Patching/Building Android .aar From Source",description:"Instructions for updating the RN Android source code",source:"@site/docs/recipes/PatchingBuildingAndroid.md",sourceDirName:"recipes",slug:"/recipes/PatchingBuildingAndroid",permalink:"/docs/recipes/PatchingBuildingAndroid",draft:!1,tags:[{label:"Debug",permalink:"/docs/tags/debug"},{label:"Guide",permalink:"/docs/tags/guide"},{label:"Android",permalink:"/docs/tags/android"}],version:"current",lastUpdatedBy:"Yulian Glukhenko",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Patching/Building Android .aar From Source",description:"Instructions for updating the RN Android source code",tags:["Debug","Guide","Android"],last_update:{author:"Yulian Glukhenko"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Migrating to MMKV",permalink:"/docs/recipes/MigratingToMMKV"},next:{title:"Pristine Expo Project",permalink:"/docs/recipes/PristineExpoProject"}},d={},s=[{value:"Why?",id:"why",level:3},{value:"Official Guides",id:"official-guides",level:3},{value:"Steps",id:"steps",level:3}],u={toc:s};function p(e){let{components:t,...o}=e;return(0,r.kt)("wrapper",(0,i.Z)({},u,o,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h3",{id:"why"},"Why?"),(0,r.kt)("p",null,"Sometimes, a situation arises when you might want to update the react-native Android source code without upgrading react-native itself. For example, there's a ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/facebook/react-native/issues/33375"},"new bug")," on Android 12 where the application crashes due to some bug with the animation queue. The potential fix is available on the ",(0,r.kt)("inlineCode",{parentName:"p"},"main"),' (unreleased) branch, but your app version is a few patches behind. Another situation is when you simply can\'t upgrade your react-native version yet, but need a fix from future version. In these cases, you can use this approach to "patch" your Android source files and build new .aar binary and use that for your app.'),(0,r.kt)("h3",{id:"official-guides"},"Official Guides"),(0,r.kt)("p",null,"The official steps to build from source are provided by react-native and you can find them ",(0,r.kt)("a",{parentName:"p",href:"https://reactnative.dev/contributing/how-to-build-from-source"},"here.")),(0,r.kt)("p",null,'The guid is fairly generic and redundant in some cases if you already have a sufficient react-native development environment setup. The steps below describe what has "worked for me" and they may or may not apply to everyone.'),(0,r.kt)("h3",{id:"steps"},"Steps"),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"1 - Fork React-Native And Clone")),(0,r.kt)("p",null,"Go to ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/facebook/react-native"},"Github")," and fork react-native. Pull the forked code down to your system."),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note: The official instructions tell you to clone react-native into your project's ",(0,r.kt)("inlineCode",{parentName:"p"},"node_modules"),". Don't do this. Just pull it down into your favorite development directory.")),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"2 - Checkout the Correct Commit")),(0,r.kt)("p",null,"If you are following this guide, you are most likely trying to patch react-native Android source files at a specific older version. The easiest way to do this is to checkout the commit specified in the respective version's git tag.\n",(0,r.kt)("img",{alt:"Branch and Commit History",src:n(7252).Z,width:"1221",height:"783"})),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"3 - Install Dependencies")),(0,r.kt)("p",null,"Just type ",(0,r.kt)("inlineCode",{parentName:"p"},"yarn"),"."),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"4 - Configure the SDK")),(0,r.kt)("p",null,"You will need the version of the SDk specifice in the ",(0,r.kt)("inlineCode",{parentName:"p"},"./ReactAndroid/build.gradle")," for ",(0,r.kt)("inlineCode",{parentName:"p"},"compileSdkVersion"),". You can install it via Android Studio.\n",(0,r.kt)("img",{alt:"Android SDK Configuration",src:n(34).Z,width:"1141",height:"141"})),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"5 - Configure the NDK")),(0,r.kt)("p",null,"Check the ",(0,r.kt)("inlineCode",{parentName:"p"},"./gradle.properties")," file, ",(0,r.kt)("inlineCode",{parentName:"p"},"ANDROID_NDK_VERSION")," key, for the version needed. This can be installed from Android Studio as well.\n",(0,r.kt)("img",{alt:"Android NDK Configuration",src:n(6332).Z,width:"1057",height:"224"})),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note: The official docs provide links to NDK archives. Installing through Android Studio is probably easer. One caveat is that I didn't find arm architecture NDKs in Android Studio. It's not a big deal though to use those since you won't do this often.")),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"6 - Configure Paths")),(0,r.kt)("p",null,"Create a ",(0,r.kt)("inlineCode",{parentName:"p"},"local.properties")," file in the root. This file is gitignored and should be kept as so."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"sdk.dir=/Users/path/to/sdk\nndk.dir=/Users/path/to/ndk\n")),(0,r.kt)("p",null,"Your SDK path is in your Library files (if installed through Android Studio).Your NDK path is inside the SDK path. This is what mine looks like:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"sdk.dir=/Users/~Usernamehere~/Library/Android/sdk\nndk.dir=/Users/~Usernamehere~/Library/Android/sdk/ndk/21.4.7075529\n")),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note: The official guides also have you setup shell paths with the same values. Not sure if this is needed. It wasn't for me.")),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"7 - Make the Necessary Changes")),(0,r.kt)("p",null,"Now, you can make any changes in the ",(0,r.kt)("inlineCode",{parentName:"p"},"./ReactAndroid")," folder."),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"8 - Build")),(0,r.kt)("p",null,"Run the following command in the root:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre"},"arch -x86_64 ./gradlew :ReactAndroid:installArchives --no-daemon\n")),(0,r.kt)("p",null,"The first time you run this, it'll take some time. It will also report any syntax/type errors. It will also report any configuration errors."),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note: If you installed an arm compatible NDK, omit the ",(0,r.kt)("inlineCode",{parentName:"p"},"arch -x86_64")," from the command")),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"9 - Commit and Push")),(0,r.kt)("p",null,"In your ",(0,r.kt)("inlineCode",{parentName:"p"},".gitignore"),", remove the line which ignores the ",(0,r.kt)("inlineCode",{parentName:"p"},"/android/")," directory. We'll want to commit this build output from step 7. Commit and push your fork."),(0,r.kt)("p",null,(0,r.kt)("em",{parentName:"p"},"10 - Update Project's React-Native")),(0,r.kt)("p",null,"Now that you have build and pushed your changes, you can reference that in your application's ",(0,r.kt)("inlineCode",{parentName:"p"},"package.json"),".\n","![package json]","(<../../static//img/PatchingBuildingAndroid"),(0,r.kt)("blockquote",null,(0,r.kt)("p",{parentName:"blockquote"},"Note: You'll most likely need to delete/re-install your node_modules as well as run ",(0,r.kt)("inlineCode",{parentName:"p"},"./android/gradlew clean"),".")))}p.isMDXComponent=!0},7252:(e,t,n)=>{n.d(t,{Z:()=>i});const i=n.p+"assets/images/PatchingBuildingAndroid(1)-68d9226be54d8c740b0c14cf4f3679e0.jpg"},34:(e,t,n)=>{n.d(t,{Z:()=>i});const i=n.p+"assets/images/PatchingBuildingAndroid(2)-13515d97cfab79a43688abf9c36d6216.jpg"},6332:(e,t,n)=>{n.d(t,{Z:()=>i});const i=n.p+"assets/images/PatchingBuildingAndroid(3)-cf31a6c6fe16cd2a2b16e76fac43081f.jpg"}}]); \ No newline at end of file diff --git a/assets/js/d63d2b89.4a64953a.js b/assets/js/d63d2b89.66cc3de2.js similarity index 99% rename from assets/js/d63d2b89.4a64953a.js rename to assets/js/d63d2b89.66cc3de2.js index 5e337edf..3e16714d 100644 --- a/assets/js/d63d2b89.4a64953a.js +++ b/assets/js/d63d2b89.66cc3de2.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[1981],{7875:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>r,toc:()=>p});var l=n(7462),o=(n(7294),n(3905)),a=n(2004);const i={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")},s="SelectField using `react-native-bottom-sheet`",r={unversionedId:"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",source:"@site/docs/recipes/SelectFieldWithBottomSheet.mdx",sourceDirName:"recipes",slug:"/recipes/SelectFieldWithBottomSheet",permalink:"/docs/recipes/SelectFieldWithBottomSheet",draft:!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:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",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:"Staying With Expo",permalink:"/docs/recipes/StayingWithExpo"}},d={},p=[{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}],c={toc:p};function u(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,l.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"selectfield-using-react-native-bottom-sheet"},"SelectField using ",(0,o.kt)("inlineCode",{parentName:"h1"},"react-native-bottom-sheet")),(0,o.kt)("p",null,"In this guide, we'll be creating a ",(0,o.kt)("inlineCode",{parentName:"p"},"SelectField")," component by extending the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," with a scrollable options View and additional props to handle its customization."),(0,o.kt)(a.Z,{width:"100%",controls:!0,url:"https://user-images.githubusercontent.com/1775841/219038677-bcc9c61d-1776-4aad-bb50-1e932721bc04.mp4",mdxType:"ReactPlayer"}),(0,o.kt)("p",null,"We will be using the ",(0,o.kt)("a",{parentName:"p",href:"https://gorhom.github.io/react-native-bottom-sheet/"},(0,o.kt)("inlineCode",{parentName:"a"},"react-native-bottom-sheet"))," library for the options list, the ",(0,o.kt)("inlineCode",{parentName:"p"},"ListItem")," component for displaying individual options, and the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," component for opening the options list and displaying selected options."),(0,o.kt)("p",null,"There are many ways you can setup ",(0,o.kt)("inlineCode",{parentName:"p"},"react-native-bottom-sheet")," to function as a ",(0,o.kt)("inlineCode",{parentName:"p"},"Picker"),". We'll keep it simple - pressing the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," will open the options-list. Pressing the option(s) will update the value via callback. You can customize this to fit your usecase."),(0,o.kt)("h2",{id:"1-installation"},"1. Installation"),(0,o.kt)("p",null,"Let's start by installing the necessary dependencies. You can see complete installation instructions for ",(0,o.kt)("inlineCode",{parentName:"p"},"react-native-bottom-sheet")," ",(0,o.kt)("a",{parentName:"p",href:"https://gorhom.github.io/react-native-bottom-sheet/"},"here"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"yarn add @gorhom/bottom-sheet@^4\n")),(0,o.kt)("p",null,"The library requires the ",(0,o.kt)("inlineCode",{parentName:"p"},"react-native-gesture-handler")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"react-native-reanimated")," dependencies, but if you're using a newer Ignite boilerplate version, those should already be installed. Just check your ",(0,o.kt)("inlineCode",{parentName:"p"},"package.json")," file and if you don't see them, follow these steps:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"yarn add react-native-reanimated react-native-gesture-handler\n# or\nexpo install react-native-reanimated react-native-gesture-handler\n")),(0,o.kt)("h2",{id:"2-create-the-selectfieldtsx-component-file"},"2. Create the ",(0,o.kt)("inlineCode",{parentName:"h2"},"SelectField.tsx")," Component File"),(0,o.kt)("p",null,"Instead of extending the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," component with more props and functionality, we'll be creating a wrapper for the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," component that contains additional functionality."),(0,o.kt)("p",null,"We'll start by creating a new file in the components directory."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"touch ./app/components/SelectField.tsx\n")),(0,o.kt)("p",null,"Let's add some preliminary code to the file. Since the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextInput")," has its own touch handlers for focus, we'll want to disable that by wrapping it in a ",(0,o.kt)("inlineCode",{parentName:"p"},"View")," with no pointer-events. The new ",(0,o.kt)("inlineCode",{parentName:"p"},"TouchableOpacity")," will trigger our options sheet."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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<\n TextFieldProps,\n "ref" | "onValueChange" | "onChange" | "value"\n > {}\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 =\n TextFieldProps.editable === false || TextFieldProps.status === "disabled";\n\n useImperativeHandle(ref, () => ({}));\n\n return (\n <>\n \n \n \n \n \n \n );\n});\n')),(0,o.kt)("details",null,(0,o.kt)("summary",null,"Demo Preview"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'import { SelectField } from "../components/SelectField";\n\nfunction FavoriteNBATeamsScreen() {\n return (\n \n );\n}\n')),(0,o.kt)("p",null,(0,o.kt)("img",{parentName:"p",src:"https://user-images.githubusercontent.com/1775841/219003766-5331678b-a5b9-42fb-b393-3851bf2ebeaf.jpg",alt:"yulolimum-capture-2023-02-15--02-34-52"}))),(0,o.kt)("h2",{id:"3-add-new-props-and-customize-the-textfield"},"3. Add New Props and Customize the TextField"),(0,o.kt)("p",null,"Now, we can start modifying the code we added in the previous step to support multiple options as well as making the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," ",(0,o.kt)("em",{parentName:"p"},"look")," like a ",(0,o.kt)("inlineCode",{parentName:"p"},"SelectField"),"."),(0,o.kt)("h3",{id:"add-a-caret-icon-accessory"},"Add a Caret Icon Accessory"),(0,o.kt)("p",null,"Let's add an accessory to the input to make it look like a ",(0,o.kt)("inlineCode",{parentName:"p"},"SelectField"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},' (\n \n )}\n/>\n')),(0,o.kt)("h3",{id:"add-props"},"Add Props"),(0,o.kt)("p",null,"The ",(0,o.kt)("inlineCode",{parentName:"p"},"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,o.kt)("inlineCode",{parentName:"p"},"SelectField")," guide, we'll be doing an array of objects."),(0,o.kt)("p",null,"We will support multi-select (by default) as well as a single select."),(0,o.kt)("p",null,"We will override the ",(0,o.kt)("inlineCode",{parentName:"p"},"value")," prop."),(0,o.kt)("p",null,"A new ",(0,o.kt)("inlineCode",{parentName:"p"},"renderValue")," prop can be used to format and display a custom text value. This can be useful when the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," is not multiline, but your ",(0,o.kt)("inlineCode",{parentName:"p"},"SelectField")," is."),(0,o.kt)("p",null,"Additionally, we'll add a new event callback called ",(0,o.kt)("inlineCode",{parentName:"p"},"onSelect")," since that makes more sense for a ",(0,o.kt)("inlineCode",{parentName:"p"},"SelectField"),". However, feel free to override ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField"),"'s ",(0,o.kt)("inlineCode",{parentName:"p"},"onChange")," if you prefer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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')),(0,o.kt)("h3",{id:"add-logic-to-display-selected-options"},"Add Logic to Display Selected Options"),(0,o.kt)("p",null,"We'll add some code to display the selected options inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField"),". This will attempt to use the ",(0,o.kt)("inlineCode",{parentName:"p"},"renderValue")," formatter function and fallback to a joined string."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'const valueString =\n renderValue?.(value) ??\n value\n .map((v) => options.find((o) => o.value === v)?.label)\n .filter(Boolean)\n .join(", ");\n')),(0,o.kt)("h3",{id:"full-code-for-this-step"},"Full Code For This Step"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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 =\n 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 \n )}\n // success-line-end\n />\n \n \n \n );\n});\n')),(0,o.kt)("details",null,(0,o.kt)("summary",null,"Demo Preview"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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,o.kt)("p",null,(0,o.kt)("img",{parentName:"p",src:"https://user-images.githubusercontent.com/1775841/219011088-688696b8-05a6-43e8-8e9b-43578320d70a.jpg",alt:"yulolimum-capture-2023-02-15--03-07-33"}))),(0,o.kt)("h2",{id:"4-add-the-sheet-components"},"4. Add the Sheet Components"),(0,o.kt)("p",null,"In this step, we'll be adding the ",(0,o.kt)("inlineCode",{parentName:"p"},"BottomSheetModal")," and related components and setting up the touch-events to show/hide it."),(0,o.kt)("h3",{id:"add-the-bottomsheetmodalprovider"},"Add the ",(0,o.kt)("inlineCode",{parentName:"h3"},"BottomSheetModalProvider")),(0,o.kt)("p",null,"Since we will be using the ",(0,o.kt)("inlineCode",{parentName:"p"},"BottomSheetModal")," component instead of ",(0,o.kt)("inlineCode",{parentName:"p"},"BottomSheet"),", we will need to add a provider to your entry file."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx",metastring:'title="./app/app.tsx"',title:'"./app/app.tsx"'},'//...\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')),(0,o.kt)("h3",{id:"add-the-necessary-components-to-selectfield"},"Add the Necessary Components to ",(0,o.kt)("inlineCode",{parentName:"h3"},"SelectField")),(0,o.kt)("p",null,"Now we will add the UI components that will display our options. This will be a basic example and can be customized as needed."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'// 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";\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 disabled =\n 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 \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: ViewStyle = {\n paddingHorizontal: spacing.large,\n};\n\nconst $listItem: ViewStyle = {\n paddingHorizontal: spacing.large,\n};\n// success-line-end\n')),(0,o.kt)("details",null,(0,o.kt)("summary",null,"Demo Preview"),(0,o.kt)("p",null,(0,o.kt)("img",{parentName:"p",src:"https://user-images.githubusercontent.com/1775841/219029547-3c92dbdc-5f04-4f02-a82e-0af9392af6ad.gif",alt:"yulolimum-capture-2023-02-15--04-38-11"}))),(0,o.kt)("h2",{id:"5-add-selected-state-to-options-and-hook-up-callback"},"5. Add Selected State to Options and Hook Up Callback"),(0,o.kt)("p",null,"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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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";\n// success-line\nimport { colors, spacing } from "../theme";\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\n const disabled =\n 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 \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: ViewStyle = {\n paddingHorizontal: spacing.large,\n};\n\nconst $listItem: ViewStyle = {\n paddingHorizontal: spacing.large,\n};\n')),(0,o.kt)("p",null,"And we're done!"),(0,o.kt)("details",null,(0,o.kt)("summary",null,"Demo Preview"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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,o.kt)("p",null,(0,o.kt)("img",{parentName:"p",src:"https://user-images.githubusercontent.com/1775841/219036892-e7e38288-b859-487d-b51a-ca67f91c83ff.gif",alt:"yulolimum-capture-2023-02-15--05-11-11"}))))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[1981],{7875:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>s,default:()=>u,frontMatter:()=>i,metadata:()=>r,toc:()=>p});var l=n(7462),o=(n(7294),n(3905)),a=n(2004);const i={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")},s="SelectField using `react-native-bottom-sheet`",r={unversionedId:"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",source:"@site/docs/recipes/SelectFieldWithBottomSheet.mdx",sourceDirName:"recipes",slug:"/recipes/SelectFieldWithBottomSheet",permalink:"/docs/recipes/SelectFieldWithBottomSheet",draft:!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:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",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:"Staying With Expo",permalink:"/docs/recipes/StayingWithExpo"}},d={},p=[{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}],c={toc:p};function u(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,l.Z)({},c,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"selectfield-using-react-native-bottom-sheet"},"SelectField using ",(0,o.kt)("inlineCode",{parentName:"h1"},"react-native-bottom-sheet")),(0,o.kt)("p",null,"In this guide, we'll be creating a ",(0,o.kt)("inlineCode",{parentName:"p"},"SelectField")," component by extending the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," with a scrollable options View and additional props to handle its customization."),(0,o.kt)(a.Z,{width:"100%",controls:!0,url:"https://user-images.githubusercontent.com/1775841/219038677-bcc9c61d-1776-4aad-bb50-1e932721bc04.mp4",mdxType:"ReactPlayer"}),(0,o.kt)("p",null,"We will be using the ",(0,o.kt)("a",{parentName:"p",href:"https://gorhom.github.io/react-native-bottom-sheet/"},(0,o.kt)("inlineCode",{parentName:"a"},"react-native-bottom-sheet"))," library for the options list, the ",(0,o.kt)("inlineCode",{parentName:"p"},"ListItem")," component for displaying individual options, and the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," component for opening the options list and displaying selected options."),(0,o.kt)("p",null,"There are many ways you can setup ",(0,o.kt)("inlineCode",{parentName:"p"},"react-native-bottom-sheet")," to function as a ",(0,o.kt)("inlineCode",{parentName:"p"},"Picker"),". We'll keep it simple - pressing the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," will open the options-list. Pressing the option(s) will update the value via callback. You can customize this to fit your usecase."),(0,o.kt)("h2",{id:"1-installation"},"1. Installation"),(0,o.kt)("p",null,"Let's start by installing the necessary dependencies. You can see complete installation instructions for ",(0,o.kt)("inlineCode",{parentName:"p"},"react-native-bottom-sheet")," ",(0,o.kt)("a",{parentName:"p",href:"https://gorhom.github.io/react-native-bottom-sheet/"},"here"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"yarn add @gorhom/bottom-sheet@^4\n")),(0,o.kt)("p",null,"The library requires the ",(0,o.kt)("inlineCode",{parentName:"p"},"react-native-gesture-handler")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"react-native-reanimated")," dependencies, but if you're using a newer Ignite boilerplate version, those should already be installed. Just check your ",(0,o.kt)("inlineCode",{parentName:"p"},"package.json")," file and if you don't see them, follow these steps:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"yarn add react-native-reanimated react-native-gesture-handler\n# or\nexpo install react-native-reanimated react-native-gesture-handler\n")),(0,o.kt)("h2",{id:"2-create-the-selectfieldtsx-component-file"},"2. Create the ",(0,o.kt)("inlineCode",{parentName:"h2"},"SelectField.tsx")," Component File"),(0,o.kt)("p",null,"Instead of extending the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," component with more props and functionality, we'll be creating a wrapper for the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," component that contains additional functionality."),(0,o.kt)("p",null,"We'll start by creating a new file in the components directory."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-bash"},"touch ./app/components/SelectField.tsx\n")),(0,o.kt)("p",null,"Let's add some preliminary code to the file. Since the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextInput")," has its own touch handlers for focus, we'll want to disable that by wrapping it in a ",(0,o.kt)("inlineCode",{parentName:"p"},"View")," with no pointer-events. The new ",(0,o.kt)("inlineCode",{parentName:"p"},"TouchableOpacity")," will trigger our options sheet."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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<\n TextFieldProps,\n "ref" | "onValueChange" | "onChange" | "value"\n > {}\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 =\n TextFieldProps.editable === false || TextFieldProps.status === "disabled";\n\n useImperativeHandle(ref, () => ({}));\n\n return (\n <>\n \n \n \n \n \n \n );\n});\n')),(0,o.kt)("details",null,(0,o.kt)("summary",null,"Demo Preview"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'import { SelectField } from "../components/SelectField";\n\nfunction FavoriteNBATeamsScreen() {\n return (\n \n );\n}\n')),(0,o.kt)("p",null,(0,o.kt)("img",{parentName:"p",src:"https://user-images.githubusercontent.com/1775841/219003766-5331678b-a5b9-42fb-b393-3851bf2ebeaf.jpg",alt:"yulolimum-capture-2023-02-15--02-34-52"}))),(0,o.kt)("h2",{id:"3-add-new-props-and-customize-the-textfield"},"3. Add New Props and Customize the TextField"),(0,o.kt)("p",null,"Now, we can start modifying the code we added in the previous step to support multiple options as well as making the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," ",(0,o.kt)("em",{parentName:"p"},"look")," like a ",(0,o.kt)("inlineCode",{parentName:"p"},"SelectField"),"."),(0,o.kt)("h3",{id:"add-a-caret-icon-accessory"},"Add a Caret Icon Accessory"),(0,o.kt)("p",null,"Let's add an accessory to the input to make it look like a ",(0,o.kt)("inlineCode",{parentName:"p"},"SelectField"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},' (\n \n )}\n/>\n')),(0,o.kt)("h3",{id:"add-props"},"Add Props"),(0,o.kt)("p",null,"The ",(0,o.kt)("inlineCode",{parentName:"p"},"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,o.kt)("inlineCode",{parentName:"p"},"SelectField")," guide, we'll be doing an array of objects."),(0,o.kt)("p",null,"We will support multi-select (by default) as well as a single select."),(0,o.kt)("p",null,"We will override the ",(0,o.kt)("inlineCode",{parentName:"p"},"value")," prop."),(0,o.kt)("p",null,"A new ",(0,o.kt)("inlineCode",{parentName:"p"},"renderValue")," prop can be used to format and display a custom text value. This can be useful when the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField")," is not multiline, but your ",(0,o.kt)("inlineCode",{parentName:"p"},"SelectField")," is."),(0,o.kt)("p",null,"Additionally, we'll add a new event callback called ",(0,o.kt)("inlineCode",{parentName:"p"},"onSelect")," since that makes more sense for a ",(0,o.kt)("inlineCode",{parentName:"p"},"SelectField"),". However, feel free to override ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField"),"'s ",(0,o.kt)("inlineCode",{parentName:"p"},"onChange")," if you prefer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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')),(0,o.kt)("h3",{id:"add-logic-to-display-selected-options"},"Add Logic to Display Selected Options"),(0,o.kt)("p",null,"We'll add some code to display the selected options inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"TextField"),". This will attempt to use the ",(0,o.kt)("inlineCode",{parentName:"p"},"renderValue")," formatter function and fallback to a joined string."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'const valueString =\n renderValue?.(value) ??\n value\n .map((v) => options.find((o) => o.value === v)?.label)\n .filter(Boolean)\n .join(", ");\n')),(0,o.kt)("h3",{id:"full-code-for-this-step"},"Full Code For This Step"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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 =\n 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 \n )}\n // success-line-end\n />\n \n \n \n );\n});\n')),(0,o.kt)("details",null,(0,o.kt)("summary",null,"Demo Preview"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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,o.kt)("p",null,(0,o.kt)("img",{parentName:"p",src:"https://user-images.githubusercontent.com/1775841/219011088-688696b8-05a6-43e8-8e9b-43578320d70a.jpg",alt:"yulolimum-capture-2023-02-15--03-07-33"}))),(0,o.kt)("h2",{id:"4-add-the-sheet-components"},"4. Add the Sheet Components"),(0,o.kt)("p",null,"In this step, we'll be adding the ",(0,o.kt)("inlineCode",{parentName:"p"},"BottomSheetModal")," and related components and setting up the touch-events to show/hide it."),(0,o.kt)("h3",{id:"add-the-bottomsheetmodalprovider"},"Add the ",(0,o.kt)("inlineCode",{parentName:"h3"},"BottomSheetModalProvider")),(0,o.kt)("p",null,"Since we will be using the ",(0,o.kt)("inlineCode",{parentName:"p"},"BottomSheetModal")," component instead of ",(0,o.kt)("inlineCode",{parentName:"p"},"BottomSheet"),", we will need to add a provider to your entry file."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx",metastring:'title="./app/app.tsx"',title:'"./app/app.tsx"'},'//...\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')),(0,o.kt)("h3",{id:"add-the-necessary-components-to-selectfield"},"Add the Necessary Components to ",(0,o.kt)("inlineCode",{parentName:"h3"},"SelectField")),(0,o.kt)("p",null,"Now we will add the UI components that will display our options. This will be a basic example and can be customized as needed."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'// 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";\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 disabled =\n 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 \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: ViewStyle = {\n paddingHorizontal: spacing.large,\n};\n\nconst $listItem: ViewStyle = {\n paddingHorizontal: spacing.large,\n};\n// success-line-end\n')),(0,o.kt)("details",null,(0,o.kt)("summary",null,"Demo Preview"),(0,o.kt)("p",null,(0,o.kt)("img",{parentName:"p",src:"https://user-images.githubusercontent.com/1775841/219029547-3c92dbdc-5f04-4f02-a82e-0af9392af6ad.gif",alt:"yulolimum-capture-2023-02-15--04-38-11"}))),(0,o.kt)("h2",{id:"5-add-selected-state-to-options-and-hook-up-callback"},"5. Add Selected State to Options and Hook Up Callback"),(0,o.kt)("p",null,"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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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";\n// success-line\nimport { colors, spacing } from "../theme";\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\n const disabled =\n 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 \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: ViewStyle = {\n paddingHorizontal: spacing.large,\n};\n\nconst $listItem: ViewStyle = {\n paddingHorizontal: spacing.large,\n};\n')),(0,o.kt)("p",null,"And we're done!"),(0,o.kt)("details",null,(0,o.kt)("summary",null,"Demo Preview"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-tsx"},'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,o.kt)("p",null,(0,o.kt)("img",{parentName:"p",src:"https://user-images.githubusercontent.com/1775841/219036892-e7e38288-b859-487d-b51a-ca67f91c83ff.gif",alt:"yulolimum-capture-2023-02-15--05-11-11"}))))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/d6ab422f.d0650c30.js b/assets/js/d6ab422f.172260f6.js similarity index 98% rename from assets/js/d6ab422f.d0650c30.js rename to assets/js/d6ab422f.172260f6.js index 8ae18e96..561425f5 100644 --- a/assets/js/d6ab422f.d0650c30.js +++ b/assets/js/d6ab422f.172260f6.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[6605],{3905:(e,n,t)=>{t.d(n,{Zo:()=>c,kt:()=>m});var r=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function i(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var s=r.createContext({}),p=function(e){var n=r.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},c=function(e){var n=p(e.components);return r.createElement(s.Provider,{value:n},e.children)},u={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},d=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=p(t),m=a,v=d["".concat(s,".").concat(m)]||d[m]||u[m]||o;return t?r.createElement(v,i(i({ref:n},c),{},{components:t})):r.createElement(v,i({ref:n},c))}));function m(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,i=new Array(o);i[0]=d;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l.mdxType="string"==typeof e?e:a,i[1]=l;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var r=t(7462),a=(t(7294),t(3905));const o={title:"Environment Variables",description:"A universal way to set up environment variables for bare and Expo projects",tags:["Guide","Environment Variables"],last_update:{author:"Frank Calise"},publish_date:new Date("2022-10-11T00:00:00.000Z")},i="Environment Variables",l={unversionedId:"recipes/EnvironmentVariables",id:"recipes/EnvironmentVariables",title:"Environment Variables",description:"A universal way to set up environment variables for bare and Expo projects",source:"@site/docs/recipes/EnvironmentVariables.md",sourceDirName:"recipes",slug:"/recipes/EnvironmentVariables",permalink:"/docs/recipes/EnvironmentVariables",draft:!1,tags:[{label:"Guide",permalink:"/docs/tags/guide"},{label:"Environment Variables",permalink:"/docs/tags/environment-variables"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Environment Variables",description:"A universal way to set up environment variables for bare and Expo projects",tags:["Guide","Environment Variables"],last_update:{author:"Frank Calise"},publish_date:"2022-10-11T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"EAS Update",permalink:"/docs/recipes/EASUpdate"},next:{title:"Generator for Component Tests",permalink:"/docs/recipes/GeneratorComponentTests"}},s={},p=[{value:"Setup",id:"setup",level:2},{value:"Install",id:"install",level:3},{value:"Configure",id:"configure",level:3},{value:"Usage",id:"usage",level:2}],c={toc:p};function u(e){let{components:n,...t}=e;return(0,a.kt)("wrapper",(0,r.Z)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"environment-variables"},"Environment Variables"),(0,a.kt)("h2",{id:"setup"},"Setup"),(0,a.kt)("h3",{id:"install"},"Install"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"yarn add -D dotenv babel-plugin-inline-dotenv\n")),(0,a.kt)("h3",{id:"configure"},"Configure"),(0,a.kt)("p",null,"Add ",(0,a.kt)("inlineCode",{parentName:"p"},"inline-dotenv")," to your ",(0,a.kt)("inlineCode",{parentName:"p"},"babel.config.js")," or ",(0,a.kt)("inlineCode",{parentName:"p"},".babelrc"),", for example:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-javascript"},'// babel.config.js\n\nconst plugins = [\n [\n "@babel/plugin-proposal-decorators",\n {\n legacy: true,\n },\n ],\n ["@babel/plugin-proposal-optional-catch-binding"],\n "inline-dotenv",\n "react-native-reanimated/plugin", // NOTE: this must be last in the plugins\n];\n\nconst expoConfig = {\n presets: ["babel-preset-expo"],\n env: {\n production: {},\n },\n plugins,\n};\n')),(0,a.kt)("p",null,(0,a.kt)("em",{parentName:"p"},"Note: this configuration also works for a bare react-native app")),(0,a.kt)("h2",{id:"usage"},"Usage"),(0,a.kt)("p",null,"Create a ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file in the root of your project"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},'MY_VAR="MY_VALUE"\nKEEP_IN_MIND="THESE ARE NOT SECURE"\n')),(0,a.kt)("p",null,"You'll now have access to your values from the ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-javascript"},"console.log(process.env.MY_VAR); // results in: MY_VALUE\nconsole.log(process.env.KEEP_IN_MIND); // results in: THESE ARE NOT SECURE\n")),(0,a.kt)("p",null,"If making changes to the environment variables, you'll have to restart your metro server (and possibly clear cache)"))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[6605],{3905:(e,n,t)=>{t.d(n,{Zo:()=>c,kt:()=>m});var r=t(7294);function a(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function i(e){for(var n=1;n=0||(a[t]=e[t]);return a}(e,n);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(a[t]=e[t])}return a}var s=r.createContext({}),p=function(e){var n=r.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):i(i({},n),e)),t},c=function(e){var n=p(e.components);return r.createElement(s.Provider,{value:n},e.children)},u={inlineCode:"code",wrapper:function(e){var n=e.children;return r.createElement(r.Fragment,{},n)}},d=r.forwardRef((function(e,n){var t=e.components,a=e.mdxType,o=e.originalType,s=e.parentName,c=l(e,["components","mdxType","originalType","parentName"]),d=p(t),m=a,v=d["".concat(s,".").concat(m)]||d[m]||u[m]||o;return t?r.createElement(v,i(i({ref:n},c),{},{components:t})):r.createElement(v,i({ref:n},c))}));function m(e,n){var t=arguments,a=n&&n.mdxType;if("string"==typeof e||a){var o=t.length,i=new Array(o);i[0]=d;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l.mdxType="string"==typeof e?e:a,i[1]=l;for(var p=2;p{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var r=t(7462),a=(t(7294),t(3905));const o={title:"Environment Variables",description:"A universal way to set up environment variables for bare and Expo projects",tags:["Guide","Environment Variables"],last_update:{author:"Frank Calise"},publish_date:new Date("2022-10-11T00:00:00.000Z")},i="Environment Variables",l={unversionedId:"recipes/EnvironmentVariables",id:"recipes/EnvironmentVariables",title:"Environment Variables",description:"A universal way to set up environment variables for bare and Expo projects",source:"@site/docs/recipes/EnvironmentVariables.md",sourceDirName:"recipes",slug:"/recipes/EnvironmentVariables",permalink:"/docs/recipes/EnvironmentVariables",draft:!1,tags:[{label:"Guide",permalink:"/docs/tags/guide"},{label:"Environment Variables",permalink:"/docs/tags/environment-variables"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Environment Variables",description:"A universal way to set up environment variables for bare and Expo projects",tags:["Guide","Environment Variables"],last_update:{author:"Frank Calise"},publish_date:"2022-10-11T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"EAS Update",permalink:"/docs/recipes/EASUpdate"},next:{title:"Generator for Component Tests",permalink:"/docs/recipes/GeneratorComponentTests"}},s={},p=[{value:"Setup",id:"setup",level:2},{value:"Install",id:"install",level:3},{value:"Configure",id:"configure",level:3},{value:"Usage",id:"usage",level:2}],c={toc:p};function u(e){let{components:n,...t}=e;return(0,a.kt)("wrapper",(0,r.Z)({},c,t,{components:n,mdxType:"MDXLayout"}),(0,a.kt)("h1",{id:"environment-variables"},"Environment Variables"),(0,a.kt)("h2",{id:"setup"},"Setup"),(0,a.kt)("h3",{id:"install"},"Install"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},"yarn add -D dotenv babel-plugin-inline-dotenv\n")),(0,a.kt)("h3",{id:"configure"},"Configure"),(0,a.kt)("p",null,"Add ",(0,a.kt)("inlineCode",{parentName:"p"},"inline-dotenv")," to your ",(0,a.kt)("inlineCode",{parentName:"p"},"babel.config.js")," or ",(0,a.kt)("inlineCode",{parentName:"p"},".babelrc"),", for example:"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-javascript"},'// babel.config.js\n\nconst plugins = [\n [\n "@babel/plugin-proposal-decorators",\n {\n legacy: true,\n },\n ],\n ["@babel/plugin-proposal-optional-catch-binding"],\n "inline-dotenv",\n "react-native-reanimated/plugin", // NOTE: this must be last in the plugins\n];\n\nconst expoConfig = {\n presets: ["babel-preset-expo"],\n env: {\n production: {},\n },\n plugins,\n};\n')),(0,a.kt)("p",null,(0,a.kt)("em",{parentName:"p"},"Note: this configuration also works for a bare react-native app")),(0,a.kt)("h2",{id:"usage"},"Usage"),(0,a.kt)("p",null,"Create a ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file in the root of your project"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-bash"},'MY_VAR="MY_VALUE"\nKEEP_IN_MIND="THESE ARE NOT SECURE"\n')),(0,a.kt)("p",null,"You'll now have access to your values from the ",(0,a.kt)("inlineCode",{parentName:"p"},".env")," file"),(0,a.kt)("pre",null,(0,a.kt)("code",{parentName:"pre",className:"language-javascript"},"console.log(process.env.MY_VAR); // results in: MY_VALUE\nconsole.log(process.env.KEEP_IN_MIND); // results in: THESE ARE NOT SECURE\n")),(0,a.kt)("p",null,"If making changes to the environment variables, you'll have to restart your metro server (and possibly clear cache)"))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/da9277bc.8e4636bc.js b/assets/js/da9277bc.c7b2fb89.js similarity index 99% rename from assets/js/da9277bc.8e4636bc.js rename to assets/js/da9277bc.c7b2fb89.js index eabc503c..4d8bbacf 100644 --- a/assets/js/da9277bc.8e4636bc.js +++ b/assets/js/da9277bc.c7b2fb89.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[2182],{3905:(e,t,a)=>{a.d(t,{Zo:()=>l,kt:()=>g});var n=a(7294);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var p=n.createContext({}),c=function(e){var t=n.useContext(p),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},l=function(e){var t=c(e.components);return n.createElement(p.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},u=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,i=e.originalType,p=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),u=c(a),g=r,m=u["".concat(p,".").concat(g)]||u[g]||d[g]||i;return a?n.createElement(m,o(o({ref:t},l),{},{components:a})):n.createElement(m,o({ref:t},l))}));function g(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=a.length,o=new Array(i);o[0]=u;var s={};for(var p in t)hasOwnProperty.call(t,p)&&(s[p]=t[p]);s.originalType=e,s.mdxType="string"==typeof e?e:r,o[1]=s;for(var c=2;c{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>c});var n=a(7462),r=(a(7294),a(3905));const i={title:"Updating Dependencies with Yarn Audit, Outdated and Upgrade",tags:["Yarn","Guide","Dependencies"],last_update:{author:"Derek Greenberg"},publish_date:new Date("2022-10-09T00:00:00.000Z")},o=void 0,s={unversionedId:"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",source:"@site/docs/recipes/UpdatingDependencies.md",sourceDirName:"recipes",slug:"/recipes/UpdatingDependencies",permalink:"/docs/recipes/UpdatingDependencies",draft:!1,tags:[{label:"Yarn",permalink:"/docs/tags/yarn"},{label:"Guide",permalink:"/docs/tags/guide"},{label:"Dependencies",permalink:"/docs/tags/dependencies"}],version:"current",lastUpdatedBy:"Derek Greenberg",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Updating Dependencies with Yarn Audit, Outdated and Upgrade",tags:["Yarn","Guide","Dependencies"],last_update:{author:"Derek Greenberg"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Scrolling to a location that hasn't been rendered using FlatList or SectionList",permalink:"/docs/recipes/UnrenderedItemInScrollView"},next:{title:"Using Screen Readers",permalink:"/docs/recipes/UsingScreenReaders"}},p={},c=[],l={toc:c};function d(e){let{components:t,...a}=e;return(0,r.kt)("wrapper",(0,n.Z)({},l,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"If you get a bunch of warnings in the git command output about vulnerabilities, similar to this: ",(0,r.kt)("inlineCode",{parentName:"p"},"remote: 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"),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://classic.yarnpkg.com/en/docs/cli/audit"},(0,r.kt)("strong",{parentName:"a"},"Yarn Audit"))," Checks for known security issues with the installed packages. Issue the command from the root of your project. The output is a list of known issues."),(0,r.kt)("p",null,"Usage:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"yarn audit\n")),(0,r.kt)("p",null,(0,r.kt)("img",{parentName:"p",src:"https://static.slab.com/prod/uploads/67oa0iba/posts/images/cwzWfJi-twHOoJcKHPVx1lm_.png",alt:"yarn audit usage in terminal"})),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://classic.yarnpkg.com/en/docs/cli/outdated"},(0,r.kt)("strong",{parentName:"a"},"Yarn Outdated"))," generates a list of outdated packages and all the info you need to make decisions about updating their versions, such as whether a major update that is NOT backwards compatible is available. A handy link to the repository is provided so you can read about the consequences of updating that dependency in your project."),(0,r.kt)("p",null,"Usage:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"yarn outdated\n")),(0,r.kt)("p",null,(0,r.kt)("img",{parentName:"p",src:"https://static.slab.com/prod/uploads/67oa0iba/posts/images/g93PKCGCzgP-Ho0-QZWV8PIF.png",alt:"yarn outdated usage in terminal"})),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://classic.yarnpkg.com/en/docs/cli/upgrade/"},(0,r.kt)("strong",{parentName:"a"},"Yarn Upgrade"))," updates the version of a given package to the latest, or to a specific version if you specify it. Be sure to provide an argument to this command; otherwise, it will update all dependencies to their latest versions, which is usually not what you want."),(0,r.kt)("p",null,"Usage:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"yarn upgrade-interactive\nyarn upgrade-interactive --latest\n")),(0,r.kt)("p",null,(0,r.kt)("img",{parentName:"p",src:"https://static.slab.com/prod/uploads/67oa0iba/posts/images/lsVpJJ9m7AGF6Nh0hbpy9927.png",alt:"yarn upgrade-interactive usage in terminal"})))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[2182],{3905:(e,t,a)=>{a.d(t,{Zo:()=>l,kt:()=>g});var n=a(7294);function r(e,t,a){return t in e?Object.defineProperty(e,t,{value:a,enumerable:!0,configurable:!0,writable:!0}):e[t]=a,e}function i(e,t){var a=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),a.push.apply(a,n)}return a}function o(e){for(var t=1;t=0||(r[a]=e[a]);return r}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,a)&&(r[a]=e[a])}return r}var p=n.createContext({}),c=function(e){var t=n.useContext(p),a=t;return e&&(a="function"==typeof e?e(t):o(o({},t),e)),a},l=function(e){var t=c(e.components);return n.createElement(p.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},u=n.forwardRef((function(e,t){var a=e.components,r=e.mdxType,i=e.originalType,p=e.parentName,l=s(e,["components","mdxType","originalType","parentName"]),u=c(a),g=r,m=u["".concat(p,".").concat(g)]||u[g]||d[g]||i;return a?n.createElement(m,o(o({ref:t},l),{},{components:a})):n.createElement(m,o({ref:t},l))}));function g(e,t){var a=arguments,r=t&&t.mdxType;if("string"==typeof e||r){var i=a.length,o=new Array(i);o[0]=u;var s={};for(var p in t)hasOwnProperty.call(t,p)&&(s[p]=t[p]);s.originalType=e,s.mdxType="string"==typeof e?e:r,o[1]=s;for(var c=2;c{a.r(t),a.d(t,{assets:()=>p,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>c});var n=a(7462),r=(a(7294),a(3905));const i={title:"Updating Dependencies with Yarn Audit, Outdated and Upgrade",tags:["Yarn","Guide","Dependencies"],last_update:{author:"Derek Greenberg"},publish_date:new Date("2022-10-09T00:00:00.000Z")},o=void 0,s={unversionedId:"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",source:"@site/docs/recipes/UpdatingDependencies.md",sourceDirName:"recipes",slug:"/recipes/UpdatingDependencies",permalink:"/docs/recipes/UpdatingDependencies",draft:!1,tags:[{label:"Yarn",permalink:"/docs/tags/yarn"},{label:"Guide",permalink:"/docs/tags/guide"},{label:"Dependencies",permalink:"/docs/tags/dependencies"}],version:"current",lastUpdatedBy:"Derek Greenberg",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Updating Dependencies with Yarn Audit, Outdated and Upgrade",tags:["Yarn","Guide","Dependencies"],last_update:{author:"Derek Greenberg"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Scrolling to a location that hasn't been rendered using FlatList or SectionList",permalink:"/docs/recipes/UnrenderedItemInScrollView"},next:{title:"Using Screen Readers",permalink:"/docs/recipes/UsingScreenReaders"}},p={},c=[],l={toc:c};function d(e){let{components:t,...a}=e;return(0,r.kt)("wrapper",(0,n.Z)({},l,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("p",null,"If you get a bunch of warnings in the git command output about vulnerabilities, similar to this: ",(0,r.kt)("inlineCode",{parentName:"p"},"remote: 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"),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://classic.yarnpkg.com/en/docs/cli/audit"},(0,r.kt)("strong",{parentName:"a"},"Yarn Audit"))," Checks for known security issues with the installed packages. Issue the command from the root of your project. The output is a list of known issues."),(0,r.kt)("p",null,"Usage:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"yarn audit\n")),(0,r.kt)("p",null,(0,r.kt)("img",{parentName:"p",src:"https://static.slab.com/prod/uploads/67oa0iba/posts/images/cwzWfJi-twHOoJcKHPVx1lm_.png",alt:"yarn audit usage in terminal"})),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://classic.yarnpkg.com/en/docs/cli/outdated"},(0,r.kt)("strong",{parentName:"a"},"Yarn Outdated"))," generates a list of outdated packages and all the info you need to make decisions about updating their versions, such as whether a major update that is NOT backwards compatible is available. A handy link to the repository is provided so you can read about the consequences of updating that dependency in your project."),(0,r.kt)("p",null,"Usage:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"yarn outdated\n")),(0,r.kt)("p",null,(0,r.kt)("img",{parentName:"p",src:"https://static.slab.com/prod/uploads/67oa0iba/posts/images/g93PKCGCzgP-Ho0-QZWV8PIF.png",alt:"yarn outdated usage in terminal"})),(0,r.kt)("p",null,(0,r.kt)("a",{parentName:"p",href:"https://classic.yarnpkg.com/en/docs/cli/upgrade/"},(0,r.kt)("strong",{parentName:"a"},"Yarn Upgrade"))," updates the version of a given package to the latest, or to a specific version if you specify it. Be sure to provide an argument to this command; otherwise, it will update all dependencies to their latest versions, which is usually not what you want."),(0,r.kt)("p",null,"Usage:"),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-console"},"yarn upgrade-interactive\nyarn upgrade-interactive --latest\n")),(0,r.kt)("p",null,(0,r.kt)("img",{parentName:"p",src:"https://static.slab.com/prod/uploads/67oa0iba/posts/images/lsVpJJ9m7AGF6Nh0hbpy9927.png",alt:"yarn upgrade-interactive usage in terminal"})))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/e438b990.7ef888a8.js b/assets/js/e438b990.39d25ab1.js similarity index 98% rename from assets/js/e438b990.7ef888a8.js rename to assets/js/e438b990.39d25ab1.js index be801d35..45405975 100644 --- a/assets/js/e438b990.7ef888a8.js +++ b/assets/js/e438b990.39d25ab1.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[1065],{3905:(e,t,n)=>{n.d(t,{Zo:()=>s,kt:()=>m});var o=n(7294);function l(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function a(e){for(var t=1;t=0||(l[n]=e[n]);return l}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(l[n]=e[n])}return l}var p=o.createContext({}),u=function(e){var t=o.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):a(a({},t),e)),n},s=function(e){var t=u(e.components);return o.createElement(p.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},c=o.forwardRef((function(e,t){var n=e.components,l=e.mdxType,r=e.originalType,p=e.parentName,s=i(e,["components","mdxType","originalType","parentName"]),c=u(n),m=l,k=c["".concat(p,".").concat(m)]||c[m]||d[m]||r;return n?o.createElement(k,a(a({ref:t},s),{},{components:n})):o.createElement(k,a({ref:t},s))}));function m(e,t){var n=arguments,l=t&&t.mdxType;if("string"==typeof e||l){var r=n.length,a=new Array(r);a[0]=c;var i={};for(var p in t)hasOwnProperty.call(t,p)&&(i[p]=t[p]);i.originalType=e,i.mdxType="string"==typeof e?e:l,a[1]=i;for(var u=2;u{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>a,default:()=>d,frontMatter:()=>r,metadata:()=>i,toc:()=>u});var o=n(7462),l=(n(7294),n(3905));const r={title:"Staying With Expo",description:"Setting up Ignite to build a custom Expo development client for use with Config Plugins",tags:["Expo","expo-dev-client"],last_update:{author:"Frank Calise"},publish_date:new Date("2022-10-11T00:00:00.000Z")},a="Staying With Expo",i={unversionedId:"recipes/StayingWithExpo",id:"recipes/StayingWithExpo",title:"Staying With Expo",description:"Setting up Ignite to build a custom Expo development client for use with Config Plugins",source:"@site/docs/recipes/StayingWithExpo.md",sourceDirName:"recipes",slug:"/recipes/StayingWithExpo",permalink:"/docs/recipes/StayingWithExpo",draft:!1,tags:[{label:"Expo",permalink:"/docs/tags/expo"},{label:"expo-dev-client",permalink:"/docs/tags/expo-dev-client"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Staying With Expo",description:"Setting up Ignite to build a custom Expo development client for use with Config Plugins",tags:["Expo","expo-dev-client"],last_update:{author:"Frank Calise"},publish_date:"2022-10-11T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"SelectField using `react-native-bottom-sheet`",permalink:"/docs/recipes/SelectFieldWithBottomSheet"},next:{title:"TypeScript baseUrl Configuration",permalink:"/docs/recipes/TypeScriptBaseURL"}},p={},u=[{value:"Appetizer",id:"appetizer",level:2},{value:"Steps",id:"steps",level:2},{value:"1. Project Setup",id:"1-project-setup",level:3},{value:"2. Build Profile",id:"2-build-profile",level:3},{value:"3. Run the Build!",id:"3-run-the-build",level:3},{value:"4. Run the Development Client",id:"4-run-the-development-client",level:3}],s={toc:u};function d(e){let{components:t,...n}=e;return(0,l.kt)("wrapper",(0,o.Z)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,l.kt)("h1",{id:"staying-with-expo"},"Staying With Expo"),(0,l.kt)("p",null,"This guide will teach you how to set up an ",(0,l.kt)("a",{parentName:"p",href:"https://docs.expo.dev/develop/development-builds/create-a-build/"},"Expo development build")," which prepares your project for native code via ",(0,l.kt)("a",{parentName:"p",href:"https://docs.expo.dev/guides/config-plugins/"},"Config Plugins"),", but keeps you in Expo's managed workflow."),(0,l.kt)("h2",{id:"appetizer"},"Appetizer"),(0,l.kt)("p",null,"Follow the ",(0,l.kt)("a",{parentName:"p",href:"/docs/recipes/PristineExpoProject"},"Pristine Expo Project")," recipe first to make sure you're starting with an Expo only project."),(0,l.kt)("p",null,"You'll also need ",(0,l.kt)("inlineCode",{parentName:"p"},"eas-cli")," globally installed and and an ",(0,l.kt)("a",{parentName:"p",href:"https://expo.dev/signup"},"Expo account")," if you don't already have one."),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"npm install -g eas-cli\n")),(0,l.kt)("p",null,(0,l.kt)("em",{parentName:"p"},"Optional"),": You can use EAS builds for free, however there is a queue time to wait for your build. It is possible to ",(0,l.kt)("a",{parentName:"p",href:"https://docs.expo.dev/build-reference/local-builds/"},"build locally"),", however you'll need a couple of other dependencies installed for proper iOS and Android builds (if you can already build iOS/Android natively, you're probably good to go!)"),(0,l.kt)("details",null,(0,l.kt)("summary",null,(0,l.kt)("strong",null,"iOS")),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"brew install cocoapods fastlane\n"))),(0,l.kt)("details",null,(0,l.kt)("summary",null,(0,l.kt)("strong",null,"Android")),(0,l.kt)("p",null,"SDK and NDK")),(0,l.kt)("h2",{id:"steps"},"Steps"),(0,l.kt)("h3",{id:"1-project-setup"},"1. Project Setup"),(0,l.kt)("p",null,"From within your project directory, run the following:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"yarn add expo-dev-client\n")),(0,l.kt)("p",null,"Create or link an EAS project."),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"eas init\n\u2714 Linked to project @infinitered/cookbook\n\u2714 Linked app.json to project with ID 012aaaa3-4ce5-4bae-9f4d-2f842489f07a\n")),(0,l.kt)("p",null,"Configure the project to support EAS Build."),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"eas build:configure\n")),(0,l.kt)("h3",{id:"2-build-profile"},"2. Build Profile"),(0,l.kt)("p",null,"Modify the newly generated ",(0,l.kt)("inlineCode",{parentName:"p"},"eas.json")," to configure a ",(0,l.kt)("inlineCode",{parentName:"p"},"preview")," build profile"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-json"},'{\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')),(0,l.kt)("h3",{id:"3-run-the-build"},"3. Run the Build!"),(0,l.kt)("p",null,(0,l.kt)("strong",{parentName:"p"},"Using EAS build servers")),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"eas build --profile preview\n")),(0,l.kt)("p",null,"Once complete, you can download the Android ",(0,l.kt)("inlineCode",{parentName:"p"},"apk")," or iOS ",(0,l.kt)("inlineCode",{parentName:"p"},"tar")," file."),(0,l.kt)("p",null,(0,l.kt)("strong",{parentName:"p"},"Build Locally")),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"eas build --profile preview --local\n")),(0,l.kt)("p",null,"Your app will be saved in the root directory unless you specify the desired directory with the environment variable ",(0,l.kt)("inlineCode",{parentName:"p"},"EAS_LOCAL_BUILD_ARTIFACTS_DIR"),", for example:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"EAS_LOCAL_BUILD_ARTIFACTS_DIR=build eas build --profile preview --local\n")),(0,l.kt)("p",null,"This will save your ",(0,l.kt)("inlineCode",{parentName:"p"},"*.apk")," or ",(0,l.kt)("inlineCode",{parentName:"p"},"*.tar.gz")," (containing the ",(0,l.kt)("inlineCode",{parentName:"p"},".app"),") in the ",(0,l.kt)("inlineCode",{parentName:"p"},"[project-dir]/build/")," directory."),(0,l.kt)("h3",{id:"4-run-the-development-client"},"4. Run the Development Client"),(0,l.kt)("p",null,"With the builds complete, let's add them to your emulator or simulator."),(0,l.kt)("details",null,(0,l.kt)("summary",null,(0,l.kt)("strong",null,"iOS")),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"tar zxvf build/build-*.tar.gz -C build/")," (adapt this command depending on where you saved it to)"),(0,l.kt)("li",{parentName:"ul"},"Drag the ",(0,l.kt)("inlineCode",{parentName:"li"},"PizzaApp.app")," onto your iOS simulator"))),(0,l.kt)("details",null,(0,l.kt)("summary",null,(0,l.kt)("strong",null,"Android")),(0,l.kt)("p",null,'Drag the APK onto your emulator or install it on a device (making sure your settings are appropriate for "Install unknown apps")')),(0,l.kt)("p",null,"You can now develop locally like you normally would with Expo Go, with one last adjustment to the ",(0,l.kt)("inlineCode",{parentName:"p"},"start")," script."),(0,l.kt)("p",null,"In ",(0,l.kt)("inlineCode",{parentName:"p"},"package.json")," modify the start script to use the ",(0,l.kt)("inlineCode",{parentName:"p"},"expo-dev-client")," package."),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-diff"},'--"start": "expo start"\n++"start": "expo start --dev-client"\n')),(0,l.kt)("p",null,"Run ",(0,l.kt)("inlineCode",{parentName:"p"},"yarn start")," in your terminal so metro starts up. Follow the Expo CLI to boot up either the Android or iOS app. You'll notice Expo Go has not launched in this case, but something that looks very similar (you can still shake the device for developer menu, etc)."))}d.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[1065],{3905:(e,t,n)=>{n.d(t,{Zo:()=>s,kt:()=>m});var o=n(7294);function l(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function a(e){for(var t=1;t=0||(l[n]=e[n]);return l}(e,t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);for(o=0;o=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(l[n]=e[n])}return l}var p=o.createContext({}),u=function(e){var t=o.useContext(p),n=t;return e&&(n="function"==typeof e?e(t):a(a({},t),e)),n},s=function(e){var t=u(e.components);return o.createElement(p.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return o.createElement(o.Fragment,{},t)}},c=o.forwardRef((function(e,t){var n=e.components,l=e.mdxType,r=e.originalType,p=e.parentName,s=i(e,["components","mdxType","originalType","parentName"]),c=u(n),m=l,k=c["".concat(p,".").concat(m)]||c[m]||d[m]||r;return n?o.createElement(k,a(a({ref:t},s),{},{components:n})):o.createElement(k,a({ref:t},s))}));function m(e,t){var n=arguments,l=t&&t.mdxType;if("string"==typeof e||l){var r=n.length,a=new Array(r);a[0]=c;var i={};for(var p in t)hasOwnProperty.call(t,p)&&(i[p]=t[p]);i.originalType=e,i.mdxType="string"==typeof e?e:l,a[1]=i;for(var u=2;u{n.r(t),n.d(t,{assets:()=>p,contentTitle:()=>a,default:()=>d,frontMatter:()=>r,metadata:()=>i,toc:()=>u});var o=n(7462),l=(n(7294),n(3905));const r={title:"Staying With Expo",description:"Setting up Ignite to build a custom Expo development client for use with Config Plugins",tags:["Expo","expo-dev-client"],last_update:{author:"Frank Calise"},publish_date:new Date("2022-10-11T00:00:00.000Z")},a="Staying With Expo",i={unversionedId:"recipes/StayingWithExpo",id:"recipes/StayingWithExpo",title:"Staying With Expo",description:"Setting up Ignite to build a custom Expo development client for use with Config Plugins",source:"@site/docs/recipes/StayingWithExpo.md",sourceDirName:"recipes",slug:"/recipes/StayingWithExpo",permalink:"/docs/recipes/StayingWithExpo",draft:!1,tags:[{label:"Expo",permalink:"/docs/tags/expo"},{label:"expo-dev-client",permalink:"/docs/tags/expo-dev-client"}],version:"current",lastUpdatedBy:"Frank Calise",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Staying With Expo",description:"Setting up Ignite to build a custom Expo development client for use with Config Plugins",tags:["Expo","expo-dev-client"],last_update:{author:"Frank Calise"},publish_date:"2022-10-11T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"SelectField using `react-native-bottom-sheet`",permalink:"/docs/recipes/SelectFieldWithBottomSheet"},next:{title:"TypeScript baseUrl Configuration",permalink:"/docs/recipes/TypeScriptBaseURL"}},p={},u=[{value:"Appetizer",id:"appetizer",level:2},{value:"Steps",id:"steps",level:2},{value:"1. Project Setup",id:"1-project-setup",level:3},{value:"2. Build Profile",id:"2-build-profile",level:3},{value:"3. Run the Build!",id:"3-run-the-build",level:3},{value:"4. Run the Development Client",id:"4-run-the-development-client",level:3}],s={toc:u};function d(e){let{components:t,...n}=e;return(0,l.kt)("wrapper",(0,o.Z)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,l.kt)("h1",{id:"staying-with-expo"},"Staying With Expo"),(0,l.kt)("p",null,"This guide will teach you how to set up an ",(0,l.kt)("a",{parentName:"p",href:"https://docs.expo.dev/develop/development-builds/create-a-build/"},"Expo development build")," which prepares your project for native code via ",(0,l.kt)("a",{parentName:"p",href:"https://docs.expo.dev/guides/config-plugins/"},"Config Plugins"),", but keeps you in Expo's managed workflow."),(0,l.kt)("h2",{id:"appetizer"},"Appetizer"),(0,l.kt)("p",null,"Follow the ",(0,l.kt)("a",{parentName:"p",href:"/docs/recipes/PristineExpoProject"},"Pristine Expo Project")," recipe first to make sure you're starting with an Expo only project."),(0,l.kt)("p",null,"You'll also need ",(0,l.kt)("inlineCode",{parentName:"p"},"eas-cli")," globally installed and and an ",(0,l.kt)("a",{parentName:"p",href:"https://expo.dev/signup"},"Expo account")," if you don't already have one."),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"npm install -g eas-cli\n")),(0,l.kt)("p",null,(0,l.kt)("em",{parentName:"p"},"Optional"),": You can use EAS builds for free, however there is a queue time to wait for your build. It is possible to ",(0,l.kt)("a",{parentName:"p",href:"https://docs.expo.dev/build-reference/local-builds/"},"build locally"),", however you'll need a couple of other dependencies installed for proper iOS and Android builds (if you can already build iOS/Android natively, you're probably good to go!)"),(0,l.kt)("details",null,(0,l.kt)("summary",null,(0,l.kt)("strong",null,"iOS")),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"brew install cocoapods fastlane\n"))),(0,l.kt)("details",null,(0,l.kt)("summary",null,(0,l.kt)("strong",null,"Android")),(0,l.kt)("p",null,"SDK and NDK")),(0,l.kt)("h2",{id:"steps"},"Steps"),(0,l.kt)("h3",{id:"1-project-setup"},"1. Project Setup"),(0,l.kt)("p",null,"From within your project directory, run the following:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"yarn add expo-dev-client\n")),(0,l.kt)("p",null,"Create or link an EAS project."),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"eas init\n\u2714 Linked to project @infinitered/cookbook\n\u2714 Linked app.json to project with ID 012aaaa3-4ce5-4bae-9f4d-2f842489f07a\n")),(0,l.kt)("p",null,"Configure the project to support EAS Build."),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"eas build:configure\n")),(0,l.kt)("h3",{id:"2-build-profile"},"2. Build Profile"),(0,l.kt)("p",null,"Modify the newly generated ",(0,l.kt)("inlineCode",{parentName:"p"},"eas.json")," to configure a ",(0,l.kt)("inlineCode",{parentName:"p"},"preview")," build profile"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-json"},'{\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')),(0,l.kt)("h3",{id:"3-run-the-build"},"3. Run the Build!"),(0,l.kt)("p",null,(0,l.kt)("strong",{parentName:"p"},"Using EAS build servers")),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"eas build --profile preview\n")),(0,l.kt)("p",null,"Once complete, you can download the Android ",(0,l.kt)("inlineCode",{parentName:"p"},"apk")," or iOS ",(0,l.kt)("inlineCode",{parentName:"p"},"tar")," file."),(0,l.kt)("p",null,(0,l.kt)("strong",{parentName:"p"},"Build Locally")),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"eas build --profile preview --local\n")),(0,l.kt)("p",null,"Your app will be saved in the root directory unless you specify the desired directory with the environment variable ",(0,l.kt)("inlineCode",{parentName:"p"},"EAS_LOCAL_BUILD_ARTIFACTS_DIR"),", for example:"),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-console"},"EAS_LOCAL_BUILD_ARTIFACTS_DIR=build eas build --profile preview --local\n")),(0,l.kt)("p",null,"This will save your ",(0,l.kt)("inlineCode",{parentName:"p"},"*.apk")," or ",(0,l.kt)("inlineCode",{parentName:"p"},"*.tar.gz")," (containing the ",(0,l.kt)("inlineCode",{parentName:"p"},".app"),") in the ",(0,l.kt)("inlineCode",{parentName:"p"},"[project-dir]/build/")," directory."),(0,l.kt)("h3",{id:"4-run-the-development-client"},"4. Run the Development Client"),(0,l.kt)("p",null,"With the builds complete, let's add them to your emulator or simulator."),(0,l.kt)("details",null,(0,l.kt)("summary",null,(0,l.kt)("strong",null,"iOS")),(0,l.kt)("ul",null,(0,l.kt)("li",{parentName:"ul"},(0,l.kt)("inlineCode",{parentName:"li"},"tar zxvf build/build-*.tar.gz -C build/")," (adapt this command depending on where you saved it to)"),(0,l.kt)("li",{parentName:"ul"},"Drag the ",(0,l.kt)("inlineCode",{parentName:"li"},"PizzaApp.app")," onto your iOS simulator"))),(0,l.kt)("details",null,(0,l.kt)("summary",null,(0,l.kt)("strong",null,"Android")),(0,l.kt)("p",null,'Drag the APK onto your emulator or install it on a device (making sure your settings are appropriate for "Install unknown apps")')),(0,l.kt)("p",null,"You can now develop locally like you normally would with Expo Go, with one last adjustment to the ",(0,l.kt)("inlineCode",{parentName:"p"},"start")," script."),(0,l.kt)("p",null,"In ",(0,l.kt)("inlineCode",{parentName:"p"},"package.json")," modify the start script to use the ",(0,l.kt)("inlineCode",{parentName:"p"},"expo-dev-client")," package."),(0,l.kt)("pre",null,(0,l.kt)("code",{parentName:"pre",className:"language-diff"},'--"start": "expo start"\n++"start": "expo start --dev-client"\n')),(0,l.kt)("p",null,"Run ",(0,l.kt)("inlineCode",{parentName:"p"},"yarn start")," in your terminal so metro starts up. Follow the Expo CLI to boot up either the Android or iOS app. You'll notice Expo Go has not launched in this case, but something that looks very similar (you can still shake the device for developer menu, etc)."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/f523b160.5ad4b3ed.js b/assets/js/f523b160.6bef3a08.js similarity index 92% rename from assets/js/f523b160.5ad4b3ed.js rename to assets/js/f523b160.6bef3a08.js index 613c08ab..79ca0f8a 100644 --- a/assets/js/f523b160.5ad4b3ed.js +++ b/assets/js/f523b160.6bef3a08.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[6649],{3905:(e,t,r)=>{r.d(t,{Zo:()=>p,kt:()=>m});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function o(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var l=n.createContext({}),c=function(e){var t=n.useContext(l),r=t;return e&&(r="function"==typeof e?e(t):o(o({},t),e)),r},p=function(e){var t=c(e.components);return n.createElement(l.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},d=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),d=c(r),m=a,h=d["".concat(l,".").concat(m)]||d[m]||u[m]||i;return r?n.createElement(h,o(o({ref:t},p),{},{components:r})):n.createElement(h,o({ref:t},p))}));function m(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=r.length,o=new Array(i);o[0]=d;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:a,o[1]=s;for(var c=2;c{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>u,frontMatter:()=>i,metadata:()=>s,toc:()=>c});var n=r(7462),a=(r(7294),r(3905));const i={title:"Creating a Good Experience for Screen Readers",description:"Learn how to improve the experience of screen readers using your app!",tags:["Accessibility","iOS","Android"],last_update:{author:"Lizzi Lindboe"},publish_date:new Date("2022-10-09T00:00:00.000Z")},o=void 0,s={unversionedId:"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!",source:"@site/docs/recipes/CreatingGreateExperienceForScreenReaders.md",sourceDirName:"recipes",slug:"/recipes/CreatingGreateExperienceForScreenReaders",permalink:"/docs/recipes/CreatingGreateExperienceForScreenReaders",draft:!1,tags:[{label:"Accessibility",permalink:"/docs/tags/accessibility"},{label:"iOS",permalink:"/docs/tags/i-os"},{label:"Android",permalink:"/docs/tags/android"}],version:"current",lastUpdatedBy:"Lizzi Lindboe",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"Creating a Good Experience for Screen Readers",description:"Learn how to improve the experience of screen readers using your app!",tags:["Accessibility","iOS","Android"],last_update:{author:"Lizzi Lindboe"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"CircleCI CD Setup - React Native",permalink:"/docs/recipes/CircleCIRNSetup"},next:{title:"Detox Intro",permalink:"/docs/recipes/DetoxIntro"}},l={},c=[{value:"UI Patterns",id:"ui-patterns",level:2},{value:"Screens",id:"screens",level:3},{value:"Common patterns that require more work to add good UX for screen readers",id:"common-patterns-that-require-more-work-to-add-good-ux-for-screen-readers",level:3},{value:"RN-specific issues",id:"rn-specific-issues",level:3},{value:"Labeling",id:"labeling",level:2}],p={toc:c};function u(e){let{components:t,...r}=e;return(0,a.kt)("wrapper",(0,n.Z)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h2",{id:"ui-patterns"},"UI Patterns"),(0,a.kt)("h3",{id:"screens"},"Screens"),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Titles"),"\nAll screen should ideally have unique titles, to make it easier to know quickly which screen you're on ",(0,a.kt)("a",{parentName:"p",href:"https://www.a11yportal.com/guidelines/design/structure.html#unique-page-screen-titles"},"source"),"."),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Headings"),'\nApps should ideally mark headings to allow for quick "scanning" of the structure of screens (',(0,a.kt)("a",{parentName:"p",href:"https://www.a11yportal.com/guidelines/design/structure.html#headings"},"source"),'). In React Native, mark headings with the "header" accessibility role.'),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Try to group controls as much as possible"),"\n(",(0,a.kt)("a",{parentName:"p",href:"https://www.a11yportal.com/guidelines/design/structure.html#grouped-elements"},"source"),")"),(0,a.kt)("blockquote",null,(0,a.kt)("p",{parentName:"blockquote"},"It is easier and quicker for people using a keyboard or screen reader to interact with content when not overwhelmed and confused by extraneous elements. Grouping elements into a single overall control makes things clearer, simplifies interactions, and can provide larger touch targets.\nFor example, a control such as a custom item selector may be made up of smaller interface elements, but will be easier to use if conveyed as a single control. Another common example is\xa0",(0,a.kt)("a",{parentName:"p",href:"https://www.a11yportal.com/guidelines/design/links.html#combining-repeated-links"},"grouping adjacent links"),"\xa0to the same resource.")),(0,a.kt)("h3",{id:"common-patterns-that-require-more-work-to-add-good-ux-for-screen-readers"},"Common patterns that require more work to add good UX for screen readers"),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Infinite Scroll"),"\nInfinite scroll causes two main problems for screen readers: 1) there's no clear \"end\" to jump to, and 2) elements pop in, which need to be announced if there weren't there when the end was reached."),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Toast, Dialog, or Modal"),'\nDepending on the implementation used, these elements may not "announce" when they pop in. Evaluate solution for this before going with a library.'),(0,a.kt)("h3",{id:"rn-specific-issues"},"RN-specific issues"),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Test links nested in text with formatting"),"\nText links nested in other text elements aren't accessible. If you need to implement that design, there are a few options:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Wrap the parent text in a View and add custom ",(0,a.kt)("a",{parentName:"li",href:"https://reactnative.dev/docs/accessibility#accessibility-actions"},"accessibilityActions")," for opening the links",(0,a.kt)("ul",{parentName:"li"},(0,a.kt)("li",{parentName:"ul"},"Do not apply custom accessibilityActions to text, those will cause a crash on iOS"))),(0,a.kt)("li",{parentName:"ul"},"Re-structure the component based on whether a screen reader is turned on (as in this ",(0,a.kt)("a",{parentName:"li",href:"https://callstack.com/blog/react-native-android-accessibility-tips/"},"blog post"),")")),(0,a.kt)("h2",{id:"labeling"},"Labeling"),(0,a.kt)("p",null,"A good accessibility label for an element is concise, but also unique and makes sense even when the screen is not read linearly. This is because one way to navigate with a screen reader is to search for a label. Another is to jump between controls (e.g., links, buttons, form fields), so if a header preceding a link is the only way to understand the link, it won't make sense in this navigation method."),(0,a.kt)("p",null,"See this post for some great advice on when and how to use labels: ",(0,a.kt)("a",{parentName:"p",href:"https://mobilea11y.com/blog/when-to-use-accessibility-labels"},"https://mobilea11y.com/blog/when-to-use-accessibility-labels")))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[6649],{3905:(e,t,r)=>{r.d(t,{Zo:()=>p,kt:()=>m});var n=r(7294);function a(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function o(e){for(var t=1;t=0||(a[r]=e[r]);return a}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(n=0;n=0||Object.prototype.propertyIsEnumerable.call(e,r)&&(a[r]=e[r])}return a}var l=n.createContext({}),c=function(e){var t=n.useContext(l),r=t;return e&&(r="function"==typeof e?e(t):o(o({},t),e)),r},p=function(e){var t=c(e.components);return n.createElement(l.Provider,{value:t},e.children)},d={inlineCode:"code",wrapper:function(e){var t=e.children;return n.createElement(n.Fragment,{},t)}},u=n.forwardRef((function(e,t){var r=e.components,a=e.mdxType,i=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),u=c(r),m=a,h=u["".concat(l,".").concat(m)]||u[m]||d[m]||i;return r?n.createElement(h,o(o({ref:t},p),{},{components:r})):n.createElement(h,o({ref:t},p))}));function m(e,t){var r=arguments,a=t&&t.mdxType;if("string"==typeof e||a){var i=r.length,o=new Array(i);o[0]=u;var s={};for(var l in t)hasOwnProperty.call(t,l)&&(s[l]=t[l]);s.originalType=e,s.mdxType="string"==typeof e?e:a,o[1]=s;for(var c=2;c{r.r(t),r.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>i,metadata:()=>s,toc:()=>c});var n=r(7462),a=(r(7294),r(3905));const i={title:"Creating a Good Experience for Screen Readers",description:"Learn how to improve the experience of screen readers using your app!",tags:["Accessibility","iOS","Android"],last_update:{author:"Lizzi Lindboe"},publish_date:new Date("2022-10-09T00:00:00.000Z")},o=void 0,s={unversionedId:"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!",source:"@site/docs/recipes/CreatingGreateExperienceForScreenReaders.md",sourceDirName:"recipes",slug:"/recipes/CreatingGreateExperienceForScreenReaders",permalink:"/docs/recipes/CreatingGreateExperienceForScreenReaders",draft:!1,tags:[{label:"Accessibility",permalink:"/docs/tags/accessibility"},{label:"iOS",permalink:"/docs/tags/i-os"},{label:"Android",permalink:"/docs/tags/android"}],version:"current",lastUpdatedBy:"Lizzi Lindboe",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"Creating a Good Experience for Screen Readers",description:"Learn how to improve the experience of screen readers using your app!",tags:["Accessibility","iOS","Android"],last_update:{author:"Lizzi Lindboe"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"CircleCI CD Setup - React Native",permalink:"/docs/recipes/CircleCIRNSetup"},next:{title:"Detox Intro",permalink:"/docs/recipes/DetoxIntro"}},l={},c=[{value:"UI Patterns",id:"ui-patterns",level:2},{value:"Screens",id:"screens",level:3},{value:"Common patterns that require more work to add good UX for screen readers",id:"common-patterns-that-require-more-work-to-add-good-ux-for-screen-readers",level:3},{value:"RN-specific issues",id:"rn-specific-issues",level:3},{value:"Labeling",id:"labeling",level:2}],p={toc:c};function d(e){let{components:t,...r}=e;return(0,a.kt)("wrapper",(0,n.Z)({},p,r,{components:t,mdxType:"MDXLayout"}),(0,a.kt)("h2",{id:"ui-patterns"},"UI Patterns"),(0,a.kt)("h3",{id:"screens"},"Screens"),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Titles"),"\nAll screen should ideally have unique titles, to make it easier to know quickly which screen you're on ",(0,a.kt)("a",{parentName:"p",href:"https://www.a11yportal.com/guidelines/design/structure.html#unique-page-screen-titles"},"source"),"."),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Headings"),'\nApps should ideally mark headings to allow for quick "scanning" of the structure of screens (',(0,a.kt)("a",{parentName:"p",href:"https://www.a11yportal.com/guidelines/design/structure.html#headings"},"source"),'). In React Native, mark headings with the "header" accessibility role.'),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Try to group controls as much as possible"),"\n(",(0,a.kt)("a",{parentName:"p",href:"https://www.a11yportal.com/guidelines/design/structure.html#grouped-elements"},"source"),")"),(0,a.kt)("blockquote",null,(0,a.kt)("p",{parentName:"blockquote"},"It is easier and quicker for people using a keyboard or screen reader to interact with content when not overwhelmed and confused by extraneous elements. Grouping elements into a single overall control makes things clearer, simplifies interactions, and can provide larger touch targets.\nFor example, a control such as a custom item selector may be made up of smaller interface elements, but will be easier to use if conveyed as a single control. Another common example is\xa0",(0,a.kt)("a",{parentName:"p",href:"https://www.a11yportal.com/guidelines/design/links.html#combining-repeated-links"},"grouping adjacent links"),"\xa0to the same resource.")),(0,a.kt)("h3",{id:"common-patterns-that-require-more-work-to-add-good-ux-for-screen-readers"},"Common patterns that require more work to add good UX for screen readers"),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Infinite Scroll"),"\nInfinite scroll causes two main problems for screen readers: 1) there's no clear \"end\" to jump to, and 2) elements pop in, which need to be announced if there weren't there when the end was reached."),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Toast, Dialog, or Modal"),'\nDepending on the implementation used, these elements may not "announce" when they pop in. Evaluate solution for this before going with a library.'),(0,a.kt)("h3",{id:"rn-specific-issues"},"RN-specific issues"),(0,a.kt)("p",null,(0,a.kt)("strong",{parentName:"p"},"Test links nested in text with formatting"),"\nText links nested in other text elements aren't accessible. If you need to implement that design, there are a few options:"),(0,a.kt)("ul",null,(0,a.kt)("li",{parentName:"ul"},"Wrap the parent text in a View and add custom ",(0,a.kt)("a",{parentName:"li",href:"https://reactnative.dev/docs/accessibility#accessibility-actions"},"accessibilityActions")," for opening the links",(0,a.kt)("ul",{parentName:"li"},(0,a.kt)("li",{parentName:"ul"},"Do not apply custom accessibilityActions to text, those will cause a crash on iOS"))),(0,a.kt)("li",{parentName:"ul"},"Re-structure the component based on whether a screen reader is turned on (as in this ",(0,a.kt)("a",{parentName:"li",href:"https://callstack.com/blog/react-native-android-accessibility-tips/"},"blog post"),")")),(0,a.kt)("h2",{id:"labeling"},"Labeling"),(0,a.kt)("p",null,"A good accessibility label for an element is concise, but also unique and makes sense even when the screen is not read linearly. This is because one way to navigate with a screen reader is to search for a label. Another is to jump between controls (e.g., links, buttons, form fields), so if a header preceding a link is the only way to understand the link, it won't make sense in this navigation method."),(0,a.kt)("p",null,"See this post for some great advice on when and how to use labels: ",(0,a.kt)("a",{parentName:"p",href:"https://mobilea11y.com/blog/when-to-use-accessibility-labels"},"https://mobilea11y.com/blog/when-to-use-accessibility-labels")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/fe9b09bf.24e20f11.js b/assets/js/fe9b09bf.eb916895.js similarity index 99% rename from assets/js/fe9b09bf.24e20f11.js rename to assets/js/fe9b09bf.eb916895.js index de763c6a..79b7fea2 100644 --- a/assets/js/fe9b09bf.24e20f11.js +++ b/assets/js/fe9b09bf.eb916895.js @@ -1 +1 @@ -"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[9340],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>m});var a=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function r(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=a.createContext({}),c=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},p=function(e){var t=c(e.components);return a.createElement(s.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,o=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),d=c(n),m=o,h=d["".concat(s,".").concat(m)]||d[m]||u[m]||i;return n?a.createElement(h,r(r({ref:t},p),{},{components:n})):a.createElement(h,r({ref:t},p))}));function m(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=n.length,r=new Array(i);r[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l.mdxType="string"==typeof e?e:o,r[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>c});var a=n(7462),o=(n(7294),n(3905));const i={title:"CircleCI CD Setup - React Native",description:"Learn how to set up your CircleCI CD instance for React Native",tags:["Guide","CI/CD"],last_update:{author:"Robin Heinze"},publish_date:new Date("2022-10-09T00:00:00.000Z")},r=void 0,l={unversionedId:"recipes/CircleCIRNSetup",id:"recipes/CircleCIRNSetup",title:"CircleCI CD Setup - React Native",description:"Learn how to set up your CircleCI CD instance for React Native",source:"@site/docs/recipes/CircleCIRNSetup.md",sourceDirName:"recipes",slug:"/recipes/CircleCIRNSetup",permalink:"/docs/recipes/CircleCIRNSetup",draft:!1,tags:[{label:"Guide",permalink:"/docs/tags/guide"},{label:"CI/CD",permalink:"/docs/tags/ci-cd"}],version:"current",lastUpdatedBy:"Robin Heinze",lastUpdatedAt:1692282198,formattedLastUpdatedAt:"Aug 17, 2023",frontMatter:{title:"CircleCI CD Setup - React Native",description:"Learn how to set up your CircleCI CD instance for React Native",tags:["Guide","CI/CD"],last_update:{author:"Robin Heinze"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Accessiblity Font Sizes",permalink:"/docs/recipes/AccessibilityFontSizes"},next:{title:"Creating a Good Experience for Screen Readers",permalink:"/docs/recipes/CreatingGreateExperienceForScreenReaders"}},s={},c=[{value:"First Things First",id:"first-things-first",level:3},{value:"CircleCI Setup",id:"circleci-setup",level:3},{value:"Continuous Integration",id:"continuous-integration",level:3},{value:"iOS Continuous Deployment",id:"ios-continuous-deployment",level:3}],p={toc:c};function u(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("p",null,"This document shows the steps necessary to set up CircleCI automatic continuous integration testing and automatic Fastlane beta builds upon successfully merging a pull request."),(0,o.kt)("h3",{id:"first-things-first"},"First Things First"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Write Tests")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"If the project already has tests, great. If not, write some."),(0,o.kt)("li",{parentName:"ul"},"See ",(0,o.kt)("a",{parentName:"li",href:"https://github.com/infinitered/ChainReactApp2019"},"this")," for an example of how Infinite Red typically sets up tests for a React Native app.")),(0,o.kt)("h3",{id:"circleci-setup"},"CircleCI Setup"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/vcs-authorize/"},"Log into CircleCI")," with your Github account"),(0,o.kt)("li",{parentName:"ol"},"Choose your organization from the dropdown in the top left"),(0,o.kt)("li",{parentName:"ol"},"Navigate to ",(0,o.kt)("inlineCode",{parentName:"li"},"Add Projects")," on the left"),(0,o.kt)("li",{parentName:"ol"},"Search for your repo"),(0,o.kt)("li",{parentName:"ol"},"Choose Set Up Project"),(0,o.kt)("li",{parentName:"ol"},"Set Up Project")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Select ",(0,o.kt)("inlineCode",{parentName:"li"},"macOS")," for the operating system and Other for the language")),(0,o.kt)("ol",{start:7},(0,o.kt)("li",{parentName:"ol"},"Copy the basic ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml")," to ",(0,o.kt)("inlineCode",{parentName:"li"},".circleci/config.yml"),", commit your code changes and push to github ",(0,o.kt)("inlineCode",{parentName:"li"},"master")),(0,o.kt)("li",{parentName:"ol"},"Choose ",(0,o.kt)("inlineCode",{parentName:"li"},"Start building")," to initiate the first CI build. This build will fail. That's ok. We will update the config in the next step."),(0,o.kt)("li",{parentName:"ol"},"Enable builds from forked pull requests. Go to project settings > Advanced Settings, then toggle on ",(0,o.kt)("inlineCode",{parentName:"li"},"Build forked pull requests")),(0,o.kt)("li",{parentName:"ol"},"If this project is open-source, you'll want to make sure the open-source setting is enabled to allow for macOS builds. Go to project settings > Advanced Settings, then toggle ",(0,o.kt)("inlineCode",{parentName:"li"},"Free and Open Source"),".")),(0,o.kt)("h3",{id:"continuous-integration"},"Continuous Integration"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Create a folder in the project root named ",(0,o.kt)("inlineCode",{parentName:"li"},".circleci"),"."),(0,o.kt)("li",{parentName:"ol"},"Create a file inside that folder named ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml")),(0,o.kt)("li",{parentName:"ol"},"Use the below template in that file."),(0,o.kt)("li",{parentName:"ol"},"If needed, see ",(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/docs/2.0/config-intro/#section=configuration"},"configuration docs")," for additional configuration options. (",(0,o.kt)("em",{parentName:"li"},"Here is a complete ",(0,o.kt)("a",{parentName:"em",href:"https://github.com/YOUR_ORG/open-source/blob/master/config.example.yml"},"config.yml")," with CI and CD steps completed"),")")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-yaml"},'defaults: &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 keys:\n - v1-dependencies-node-{{ checksum "package.json" }}\n # fallback to using the latest cache if no exact match is found\n - v1-dependencies-node-\n - run:\n name: Install dependencies\n command: yarn install\n - save_cache:\n name: Save node modules\n paths:\n - node_modules\n key: v1-dependencies-node-{{ checksum "package.json" }}\n\n tests:\n <<: *defaults\n steps:\n - checkout\n - restore_cache:\n keys:\n - v1-dependencies-node-{{ checksum "package.json" }}\n # fallback to using the latest cache if no exact match is found\n - v1-dependencies-node-\n - run:\n name: Run tests\n command: yarn ci:test # this command will be added to/found in your package.json scripts\n\nworkflows:\n version: 2\n test_and_release:\n jobs:\n - setup\n - tests:\n requires:\n - setup\n')),(0,o.kt)("ol",{start:5},(0,o.kt)("li",{parentName:"ol"},"Make sure the test script is added to your ",(0,o.kt)("inlineCode",{parentName:"li"},"package.json"))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-json"},' {\n ...\n "scripts": {\n ...\n "ci:test": "" <<-- if you don\'t already have this one\n },\n ...\n }\n')),(0,o.kt)("h3",{id:"ios-continuous-deployment"},"iOS Continuous Deployment"),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"Add Fastlane")),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Before you can add continuous deployment, you'll need to setup Fastlane and Match to sign and deploy your app. You can follow these blog posts to get setup!")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://shift.infinite.red/simple-react-native-ios-releases-4c28bb53a97b"},"Releasing on iOS with Fastlane")," Make sure you get to the point of being able to run:"),(0,o.kt)("li",{parentName:"ul"},"fastlane ios beta")),(0,o.kt)("ol",{start:2},(0,o.kt)("li",{parentName:"ol"},"In your Fastfile, add:")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-ruby"},"before_all do\n setup_circle_ci\nend\n")),(0,o.kt)("ol",{start:3},(0,o.kt)("li",{parentName:"ol"},"In your ",(0,o.kt)("inlineCode",{parentName:"li"},"beta")," lane, make sure you have included a command that bumps the build number prior to building, and then commits the build number after building.\nExample:")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-ruby"},'PROJECT = "YourProject"\nXCODE_PROJECT = "#{PROJECT}.xcodeproj"\n lane :beta do\n increment_build_number(xcodeproj: "./#{XCODE_PROJECT}")\n match(type: "appstore")\n build_ios_app(\n scheme: PROJECT,\n workspace: "./YourProject.xcworkspace",\n xcargs: "-UseNewBuildSystem=NO -allowProvisioningUpdates",\n export_method: "app-store"\n )\n # Ship it!\n upload_to_testflight(\n skip_waiting_for_build_processing: true\n )\n commit_version_bump(\n xcodeproj: "./#{XCODE_PROJECT}",\n ignore: /tvOS/,\n force: true,\n message: "[skip ci] Version bump"\n )\n end\n')),(0,o.kt)("ol",{start:4},(0,o.kt)("li",{parentName:"ol"},"If you prefer, you can also do these steps as separate fastlane commands, just make sure to include a ",(0,o.kt)("inlineCode",{parentName:"li"},"- run:")," entry for each one in ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml"),".")),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"Setting up CircleCI to Run Fastlane"),"\nCheck out ",(0,o.kt)("a",{parentName:"p",href:"https://medium.com/@odedre/circle-ci-v2-react-native-project-build-release-setup-ce4ef31209d0"},"this blog post")," for lots of helpful tips."),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Make sure CircleCI has all the credentials to run your fastlane scripts:",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Go into the Settings screen for your project on CircleCI"),(0,o.kt)("li",{parentName:"ul"},'Under "Build Settings", click on "Environment Variables" (',(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/gh/YOUR_ORG/YOURPROJECT/edit#env-vars"},"https://circleci.com/gh/YOUR_ORG/YOURPROJECT/edit#env-vars"),")"),(0,o.kt)("li",{parentName:"ul"},'Click "Add Variable"'),(0,o.kt)("li",{parentName:"ul"},"Set ",(0,o.kt)("inlineCode",{parentName:"li"},"FASTLANE_USER")," to the email address of your your Apple App Store Connect / Dev Portal user."),(0,o.kt)("li",{parentName:"ul"},"Do this for all of the variables listed ",(0,o.kt)("a",{parentName:"li",href:"https://github.com/fastlane/docs/blob/950c6f42231d86b5187d2cfdcab2a6c81d0f61dc/docs/best-practices/continuous-integration.md#environment-variables-to-set"},"here")," ",(0,o.kt)("strong",{parentName:"li"},"Note"),": If your dev portal user does not have 2-Factor Auth turned on, you DO NOT need to set FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD. Including this variable when your account does need it will result in errors during TestFlight upload. You can find more info from the Fastlane Docs, and from the CircleCI codesigning docs"))),(0,o.kt)("li",{parentName:"ol"},"Add ",(0,o.kt)("inlineCode",{parentName:"li"},"GITHUB_TOKEN")," to env vars on CircleCI (",(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/gh/YOUR_ORG/YOURPROJECT/edit#env-vars"},"https://circleci.com/gh/YOUR_ORG/YOURPROJECT/edit#env-vars"),"). Y")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"If you need to make a new ",(0,o.kt)("inlineCode",{parentName:"li"},"GITHUB_TOKEN"),", go to ",(0,o.kt)("a",{parentName:"li",href:"https://github.com/settings/tokens/new"},"https://github.com/settings/tokens/new")," and create a new one with ",(0,o.kt)("inlineCode",{parentName:"li"},"repo")," access.")),(0,o.kt)("ol",{start:3},(0,o.kt)("li",{parentName:"ol"},"Add the ",(0,o.kt)("inlineCode",{parentName:"li"},"Circle CI")," Github team to your repo (",(0,o.kt)("a",{parentName:"li",href:"https://github.com/YOUR_ORG/YOURPROJECT/settings/collaboration"},"https://github.com/YOUR_ORG/YOURPROJECT/settings/collaboration"),") with write access."),(0,o.kt)("li",{parentName:"ol"},"Add the ",(0,o.kt)("inlineCode",{parentName:"li"},"Circle CI")," Github team as a read-only collaborator to the private match certificates repo."),(0,o.kt)("li",{parentName:"ol"},"Log in to GitHub/CircleCI as the CI user. Then in CircleCI, go to Project Settings > Checkout SSH keys (",(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/gh/YOURORGANIZATION/YOURPROJECT/edit#checkout"},"https://circleci.com/gh/YOURORGANIZATION/YOURPROJECT/edit#checkout"),") and add a new user key. This will allow CircleCI to clone the certs repo in order to sign your app."),(0,o.kt)("li",{parentName:"ol"},"Go to Project Settings > Checkout SSH Keys and add a new deploy key. You will copy the fingerprint and paste into the ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml")," example below in the ",(0,o.kt)("inlineCode",{parentName:"li"},"add_ssh_keys")," section (there should be ",(0,o.kt)("inlineCode",{parentName:"li"},'"'),"s around it)"),(0,o.kt)("li",{parentName:"ol"},"Add a script to your ",(0,o.kt)("inlineCode",{parentName:"li"},"package.json")," called ",(0,o.kt)("inlineCode",{parentName:"li"},"ci:setup"),". This will run any necessary shell commands to prepare your project for building. For example, creating private files like ",(0,o.kt)("inlineCode",{parentName:"li"},".env"),'. If you don\'t any additional setup, you can leave this command as "", or remove the ',(0,o.kt)("inlineCode",{parentName:"li"},"ci:setup")," step from the ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml")," example below.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-json"},' {\n ...\n "scripts": {\n ...\n "ci:setup": "touch .env && echo \\"ENV_VAR=\\"$ENV_VAR >> .env",\n },\n ...\n }\n')),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"Note"),": ",(0,o.kt)("inlineCode",{parentName:"p"},"react-native-dotenv")," throws errors if there is not a ",(0,o.kt)("inlineCode",{parentName:"p"},".env")," present with the variables it expects. However, if you don't want to put secret values in this script (you shouldn't), then you can add them directly to CircleCI under Project Settings > Environment Variables. Then you can reference them in this script as ",(0,o.kt)("inlineCode",{parentName:"p"},"$ENV_VAR"),"."),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Add ",(0,o.kt)("inlineCode",{parentName:"li"},"mac")," configuration and ",(0,o.kt)("inlineCode",{parentName:"li"},"deploy_ios")," job to your CircleCI ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml"),"\nNOTE: The macOS boxes currently come with Node 11.0, with no apparent way to change the version. This shouldn't be a huge problem. One known issue is with ",(0,o.kt)("inlineCode",{parentName:"li"},"upath"),", which is a deep dependency of react-native. If you encounter errors related to ",(0,o.kt)("inlineCode",{parentName:"li"},"upath")," requiring a lower version of Node, just make sure it is at ",(0,o.kt)("inlineCode",{parentName:"li"},"1.1.0"),", and not ",(0,o.kt)("inlineCode",{parentName:"li"},"1.0.4")," in your yarn.lock. See ",(0,o.kt)("a",{parentName:"li",href:"https://github.com/airbnb/enzyme/issues/1637#issuecomment-397327562"},"https://github.com/airbnb/enzyme/issues/1637#issuecomment-397327562"),".")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-yaml"},'defaults: ...\n\nmac: &mac\n macos:\n xcode: "10.1.0"\n working_directory: ~/repo\n environment:\n FL_OUTPUT_DIR: output\n shell: /bin/bash --login -o pipefail\n\nversion: 2\njobs:\n setup: ...\n\n tests: ...\n\n deploy_ios:\n <<: *mac\n steps:\n - checkout\n - add_ssh_keys:\n fingerprints: \u2014 \u201cSSH_FINGERPRINT_HERE\u201d\n - run:\n name: Git configuration\n command: git config user.email "ci@your.domain" && git config user.name "CircleCI"\n - run:\n name: Set upstream branch\n command: git branch --set-upstream-to origin ${CIRCLE_BRANCH}\n # Node modules\n - restore_cache:\n name: Restore node modules\n keys:\n - v1-dependencies-mac-{{ checksum "package.json" }}\n # fallback to using the latest cache if no exact match is found\n - v1-dependencies-mac-\n - run:\n name: Install dependencies\n command: NOYARNPOSTINSTALL=1 yarn install\n - save_cache:\n name: Save node modules\n paths:\n - node_modules\n key: v1-dependencies-mac-{{ checksum "package.json" }}\n\n # Cocoapods\n - run:\n name: Fetch CocoaPods Specs\n command: |\n curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf\n - run:\n working_directory: ios\n name: Install CocoaPods\n command: pod install --verbose\n\n # Gems\n - restore_cache:\n name: Restore gems\n key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}-{{ arch }}\n - run:\n name: Bundle Install\n command: bundle install\n working_directory: ios\n - save_cache:\n key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}-{{ arch }}\n paths:\n - vendor/bundle\n\n # Misc setup\n - run:\n name: Misc setup\n command: yarn ci:setup\n\n # Git grooming\n - run:\n name: Pull latest git\n command: git stash && git pull && git stash pop\n\n # Run Fastlane\n - run:\n working_directory: ios\n name: Fastlane\n command: bundle exec fastlane ios beta\n\n # Git cleanup\n - run:\n name: Pull latest git\n command: git stash && git pull && git stash pop\n - run:\n name: Push version bump commit\n command: git push\n - store_artifacts:\n path: output\n\nworkflows:\n version: 2\n test_and_release:\n jobs:\n - setup\n - tests:\n requires:\n - setup\n - deploy_ios:\n filters:\n branches:\n only: master\n ```\n')),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"Troubleshooting tips")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},'If you need to debug failed builds, you can use the "Rebuild with SSH" option in CircleCI. See ',(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/docs/2.0/ssh-access-jobs/"},"https://circleci.com/docs/2.0/ssh-access-jobs/")," for more info."),(0,o.kt)("li",{parentName:"ul"},"Tip: make sure you are logged in to Github/CircleCI as yourself (not the CI user) when you hit the button to rebuild with SSH."),(0,o.kt)("li",{parentName:"ul"},"If you get a vague error saying ",(0,o.kt)("inlineCode",{parentName:"li"},"File main.jsbundle does not exist"),", that means there was an error while building the app and you can view the more detailed message by inspecting the log files with the following command (while in SSH mode). Increase the number of lines from 50 as needed."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"tail -50 ios/output/buildlogs/gym/YourProject-YourProject.log"))))}u.isMDXComponent=!0}}]); \ No newline at end of file +"use strict";(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[9340],{3905:(e,t,n)=>{n.d(t,{Zo:()=>p,kt:()=>m});var a=n(7294);function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function r(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var s=a.createContext({}),c=function(e){var t=a.useContext(s),n=t;return e&&(n="function"==typeof e?e(t):r(r({},t),e)),n},p=function(e){var t=c(e.components);return a.createElement(s.Provider,{value:t},e.children)},u={inlineCode:"code",wrapper:function(e){var t=e.children;return a.createElement(a.Fragment,{},t)}},d=a.forwardRef((function(e,t){var n=e.components,o=e.mdxType,i=e.originalType,s=e.parentName,p=l(e,["components","mdxType","originalType","parentName"]),d=c(n),m=o,h=d["".concat(s,".").concat(m)]||d[m]||u[m]||i;return n?a.createElement(h,r(r({ref:t},p),{},{components:n})):a.createElement(h,r({ref:t},p))}));function m(e,t){var n=arguments,o=t&&t.mdxType;if("string"==typeof e||o){var i=n.length,r=new Array(i);r[0]=d;var l={};for(var s in t)hasOwnProperty.call(t,s)&&(l[s]=t[s]);l.originalType=e,l.mdxType="string"==typeof e?e:o,r[1]=l;for(var c=2;c{n.r(t),n.d(t,{assets:()=>s,contentTitle:()=>r,default:()=>u,frontMatter:()=>i,metadata:()=>l,toc:()=>c});var a=n(7462),o=(n(7294),n(3905));const i={title:"CircleCI CD Setup - React Native",description:"Learn how to set up your CircleCI CD instance for React Native",tags:["Guide","CI/CD"],last_update:{author:"Robin Heinze"},publish_date:new Date("2022-10-09T00:00:00.000Z")},r=void 0,l={unversionedId:"recipes/CircleCIRNSetup",id:"recipes/CircleCIRNSetup",title:"CircleCI CD Setup - React Native",description:"Learn how to set up your CircleCI CD instance for React Native",source:"@site/docs/recipes/CircleCIRNSetup.md",sourceDirName:"recipes",slug:"/recipes/CircleCIRNSetup",permalink:"/docs/recipes/CircleCIRNSetup",draft:!1,tags:[{label:"Guide",permalink:"/docs/tags/guide"},{label:"CI/CD",permalink:"/docs/tags/ci-cd"}],version:"current",lastUpdatedBy:"Robin Heinze",lastUpdatedAt:1700228225,formattedLastUpdatedAt:"Nov 17, 2023",frontMatter:{title:"CircleCI CD Setup - React Native",description:"Learn how to set up your CircleCI CD instance for React Native",tags:["Guide","CI/CD"],last_update:{author:"Robin Heinze"},publish_date:"2022-10-09T00:00:00.000Z"},sidebar:"mainSidebar",previous:{title:"Accessiblity Font Sizes",permalink:"/docs/recipes/AccessibilityFontSizes"},next:{title:"Creating a Good Experience for Screen Readers",permalink:"/docs/recipes/CreatingGreateExperienceForScreenReaders"}},s={},c=[{value:"First Things First",id:"first-things-first",level:3},{value:"CircleCI Setup",id:"circleci-setup",level:3},{value:"Continuous Integration",id:"continuous-integration",level:3},{value:"iOS Continuous Deployment",id:"ios-continuous-deployment",level:3}],p={toc:c};function u(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("p",null,"This document shows the steps necessary to set up CircleCI automatic continuous integration testing and automatic Fastlane beta builds upon successfully merging a pull request."),(0,o.kt)("h3",{id:"first-things-first"},"First Things First"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Write Tests")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"If the project already has tests, great. If not, write some."),(0,o.kt)("li",{parentName:"ul"},"See ",(0,o.kt)("a",{parentName:"li",href:"https://github.com/infinitered/ChainReactApp2019"},"this")," for an example of how Infinite Red typically sets up tests for a React Native app.")),(0,o.kt)("h3",{id:"circleci-setup"},"CircleCI Setup"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/vcs-authorize/"},"Log into CircleCI")," with your Github account"),(0,o.kt)("li",{parentName:"ol"},"Choose your organization from the dropdown in the top left"),(0,o.kt)("li",{parentName:"ol"},"Navigate to ",(0,o.kt)("inlineCode",{parentName:"li"},"Add Projects")," on the left"),(0,o.kt)("li",{parentName:"ol"},"Search for your repo"),(0,o.kt)("li",{parentName:"ol"},"Choose Set Up Project"),(0,o.kt)("li",{parentName:"ol"},"Set Up Project")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Select ",(0,o.kt)("inlineCode",{parentName:"li"},"macOS")," for the operating system and Other for the language")),(0,o.kt)("ol",{start:7},(0,o.kt)("li",{parentName:"ol"},"Copy the basic ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml")," to ",(0,o.kt)("inlineCode",{parentName:"li"},".circleci/config.yml"),", commit your code changes and push to github ",(0,o.kt)("inlineCode",{parentName:"li"},"master")),(0,o.kt)("li",{parentName:"ol"},"Choose ",(0,o.kt)("inlineCode",{parentName:"li"},"Start building")," to initiate the first CI build. This build will fail. That's ok. We will update the config in the next step."),(0,o.kt)("li",{parentName:"ol"},"Enable builds from forked pull requests. Go to project settings > Advanced Settings, then toggle on ",(0,o.kt)("inlineCode",{parentName:"li"},"Build forked pull requests")),(0,o.kt)("li",{parentName:"ol"},"If this project is open-source, you'll want to make sure the open-source setting is enabled to allow for macOS builds. Go to project settings > Advanced Settings, then toggle ",(0,o.kt)("inlineCode",{parentName:"li"},"Free and Open Source"),".")),(0,o.kt)("h3",{id:"continuous-integration"},"Continuous Integration"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Create a folder in the project root named ",(0,o.kt)("inlineCode",{parentName:"li"},".circleci"),"."),(0,o.kt)("li",{parentName:"ol"},"Create a file inside that folder named ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml")),(0,o.kt)("li",{parentName:"ol"},"Use the below template in that file."),(0,o.kt)("li",{parentName:"ol"},"If needed, see ",(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/docs/2.0/config-intro/#section=configuration"},"configuration docs")," for additional configuration options. (",(0,o.kt)("em",{parentName:"li"},"Here is a complete ",(0,o.kt)("a",{parentName:"em",href:"https://github.com/YOUR_ORG/open-source/blob/master/config.example.yml"},"config.yml")," with CI and CD steps completed"),")")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-yaml"},'defaults: &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 keys:\n - v1-dependencies-node-{{ checksum "package.json" }}\n # fallback to using the latest cache if no exact match is found\n - v1-dependencies-node-\n - run:\n name: Install dependencies\n command: yarn install\n - save_cache:\n name: Save node modules\n paths:\n - node_modules\n key: v1-dependencies-node-{{ checksum "package.json" }}\n\n tests:\n <<: *defaults\n steps:\n - checkout\n - restore_cache:\n keys:\n - v1-dependencies-node-{{ checksum "package.json" }}\n # fallback to using the latest cache if no exact match is found\n - v1-dependencies-node-\n - run:\n name: Run tests\n command: yarn ci:test # this command will be added to/found in your package.json scripts\n\nworkflows:\n version: 2\n test_and_release:\n jobs:\n - setup\n - tests:\n requires:\n - setup\n')),(0,o.kt)("ol",{start:5},(0,o.kt)("li",{parentName:"ol"},"Make sure the test script is added to your ",(0,o.kt)("inlineCode",{parentName:"li"},"package.json"))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-json"},' {\n ...\n "scripts": {\n ...\n "ci:test": "" <<-- if you don\'t already have this one\n },\n ...\n }\n')),(0,o.kt)("h3",{id:"ios-continuous-deployment"},"iOS Continuous Deployment"),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"Add Fastlane")),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Before you can add continuous deployment, you'll need to setup Fastlane and Match to sign and deploy your app. You can follow these blog posts to get setup!")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("a",{parentName:"li",href:"https://shift.infinite.red/simple-react-native-ios-releases-4c28bb53a97b"},"Releasing on iOS with Fastlane")," Make sure you get to the point of being able to run:"),(0,o.kt)("li",{parentName:"ul"},"fastlane ios beta")),(0,o.kt)("ol",{start:2},(0,o.kt)("li",{parentName:"ol"},"In your Fastfile, add:")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-ruby"},"before_all do\n setup_circle_ci\nend\n")),(0,o.kt)("ol",{start:3},(0,o.kt)("li",{parentName:"ol"},"In your ",(0,o.kt)("inlineCode",{parentName:"li"},"beta")," lane, make sure you have included a command that bumps the build number prior to building, and then commits the build number after building.\nExample:")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-ruby"},'PROJECT = "YourProject"\nXCODE_PROJECT = "#{PROJECT}.xcodeproj"\n lane :beta do\n increment_build_number(xcodeproj: "./#{XCODE_PROJECT}")\n match(type: "appstore")\n build_ios_app(\n scheme: PROJECT,\n workspace: "./YourProject.xcworkspace",\n xcargs: "-UseNewBuildSystem=NO -allowProvisioningUpdates",\n export_method: "app-store"\n )\n # Ship it!\n upload_to_testflight(\n skip_waiting_for_build_processing: true\n )\n commit_version_bump(\n xcodeproj: "./#{XCODE_PROJECT}",\n ignore: /tvOS/,\n force: true,\n message: "[skip ci] Version bump"\n )\n end\n')),(0,o.kt)("ol",{start:4},(0,o.kt)("li",{parentName:"ol"},"If you prefer, you can also do these steps as separate fastlane commands, just make sure to include a ",(0,o.kt)("inlineCode",{parentName:"li"},"- run:")," entry for each one in ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml"),".")),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"Setting up CircleCI to Run Fastlane"),"\nCheck out ",(0,o.kt)("a",{parentName:"p",href:"https://medium.com/@odedre/circle-ci-v2-react-native-project-build-release-setup-ce4ef31209d0"},"this blog post")," for lots of helpful tips."),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Make sure CircleCI has all the credentials to run your fastlane scripts:",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Go into the Settings screen for your project on CircleCI"),(0,o.kt)("li",{parentName:"ul"},'Under "Build Settings", click on "Environment Variables" (',(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/gh/YOUR_ORG/YOURPROJECT/edit#env-vars"},"https://circleci.com/gh/YOUR_ORG/YOURPROJECT/edit#env-vars"),")"),(0,o.kt)("li",{parentName:"ul"},'Click "Add Variable"'),(0,o.kt)("li",{parentName:"ul"},"Set ",(0,o.kt)("inlineCode",{parentName:"li"},"FASTLANE_USER")," to the email address of your your Apple App Store Connect / Dev Portal user."),(0,o.kt)("li",{parentName:"ul"},"Do this for all of the variables listed ",(0,o.kt)("a",{parentName:"li",href:"https://github.com/fastlane/docs/blob/950c6f42231d86b5187d2cfdcab2a6c81d0f61dc/docs/best-practices/continuous-integration.md#environment-variables-to-set"},"here")," ",(0,o.kt)("strong",{parentName:"li"},"Note"),": If your dev portal user does not have 2-Factor Auth turned on, you DO NOT need to set FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD. Including this variable when your account does need it will result in errors during TestFlight upload. You can find more info from the Fastlane Docs, and from the CircleCI codesigning docs"))),(0,o.kt)("li",{parentName:"ol"},"Add ",(0,o.kt)("inlineCode",{parentName:"li"},"GITHUB_TOKEN")," to env vars on CircleCI (",(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/gh/YOUR_ORG/YOURPROJECT/edit#env-vars"},"https://circleci.com/gh/YOUR_ORG/YOURPROJECT/edit#env-vars"),"). Y")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"If you need to make a new ",(0,o.kt)("inlineCode",{parentName:"li"},"GITHUB_TOKEN"),", go to ",(0,o.kt)("a",{parentName:"li",href:"https://github.com/settings/tokens/new"},"https://github.com/settings/tokens/new")," and create a new one with ",(0,o.kt)("inlineCode",{parentName:"li"},"repo")," access.")),(0,o.kt)("ol",{start:3},(0,o.kt)("li",{parentName:"ol"},"Add the ",(0,o.kt)("inlineCode",{parentName:"li"},"Circle CI")," Github team to your repo (",(0,o.kt)("a",{parentName:"li",href:"https://github.com/YOUR_ORG/YOURPROJECT/settings/collaboration"},"https://github.com/YOUR_ORG/YOURPROJECT/settings/collaboration"),") with write access."),(0,o.kt)("li",{parentName:"ol"},"Add the ",(0,o.kt)("inlineCode",{parentName:"li"},"Circle CI")," Github team as a read-only collaborator to the private match certificates repo."),(0,o.kt)("li",{parentName:"ol"},"Log in to GitHub/CircleCI as the CI user. Then in CircleCI, go to Project Settings > Checkout SSH keys (",(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/gh/YOURORGANIZATION/YOURPROJECT/edit#checkout"},"https://circleci.com/gh/YOURORGANIZATION/YOURPROJECT/edit#checkout"),") and add a new user key. This will allow CircleCI to clone the certs repo in order to sign your app."),(0,o.kt)("li",{parentName:"ol"},"Go to Project Settings > Checkout SSH Keys and add a new deploy key. You will copy the fingerprint and paste into the ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml")," example below in the ",(0,o.kt)("inlineCode",{parentName:"li"},"add_ssh_keys")," section (there should be ",(0,o.kt)("inlineCode",{parentName:"li"},'"'),"s around it)"),(0,o.kt)("li",{parentName:"ol"},"Add a script to your ",(0,o.kt)("inlineCode",{parentName:"li"},"package.json")," called ",(0,o.kt)("inlineCode",{parentName:"li"},"ci:setup"),". This will run any necessary shell commands to prepare your project for building. For example, creating private files like ",(0,o.kt)("inlineCode",{parentName:"li"},".env"),'. If you don\'t any additional setup, you can leave this command as "", or remove the ',(0,o.kt)("inlineCode",{parentName:"li"},"ci:setup")," step from the ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml")," example below.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-json"},' {\n ...\n "scripts": {\n ...\n "ci:setup": "touch .env && echo \\"ENV_VAR=\\"$ENV_VAR >> .env",\n },\n ...\n }\n')),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"Note"),": ",(0,o.kt)("inlineCode",{parentName:"p"},"react-native-dotenv")," throws errors if there is not a ",(0,o.kt)("inlineCode",{parentName:"p"},".env")," present with the variables it expects. However, if you don't want to put secret values in this script (you shouldn't), then you can add them directly to CircleCI under Project Settings > Environment Variables. Then you can reference them in this script as ",(0,o.kt)("inlineCode",{parentName:"p"},"$ENV_VAR"),"."),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Add ",(0,o.kt)("inlineCode",{parentName:"li"},"mac")," configuration and ",(0,o.kt)("inlineCode",{parentName:"li"},"deploy_ios")," job to your CircleCI ",(0,o.kt)("inlineCode",{parentName:"li"},"config.yml"),"\nNOTE: The macOS boxes currently come with Node 11.0, with no apparent way to change the version. This shouldn't be a huge problem. One known issue is with ",(0,o.kt)("inlineCode",{parentName:"li"},"upath"),", which is a deep dependency of react-native. If you encounter errors related to ",(0,o.kt)("inlineCode",{parentName:"li"},"upath")," requiring a lower version of Node, just make sure it is at ",(0,o.kt)("inlineCode",{parentName:"li"},"1.1.0"),", and not ",(0,o.kt)("inlineCode",{parentName:"li"},"1.0.4")," in your yarn.lock. See ",(0,o.kt)("a",{parentName:"li",href:"https://github.com/airbnb/enzyme/issues/1637#issuecomment-397327562"},"https://github.com/airbnb/enzyme/issues/1637#issuecomment-397327562"),".")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-yaml"},'defaults: ...\n\nmac: &mac\n macos:\n xcode: "10.1.0"\n working_directory: ~/repo\n environment:\n FL_OUTPUT_DIR: output\n shell: /bin/bash --login -o pipefail\n\nversion: 2\njobs:\n setup: ...\n\n tests: ...\n\n deploy_ios:\n <<: *mac\n steps:\n - checkout\n - add_ssh_keys:\n fingerprints: \u2014 \u201cSSH_FINGERPRINT_HERE\u201d\n - run:\n name: Git configuration\n command: git config user.email "ci@your.domain" && git config user.name "CircleCI"\n - run:\n name: Set upstream branch\n command: git branch --set-upstream-to origin ${CIRCLE_BRANCH}\n # Node modules\n - restore_cache:\n name: Restore node modules\n keys:\n - v1-dependencies-mac-{{ checksum "package.json" }}\n # fallback to using the latest cache if no exact match is found\n - v1-dependencies-mac-\n - run:\n name: Install dependencies\n command: NOYARNPOSTINSTALL=1 yarn install\n - save_cache:\n name: Save node modules\n paths:\n - node_modules\n key: v1-dependencies-mac-{{ checksum "package.json" }}\n\n # Cocoapods\n - run:\n name: Fetch CocoaPods Specs\n command: |\n curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf\n - run:\n working_directory: ios\n name: Install CocoaPods\n command: pod install --verbose\n\n # Gems\n - restore_cache:\n name: Restore gems\n key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}-{{ arch }}\n - run:\n name: Bundle Install\n command: bundle install\n working_directory: ios\n - save_cache:\n key: bundle-v1-{{ checksum "ios/Gemfile.lock" }}-{{ arch }}\n paths:\n - vendor/bundle\n\n # Misc setup\n - run:\n name: Misc setup\n command: yarn ci:setup\n\n # Git grooming\n - run:\n name: Pull latest git\n command: git stash && git pull && git stash pop\n\n # Run Fastlane\n - run:\n working_directory: ios\n name: Fastlane\n command: bundle exec fastlane ios beta\n\n # Git cleanup\n - run:\n name: Pull latest git\n command: git stash && git pull && git stash pop\n - run:\n name: Push version bump commit\n command: git push\n - store_artifacts:\n path: output\n\nworkflows:\n version: 2\n test_and_release:\n jobs:\n - setup\n - tests:\n requires:\n - setup\n - deploy_ios:\n filters:\n branches:\n only: master\n ```\n')),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"Troubleshooting tips")),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},'If you need to debug failed builds, you can use the "Rebuild with SSH" option in CircleCI. See ',(0,o.kt)("a",{parentName:"li",href:"https://circleci.com/docs/2.0/ssh-access-jobs/"},"https://circleci.com/docs/2.0/ssh-access-jobs/")," for more info."),(0,o.kt)("li",{parentName:"ul"},"Tip: make sure you are logged in to Github/CircleCI as yourself (not the CI user) when you hit the button to rebuild with SSH."),(0,o.kt)("li",{parentName:"ul"},"If you get a vague error saying ",(0,o.kt)("inlineCode",{parentName:"li"},"File main.jsbundle does not exist"),", that means there was an error while building the app and you can view the more detailed message by inspecting the log files with the following command (while in SSH mode). Increase the number of lines from 50 as needed."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"tail -50 ios/output/buildlogs/gym/YourProject-YourProject.log"))))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/main.ae25f071.js b/assets/js/main.ae25f071.js deleted file mode 100644 index e1473105..00000000 --- a/assets/js/main.ae25f071.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! For license information please see main.ae25f071.js.LICENSE.txt */ -(self.webpackChunkignite_cookbook=self.webpackChunkignite_cookbook||[]).push([[179],{723:(e,t,n)=>{"use strict";n.d(t,{Z:()=>f});var r=n(7294),o=n(7462),a=n(8356),i=n.n(a),l=n(6887);const s={"03224537":[()=>n.e(6275).then(n.bind(n,7527)),"@site/docs/recipes/DetoxIntro.md",7527],"08a54ed9":[()=>n.e(4803).then(n.t.bind(n,7562,19)),"~docs/default/tag-docs-tags-expo-updates-8e6.json",7562],"0e384e19":[()=>n.e(9671).then(n.bind(n,9881)),"@site/docs/intro.md",9881],"145425bc":[()=>n.e(3188).then(n.bind(n,9556)),"@site/docs/recipes/AccessibilityFontSizes.md",9556],17896441:[()=>Promise.all([n.e(532),n.e(7918)]).then(n.bind(n,6102)),"@theme/DocItem",6102],"19d620af":[()=>n.e(5834).then(n.t.bind(n,5041,19)),"~docs/default/tag-docs-tags-ui-22c.json",5041],"1ab29606":[()=>n.e(5856).then(n.t.bind(n,7611,19)),"~docs/default/tag-docs-tags-apisauce-8de.json",7611],"1be78505":[()=>Promise.all([n.e(532),n.e(9514)]).then(n.bind(n,9963)),"@theme/DocPage",9963],"1c9ea255":[()=>n.e(420).then(n.bind(n,5882)),"@site/docs/recipes/TypeScriptBaseURL.md",5882],"1df93b7f":[()=>Promise.all([n.e(532),n.e(1762),n.e(3237)]).then(n.bind(n,2354)),"@site/src/pages/index.tsx",2354],"24a07a83":[()=>n.e(4871).then(n.t.bind(n,7222,19)),"~docs/default/tag-docs-tags-generator-e43.json",7222],"24c3776a":[()=>n.e(7357).then(n.bind(n,4066)),"@site/docs/recipes/DistributingAuthTokenToAPI.md",4066],"2b5cb6b8":[()=>n.e(4598).then(n.t.bind(n,3191,19)),"~docs/default/tag-docs-tags-flash-list-de9.json",3191],"3192f89a":[()=>n.e(9853).then(n.t.bind(n,5745,19)),"/home/runner/work/ignite-cookbook/ignite-cookbook/.docusaurus/docusaurus-plugin-content-pages/default/plugin-route-context-module-100.json",5745],"33f24359":[()=>n.e(5766).then(n.t.bind(n,3639,19)),"~docs/default/tag-docs-tags-eas-update-bce.json",3639],"3720c009":[()=>Promise.all([n.e(532),n.e(3751)]).then(n.bind(n,6406)),"@theme/DocTagsListPage",6406],"3bb7a4af":[()=>n.e(6378).then(n.t.bind(n,6522,19)),"~docs/default/tag-docs-tags-i-os-d44.json",6522],"47a03c7f":[()=>n.e(1606).then(n.t.bind(n,5458,19)),"~docs/default/tag-docs-tags-testing-194.json",5458],"4a4fb967":[()=>n.e(3549).then(n.t.bind(n,9294,19)),"~docs/default/tag-docs-tags-accessibility-83c.json",9294],"4e09609f":[()=>n.e(8154).then(n.bind(n,787)),"@site/docs/recipes/SampleYAMLCircleCI.md",787],"501cbb42":[()=>n.e(4930).then(n.t.bind(n,9492,19)),"~docs/default/tag-docs-tags-shopify-d9d.json",9492],"51658ad1":[()=>n.e(5075).then(n.t.bind(n,9971,19)),"~docs/default/tag-docs-tags-intro-ce4.json",9971],"51e76fb5":[()=>n.e(7940).then(n.bind(n,9437)),"@site/docs/recipes/MaestroSetup.md",9437],"55960ee5":[()=>n.e(4121).then(n.t.bind(n,8070,19)),"~docs/default/tags-list-current-prop-15a.json",8070],"5e9f5e1a":[()=>Promise.resolve().then(n.bind(n,6809)),"@generated/docusaurus.config",6809],63181745:[()=>n.e(3441).then(n.bind(n,9202)),"@site/docs/recipes/UsingScreenReaders.md",9202],"657027a7":[()=>n.e(5893).then(n.bind(n,3218)),"@site/docs/recipes/MigratingToMMKV.md",3218],"6728e797":[()=>n.e(3362).then(n.t.bind(n,8686,19)),"~docs/default/tag-docs-tags-android-593.json",8686],"72dfd944":[()=>n.e(3166).then(n.t.bind(n,2117,19)),"~docs/default/tag-docs-tags-text-field-8cc.json",2117],"7acb6f50":[()=>n.e(7508).then(n.t.bind(n,5690,19)),"~docs/default/tag-docs-tags-yarn-9bd.json",5690],"7b45617e":[()=>n.e(1833).then(n.bind(n,1830)),"@site/docs/recipes/GeneratorComponentTests.md",1830],"861d4b97":[()=>n.e(4011).then(n.bind(n,9014)),"@site/docs/recipes/PristineExpoProject.md",9014],"8b0d950b":[()=>n.e(170).then(n.t.bind(n,5271,19)),"~docs/default/tag-docs-tags-dependencies-d4f.json",5271],"935f2afb":[()=>n.e(53).then(n.t.bind(n,1109,19)),"~docs/default/version-current-metadata-prop-751.json",1109],"9dc0f37b":[()=>n.e(1498).then(n.t.bind(n,5046,19)),"~docs/default/tag-docs-tags-section-list-198.json",5046],"9fc76e3d":[()=>n.e(6247).then(n.bind(n,2183)),"@site/docs/recipes/EASUpdate.md",2183],"9ff7e1f8":[()=>n.e(2181).then(n.bind(n,1218)),"@site/docs/recipes/MigratingToFlashList.md",1218],a0d6a633:[()=>n.e(7043).then(n.t.bind(n,436,19)),"~docs/default/tag-docs-tags-expo-dev-client-4bf.json",436],a8646ade:[()=>n.e(9179).then(n.t.bind(n,1527,19)),"~docs/default/tag-docs-tags-mmkv-046.json",1527],a8f9d519:[()=>n.e(8674).then(n.t.bind(n,7685,19)),"~docs/default/tag-docs-tags-scroll-to-8d4.json",7685],a951c726:[()=>n.e(7738).then(n.t.bind(n,5404,19)),"~docs/default/tag-docs-tags-ci-cd-ed9.json",5404],b3d1732c:[()=>n.e(6610).then(n.t.bind(n,3477,19)),"~docs/default/tag-docs-tags-babel-ecc.json",3477],b8c37621:[()=>n.e(920).then(n.t.bind(n,3908,19)),"~docs/default/tag-docs-tags-guide-a32.json",3908],c3e2f4d4:[()=>n.e(3135).then(n.bind(n,9262)),"@site/docs/recipes/UnrenderedItemInScrollView.md",9262],ce17b301:[()=>n.e(4796).then(n.t.bind(n,1072,19)),"~docs/default/tag-docs-tags-expo-4de.json",1072],d01c4de2:[()=>n.e(7145).then(n.t.bind(n,777,19)),"~docs/default/tag-docs-tags-async-storage-da0.json",777],d0e08e4a:[()=>n.e(6862).then(n.bind(n,6368)),"@site/docs/recipes/PatchingBuildingAndroid.md",6368],d63d2b89:[()=>Promise.all([n.e(1068),n.e(1981)]).then(n.bind(n,7875)),"@site/docs/recipes/SelectFieldWithBottomSheet.mdx",7875],d6ab422f:[()=>n.e(6605).then(n.bind(n,8369)),"@site/docs/recipes/EnvironmentVariables.md",8369],da9277bc:[()=>n.e(2182).then(n.bind(n,9949)),"@site/docs/recipes/UpdatingDependencies.md",9949],dce6faa4:[()=>n.e(7376).then(n.t.bind(n,6853,19)),"~docs/default/tag-docs-tags-select-field-e86.json",6853],dec1aed8:[()=>n.e(8526).then(n.t.bind(n,1409,19)),"~docs/default/tag-docs-tags-maestro-a0f.json",1409],df203c0f:[()=>n.e(9924).then(n.bind(n,7068)),"@theme/DocTagDocListPage",7068],e0854532:[()=>n.e(5318).then(n.t.bind(n,6635,19)),"~docs/default/tag-docs-tags-flat-list-bca.json",6635],e1b6b0a8:[()=>n.e(752).then(n.t.bind(n,8531,19)),"~docs/default/tag-docs-tags-debug-69b.json",8531],e438b990:[()=>n.e(1065).then(n.bind(n,1606)),"@site/docs/recipes/StayingWithExpo.md",1606],e7928ab4:[()=>n.e(327).then(n.t.bind(n,3769,19)),"/home/runner/work/ignite-cookbook/ignite-cookbook/.docusaurus/docusaurus-plugin-content-docs/default/plugin-route-context-module-100.json",3769],ecce3b64:[()=>n.e(1784).then(n.t.bind(n,6103,19)),"~docs/default/tag-docs-tags-authentication-9a3.json",6103],f523b160:[()=>n.e(6649).then(n.bind(n,4488)),"@site/docs/recipes/CreatingGreateExperienceForScreenReaders.md",4488],fd1937a7:[()=>n.e(4881).then(n.t.bind(n,1857,19)),"~docs/default/tag-docs-tags-environment-variables-3be.json",1857],fe9b09bf:[()=>n.e(9340).then(n.bind(n,476)),"@site/docs/recipes/CircleCIRNSetup.md",476],ff2c7cca:[()=>n.e(7706).then(n.t.bind(n,5028,19)),"~docs/default/tag-docs-tags-type-script-6e5.json",5028]};function c(e){let{error:t,retry:n,pastDelay:o}=e;return t?r.createElement("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%"}},r.createElement("p",null,String(t)),r.createElement("div",null,r.createElement("button",{type:"button",onClick:n},"Retry"))):o?r.createElement("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh"}},r.createElement("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"},r.createElement("g",{fill:"none",fillRule:"evenodd",transform:"translate(1 1)",strokeWidth:"2"},r.createElement("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0"},r.createElement("animate",{attributeName:"r",begin:"1.5s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-opacity",begin:"1.5s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-width",begin:"1.5s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})),r.createElement("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0"},r.createElement("animate",{attributeName:"r",begin:"3s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-opacity",begin:"3s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),r.createElement("animate",{attributeName:"stroke-width",begin:"3s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})),r.createElement("circle",{cx:"22",cy:"22",r:"8"},r.createElement("animate",{attributeName:"r",begin:"0s",dur:"1.5s",values:"6;1;2;3;4;5;6",calcMode:"linear",repeatCount:"indefinite"}))))):null}var u=n(9670),d=n(226);function p(e,t){if("*"===e)return i()({loading:c,loader:()=>n.e(4972).then(n.bind(n,4972)),modules:["@theme/NotFound"],webpack:()=>[4972],render(e,t){const n=e.default;return r.createElement(d.z,{value:{plugin:{name:"native",id:"default"}}},r.createElement(n,t))}});const a=l[e+"-"+t],p={},f=[],m=[],h=(0,u.Z)(a);return Object.entries(h).forEach((e=>{let[t,n]=e;const r=s[n];r&&(p[t]=r[0],f.push(r[1]),m.push(r[2]))})),i().Map({loading:c,loader:p,modules:f,webpack:()=>m,render(t,n){const i=JSON.parse(JSON.stringify(a));Object.entries(t).forEach((t=>{let[n,r]=t;const o=r.default;if(!o)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 o&&"function"!=typeof o||Object.keys(r).filter((e=>"default"!==e)).forEach((e=>{o[e]=r[e]}));let a=i;const l=n.split(".");l.slice(0,-1).forEach((e=>{a=a[e]})),a[l[l.length-1]]=o}));const l=i.__comp;delete i.__comp;const s=i.__context;return delete i.__context,r.createElement(d.z,{value:s},r.createElement(l,(0,o.Z)({},i,n)))}})}const f=[{path:"/docs/tags",component:p("/docs/tags","027"),exact:!0},{path:"/docs/tags/accessibility",component:p("/docs/tags/accessibility","2da"),exact:!0},{path:"/docs/tags/android",component:p("/docs/tags/android","c64"),exact:!0},{path:"/docs/tags/apisauce",component:p("/docs/tags/apisauce","3ec"),exact:!0},{path:"/docs/tags/async-storage",component:p("/docs/tags/async-storage","565"),exact:!0},{path:"/docs/tags/authentication",component:p("/docs/tags/authentication","b45"),exact:!0},{path:"/docs/tags/babel",component:p("/docs/tags/babel","43e"),exact:!0},{path:"/docs/tags/ci-cd",component:p("/docs/tags/ci-cd","0c3"),exact:!0},{path:"/docs/tags/debug",component:p("/docs/tags/debug","8d2"),exact:!0},{path:"/docs/tags/dependencies",component:p("/docs/tags/dependencies","95d"),exact:!0},{path:"/docs/tags/eas-update",component:p("/docs/tags/eas-update","2cd"),exact:!0},{path:"/docs/tags/environment-variables",component:p("/docs/tags/environment-variables","b25"),exact:!0},{path:"/docs/tags/expo",component:p("/docs/tags/expo","b96"),exact:!0},{path:"/docs/tags/expo-dev-client",component:p("/docs/tags/expo-dev-client","b4f"),exact:!0},{path:"/docs/tags/expo-updates",component:p("/docs/tags/expo-updates","d12"),exact:!0},{path:"/docs/tags/flash-list",component:p("/docs/tags/flash-list","bc3"),exact:!0},{path:"/docs/tags/flat-list",component:p("/docs/tags/flat-list","ed3"),exact:!0},{path:"/docs/tags/generator",component:p("/docs/tags/generator","431"),exact:!0},{path:"/docs/tags/guide",component:p("/docs/tags/guide","462"),exact:!0},{path:"/docs/tags/i-os",component:p("/docs/tags/i-os","fa6"),exact:!0},{path:"/docs/tags/intro",component:p("/docs/tags/intro","409"),exact:!0},{path:"/docs/tags/maestro",component:p("/docs/tags/maestro","e64"),exact:!0},{path:"/docs/tags/mmkv",component:p("/docs/tags/mmkv","486"),exact:!0},{path:"/docs/tags/scroll-to",component:p("/docs/tags/scroll-to","893"),exact:!0},{path:"/docs/tags/section-list",component:p("/docs/tags/section-list","207"),exact:!0},{path:"/docs/tags/select-field",component:p("/docs/tags/select-field","e66"),exact:!0},{path:"/docs/tags/shopify",component:p("/docs/tags/shopify","354"),exact:!0},{path:"/docs/tags/testing",component:p("/docs/tags/testing","e58"),exact:!0},{path:"/docs/tags/text-field",component:p("/docs/tags/text-field","377"),exact:!0},{path:"/docs/tags/type-script",component:p("/docs/tags/type-script","747"),exact:!0},{path:"/docs/tags/ui",component:p("/docs/tags/ui","f55"),exact:!0},{path:"/docs/tags/yarn",component:p("/docs/tags/yarn","aac"),exact:!0},{path:"/docs",component:p("/docs","cf3"),routes:[{path:"/docs/intro",component:p("/docs/intro","194"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/AccessibilityFontSizes",component:p("/docs/recipes/AccessibilityFontSizes","872"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/CircleCIRNSetup",component:p("/docs/recipes/CircleCIRNSetup","ce2"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/CreatingGreateExperienceForScreenReaders",component:p("/docs/recipes/CreatingGreateExperienceForScreenReaders","752"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/DetoxIntro",component:p("/docs/recipes/DetoxIntro","700"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/DistributingAuthTokenToAPI",component:p("/docs/recipes/DistributingAuthTokenToAPI","298"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/EASUpdate",component:p("/docs/recipes/EASUpdate","938"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/EnvironmentVariables",component:p("/docs/recipes/EnvironmentVariables","92b"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/GeneratorComponentTests",component:p("/docs/recipes/GeneratorComponentTests","e55"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/MaestroSetup",component:p("/docs/recipes/MaestroSetup","237"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/MigratingToFlashList",component:p("/docs/recipes/MigratingToFlashList","532"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/MigratingToMMKV",component:p("/docs/recipes/MigratingToMMKV","9d0"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/PatchingBuildingAndroid",component:p("/docs/recipes/PatchingBuildingAndroid","ac8"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/PristineExpoProject",component:p("/docs/recipes/PristineExpoProject","620"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/SampleYAMLCircleCI",component:p("/docs/recipes/SampleYAMLCircleCI","30a"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/SelectFieldWithBottomSheet",component:p("/docs/recipes/SelectFieldWithBottomSheet","ca3"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/StayingWithExpo",component:p("/docs/recipes/StayingWithExpo","e59"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/TypeScriptBaseURL",component:p("/docs/recipes/TypeScriptBaseURL","986"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/UnrenderedItemInScrollView",component:p("/docs/recipes/UnrenderedItemInScrollView","c15"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/UpdatingDependencies",component:p("/docs/recipes/UpdatingDependencies","21c"),exact:!0,sidebar:"mainSidebar"},{path:"/docs/recipes/UsingScreenReaders",component:p("/docs/recipes/UsingScreenReaders","324"),exact:!0,sidebar:"mainSidebar"}]},{path:"/",component:p("/","703"),exact:!0},{path:"*",component:p("*")}]},8965:(e,t,n)=>{var r,o;!function(){var a,i,l,s,c,u,d,p,f,m,h,g,v,b,y,w,S,k,E,_,x,O,T,P,C,I,A=function(e){var t=new A.Builder;return t.pipeline.add(A.trimmer,A.stopWordFilter,A.stemmer),t.searchPipeline.add(A.stemmer),e.call(t,t),t.build()};A.version="2.3.9",A.utils={},A.utils.warn=(a=this,function(e){a.console&&console.warn&&console.warn(e)}),A.utils.asString=function(e){return null==e?"":e.toString()},A.utils.clone=function(e){if(null==e)return e;for(var t=Object.create(null),n=Object.keys(e),r=0;r0){var s=A.utils.clone(t)||{};s.position=[i,l],s.index=o.length,o.push(new A.Token(n.slice(i,a),s))}i=a+1}}return o},A.tokenizer.separator=/[\s\-]+/,A.Pipeline=function(){this._stack=[]},A.Pipeline.registeredFunctions=Object.create(null),A.Pipeline.registerFunction=function(e,t){t in this.registeredFunctions&&A.utils.warn("Overwriting existing registered function: "+t),e.label=t,A.Pipeline.registeredFunctions[e.label]=e},A.Pipeline.warnIfFunctionNotRegistered=function(e){e.label&&e.label in this.registeredFunctions||A.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},A.Pipeline.load=function(e){var t=new A.Pipeline;return e.forEach((function(e){var n=A.Pipeline.registeredFunctions[e];if(!n)throw new Error("Cannot load unregistered function: "+e);t.add(n)})),t},A.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach((function(e){A.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)}),this)},A.Pipeline.prototype.after=function(e,t){A.Pipeline.warnIfFunctionNotRegistered(t);var n=this._stack.indexOf(e);if(-1==n)throw new Error("Cannot find existingFn");n+=1,this._stack.splice(n,0,t)},A.Pipeline.prototype.before=function(e,t){A.Pipeline.warnIfFunctionNotRegistered(t);var n=this._stack.indexOf(e);if(-1==n)throw new Error("Cannot find existingFn");this._stack.splice(n,0,t)},A.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);-1!=t&&this._stack.splice(t,1)},A.Pipeline.prototype.run=function(e){for(var t=this._stack.length,n=0;n1&&(ae&&(n=o),a!=e);)r=n-t,o=t+Math.floor(r/2),a=this.elements[2*o];return a==e||a>e?2*o:al?c+=2:i==l&&(t+=n[s+1]*r[c+1],s+=2,c+=2);return t},A.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},A.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,n=0;t0){var a,i=o.str.charAt(0);i in o.node.edges?a=o.node.edges[i]:(a=new A.TokenSet,o.node.edges[i]=a),1==o.str.length&&(a.final=!0),r.push({node:a,editsRemaining:o.editsRemaining,str:o.str.slice(1)})}if(0!=o.editsRemaining){if("*"in o.node.edges)var l=o.node.edges["*"];else{l=new A.TokenSet;o.node.edges["*"]=l}if(0==o.str.length&&(l.final=!0),r.push({node:l,editsRemaining:o.editsRemaining-1,str:o.str}),o.str.length>1&&r.push({node:o.node,editsRemaining:o.editsRemaining-1,str:o.str.slice(1)}),1==o.str.length&&(o.node.final=!0),o.str.length>=1){if("*"in o.node.edges)var s=o.node.edges["*"];else{s=new A.TokenSet;o.node.edges["*"]=s}1==o.str.length&&(s.final=!0),r.push({node:s,editsRemaining:o.editsRemaining-1,str:o.str.slice(1)})}if(o.str.length>1){var c,u=o.str.charAt(0),d=o.str.charAt(1);d in o.node.edges?c=o.node.edges[d]:(c=new A.TokenSet,o.node.edges[d]=c),1==o.str.length&&(c.final=!0),r.push({node:c,editsRemaining:o.editsRemaining-1,str:u+o.str.slice(2)})}}}return n},A.TokenSet.fromString=function(e){for(var t=new A.TokenSet,n=t,r=0,o=e.length;r=e;t--){var n=this.uncheckedNodes[t],r=n.child.toString();r in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[r]:(n.child._str=r,this.minimizedNodes[r]=n.child),this.uncheckedNodes.pop()}},A.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},A.Index.prototype.search=function(e){return this.query((function(t){new A.QueryParser(e,t).parse()}))},A.Index.prototype.query=function(e){for(var t=new A.Query(this.fields),n=Object.create(null),r=Object.create(null),o=Object.create(null),a=Object.create(null),i=Object.create(null),l=0;l1?1:e},A.Builder.prototype.k1=function(e){this._k1=e},A.Builder.prototype.add=function(e,t){var n=e[this._ref],r=Object.keys(this._fields);this._documents[n]=t||{},this.documentCount+=1;for(var o=0;o=this.length)return A.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},A.QueryLexer.prototype.width=function(){return this.pos-this.start},A.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},A.QueryLexer.prototype.backup=function(){this.pos-=1},A.QueryLexer.prototype.acceptDigitRun=function(){var e,t;do{t=(e=this.next()).charCodeAt(0)}while(t>47&&t<58);e!=A.QueryLexer.EOS&&this.backup()},A.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(A.QueryLexer.TERM)),e.ignore(),e.more())return A.QueryLexer.lexText},A.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(A.QueryLexer.EDIT_DISTANCE),A.QueryLexer.lexText},A.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(A.QueryLexer.BOOST),A.QueryLexer.lexText},A.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(A.QueryLexer.TERM)},A.QueryLexer.termSeparator=A.tokenizer.separator,A.QueryLexer.lexText=function(e){for(;;){var t=e.next();if(t==A.QueryLexer.EOS)return A.QueryLexer.lexEOS;if(92!=t.charCodeAt(0)){if(":"==t)return A.QueryLexer.lexField;if("~"==t)return e.backup(),e.width()>0&&e.emit(A.QueryLexer.TERM),A.QueryLexer.lexEditDistance;if("^"==t)return e.backup(),e.width()>0&&e.emit(A.QueryLexer.TERM),A.QueryLexer.lexBoost;if("+"==t&&1===e.width())return e.emit(A.QueryLexer.PRESENCE),A.QueryLexer.lexText;if("-"==t&&1===e.width())return e.emit(A.QueryLexer.PRESENCE),A.QueryLexer.lexText;if(t.match(A.QueryLexer.termSeparator))return A.QueryLexer.lexTerm}else e.escapeCharacter()}},A.QueryParser=function(e,t){this.lexer=new A.QueryLexer(e),this.query=t,this.currentClause={},this.lexemeIdx=0},A.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=A.QueryParser.parseClause;e;)e=e(this);return this.query},A.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},A.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},A.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},A.QueryParser.parseClause=function(e){var t=e.peekLexeme();if(null!=t)switch(t.type){case A.QueryLexer.PRESENCE:return A.QueryParser.parsePresence;case A.QueryLexer.FIELD:return A.QueryParser.parseField;case A.QueryLexer.TERM:return A.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+t.type;throw t.str.length>=1&&(n+=" with value '"+t.str+"'"),new A.QueryParseError(n,t.start,t.end)}},A.QueryParser.parsePresence=function(e){var t=e.consumeLexeme();if(null!=t){switch(t.str){case"-":e.currentClause.presence=A.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=A.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+t.str+"'";throw new A.QueryParseError(n,t.start,t.end)}var r=e.peekLexeme();if(null==r){n="expecting term or field, found nothing";throw new A.QueryParseError(n,t.start,t.end)}switch(r.type){case A.QueryLexer.FIELD:return A.QueryParser.parseField;case A.QueryLexer.TERM:return A.QueryParser.parseTerm;default:n="expecting term or field, found '"+r.type+"'";throw new A.QueryParseError(n,r.start,r.end)}}},A.QueryParser.parseField=function(e){var t=e.consumeLexeme();if(null!=t){if(-1==e.query.allFields.indexOf(t.str)){var n=e.query.allFields.map((function(e){return"'"+e+"'"})).join(", "),r="unrecognised field '"+t.str+"', possible fields: "+n;throw new A.QueryParseError(r,t.start,t.end)}e.currentClause.fields=[t.str];var o=e.peekLexeme();if(null==o){r="expecting term, found nothing";throw new A.QueryParseError(r,t.start,t.end)}if(o.type===A.QueryLexer.TERM)return A.QueryParser.parseTerm;r="expecting term, found '"+o.type+"'";throw new A.QueryParseError(r,o.start,o.end)}},A.QueryParser.parseTerm=function(e){var t=e.consumeLexeme();if(null!=t){e.currentClause.term=t.str.toLowerCase(),-1!=t.str.indexOf("*")&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(null!=n)switch(n.type){case A.QueryLexer.TERM:return e.nextClause(),A.QueryParser.parseTerm;case A.QueryLexer.FIELD:return e.nextClause(),A.QueryParser.parseField;case A.QueryLexer.EDIT_DISTANCE:return A.QueryParser.parseEditDistance;case A.QueryLexer.BOOST:return A.QueryParser.parseBoost;case A.QueryLexer.PRESENCE:return e.nextClause(),A.QueryParser.parsePresence;default:var r="Unexpected lexeme type '"+n.type+"'";throw new A.QueryParseError(r,n.start,n.end)}else e.nextClause()}},A.QueryParser.parseEditDistance=function(e){var t=e.consumeLexeme();if(null!=t){var n=parseInt(t.str,10);if(isNaN(n)){var r="edit distance must be numeric";throw new A.QueryParseError(r,t.start,t.end)}e.currentClause.editDistance=n;var o=e.peekLexeme();if(null!=o)switch(o.type){case A.QueryLexer.TERM:return e.nextClause(),A.QueryParser.parseTerm;case A.QueryLexer.FIELD:return e.nextClause(),A.QueryParser.parseField;case A.QueryLexer.EDIT_DISTANCE:return A.QueryParser.parseEditDistance;case A.QueryLexer.BOOST:return A.QueryParser.parseBoost;case A.QueryLexer.PRESENCE:return e.nextClause(),A.QueryParser.parsePresence;default:r="Unexpected lexeme type '"+o.type+"'";throw new A.QueryParseError(r,o.start,o.end)}else e.nextClause()}},A.QueryParser.parseBoost=function(e){var t=e.consumeLexeme();if(null!=t){var n=parseInt(t.str,10);if(isNaN(n)){var r="boost must be numeric";throw new A.QueryParseError(r,t.start,t.end)}e.currentClause.boost=n;var o=e.peekLexeme();if(null!=o)switch(o.type){case A.QueryLexer.TERM:return e.nextClause(),A.QueryParser.parseTerm;case A.QueryLexer.FIELD:return e.nextClause(),A.QueryParser.parseField;case A.QueryLexer.EDIT_DISTANCE:return A.QueryParser.parseEditDistance;case A.QueryLexer.BOOST:return A.QueryParser.parseBoost;case A.QueryLexer.PRESENCE:return e.nextClause(),A.QueryParser.parsePresence;default:r="Unexpected lexeme type '"+o.type+"'";throw new A.QueryParseError(r,o.start,o.end)}else e.nextClause()}},void 0===(o="function"==typeof(r=function(){return A})?r.call(t,n,t,e):r)||(e.exports=o)}()},8934:(e,t,n)=>{"use strict";n.d(t,{_:()=>o,t:()=>a});var r=n(7294);const o=r.createContext(!1);function a(e){let{children:t}=e;const[n,a]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{a(!0)}),[]),r.createElement(o.Provider,{value:n},t)}},9383:(e,t,n)=>{"use strict";var r=n(7294),o=n(3935),a=n(3727),i=n(405),l=n(412);const s=[n(2497),n(3310),n(8320),n(2295)];var c=n(723),u=n(6775),d=n(8790);function p(e){let{children:t}=e;return r.createElement(r.Fragment,null,t)}var f=n(7462),m=n(5742),h=n(2263),g=n(4996),v=n(6668),b=n(1944),y=n(4711),w=n(9727),S=n(3320),k=n(197);function E(){const{i18n:{defaultLocale:e,localeConfigs:t}}=(0,h.Z)(),n=(0,y.l)();return r.createElement(m.Z,null,Object.entries(t).map((e=>{let[t,{htmlLang:o}]=e;return r.createElement("link",{key:t,rel:"alternate",href:n.createUrl({locale:t,fullyQualified:!0}),hrefLang:o})})),r.createElement("link",{rel:"alternate",href:n.createUrl({locale:e,fullyQualified:!0}),hrefLang:"x-default"}))}function _(e){let{permalink:t}=e;const{siteConfig:{url:n}}=(0,h.Z)(),o=function(){const{siteConfig:{url:e}}=(0,h.Z)(),{pathname:t}=(0,u.TH)();return e+(0,g.Z)(t)}(),a=t?""+n+t:o;return r.createElement(m.Z,null,r.createElement("meta",{property:"og:url",content:a}),r.createElement("link",{rel:"canonical",href:a}))}function x(){const{i18n:{currentLocale:e}}=(0,h.Z)(),{metadata:t,image:n}=(0,v.L)();return r.createElement(r.Fragment,null,r.createElement(m.Z,null,r.createElement("meta",{name:"twitter:card",content:"summary_large_image"}),r.createElement("body",{className:w.h})),n&&r.createElement(b.d,{image:n}),r.createElement(_,null),r.createElement(E,null),r.createElement(k.Z,{tag:S.HX,locale:e}),r.createElement(m.Z,null,t.map(((e,t)=>r.createElement("meta",(0,f.Z)({key:t},e))))))}const O=new Map;function T(e){if(O.has(e.pathname))return{...e,pathname:O.get(e.pathname)};if((0,d.f)(c.Z,e.pathname).some((e=>{let{route:t}=e;return!0===t.exact})))return O.set(e.pathname,e.pathname),e;const t=e.pathname.trim().replace(/(?:\/index)?\.html$/,"")||"/";return O.set(e.pathname,t),{...e,pathname:t}}var P=n(8934),C=n(8940);function I(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r{var r,o;const a=null!=(r=null==(o=t.default)?void 0:o[e])?r:t[e];return null==a?void 0:a(...n)}));return()=>o.forEach((e=>null==e?void 0:e()))}const A=function(e){let{children:t,location:n,previousLocation:o}=e;return(0,r.useLayoutEffect)((()=>{o!==n&&(o&&function(e){const{hash:t}=e;if(t){const e=decodeURIComponent(t.substring(1)),n=document.getElementById(e);null==n||n.scrollIntoView()}else window.scrollTo(0,0)}(n),I("onRouteDidUpdate",{previousLocation:o,location:n}))}),[o,n]),t};function R(e){const t=(0,d.f)(c.Z,e);return Promise.all(t.map((e=>null==e.route.component.preload?void 0:e.route.component.preload())))}class L extends r.Component{constructor(e){super(e),this.previousLocation=void 0,this.routeUpdateCleanupCb=void 0,this.previousLocation=null,this.routeUpdateCleanupCb=l.Z.canUseDOM?I("onRouteUpdate",{previousLocation:null,location:this.props.location}):()=>{},this.state={nextRouteHasLoaded:!0}}shouldComponentUpdate(e,t){if(e.location===this.props.location)return t.nextRouteHasLoaded;const n=e.location;return this.previousLocation=this.props.location,this.setState({nextRouteHasLoaded:!1}),this.routeUpdateCleanupCb=I("onRouteUpdate",{previousLocation:this.previousLocation,location:n}),R(n.pathname).then((()=>{this.routeUpdateCleanupCb(),this.setState({nextRouteHasLoaded:!0})})).catch((e=>{console.warn(e),window.location.reload()})),!1}render(){const{children:e,location:t}=this.props;return r.createElement(A,{previousLocation:this.previousLocation,location:t},r.createElement(u.AW,{location:t,render:()=>e}))}}const N=L,j="docusaurus-base-url-issue-banner-container",D="docusaurus-base-url-issue-banner-suggestion-container",F="__DOCUSAURUS_INSERT_BASEURL_BANNER";function M(e){return"\nwindow['"+F+"'] = true;\n\ndocument.addEventListener('DOMContentLoaded', maybeInsertBanner);\n\nfunction maybeInsertBanner() {\n var shouldInsert = window['"+F+"'];\n shouldInsert && insertBanner();\n}\n\nfunction insertBanner() {\n var bannerContainer = document.getElementById('"+j+"');\n if (!bannerContainer) {\n return;\n }\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(/{window[F]=!1}),[]),r.createElement(r.Fragment,null,!l.Z.canUseDOM&&r.createElement(m.Z,null,r.createElement("script",null,M(e))),r.createElement("div",{id:j}))}function U(){const{siteConfig:{baseUrl:e,baseUrlIssueBanner:t}}=(0,h.Z)(),{pathname:n}=(0,u.TH)();return t&&n===e?r.createElement(B,null):null}function z(){const{siteConfig:{favicon:e,title:t},i18n:{currentLocale:n,localeConfigs:o}}=(0,h.Z)(),a=(0,g.Z)(e),{htmlLang:i,direction:l}=o[n];return r.createElement(m.Z,null,r.createElement("html",{lang:i,dir:l}),r.createElement("title",null,t),r.createElement("meta",{property:"og:title",content:t}),e&&r.createElement("link",{rel:"icon",href:a}))}var V=n(4763);function H(){const e=(0,d.H)(c.Z),t=(0,u.TH)();return r.createElement(V.Z,null,r.createElement(C.M,null,r.createElement(P.t,null,r.createElement(p,null,r.createElement(z,null),r.createElement(x,null),r.createElement(U,null),r.createElement(N,{location:T(t)},e)))))}var q=n(6887);const $=function(e){try{return document.createElement("link").relList.supports(e)}catch{return!1}}("prefetch")?function(e){return new Promise(((t,n)=>{var r,o;if("undefined"==typeof document)return void n();const a=document.createElement("link");a.setAttribute("rel","prefetch"),a.setAttribute("href",e),a.onload=()=>t(),a.onerror=()=>n();const i=null!=(r=document.getElementsByTagName("head")[0])?r:null==(o=document.getElementsByName("script")[0])?void 0:o.parentNode;null==i||i.appendChild(a)}))}:function(e){return new Promise(((t,n)=>{const r=new XMLHttpRequest;r.open("GET",e,!0),r.withCredentials=!0,r.onload=()=>{200===r.status?t():n()},r.send(null)}))};var G=n(9670);const W=new Set,Q=new Set,Z=()=>{var e,t;return(null==(e=navigator.connection)?void 0:e.effectiveType.includes("2g"))||(null==(t=navigator.connection)?void 0:t.saveData)},Y={prefetch(e){if(!(e=>!Z()&&!Q.has(e)&&!W.has(e))(e))return!1;W.add(e);const t=(0,d.f)(c.Z,e).flatMap((e=>{return t=e.route.path,Object.entries(q).filter((e=>{let[n]=e;return n.replace(/-[^-]+$/,"")===t})).flatMap((e=>{let[,t]=e;return Object.values((0,G.Z)(t))}));var t}));return Promise.all(t.map((e=>{const t=n.gca(e);return t&&!t.includes("undefined")?$(t).catch((()=>{})):Promise.resolve()})))},preload:e=>!!(e=>!Z()&&!Q.has(e))(e)&&(Q.add(e),R(e))},K=Object.freeze(Y);if(l.Z.canUseDOM){window.docusaurus=K;const e=o.hydrate;R(window.location.pathname).then((()=>{e(r.createElement(i.B6,null,r.createElement(a.VK,null,r.createElement(H,null))),document.getElementById("__docusaurus"))}))}},8940:(e,t,n)=>{"use strict";n.d(t,{_:()=>u,M:()=>d});var r=n(7294),o=n(6809);const a=JSON.parse('{"@cmfcmf/docusaurus-search-local":{"default":{"titleBoost":5,"contentBoost":1,"tagsBoost":3,"parentCategoriesBoost":2,"indexDocSidebarParentCategories":0,"maxSearchResults":8}},"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":"47 seconds ago","title":"Accessiblity Font Sizes","publish_date":"2022-10-09","doc_name":"AccessibilityFontSizes.md"},{"author":"Robin Heinze","content":"defaults: &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 keys:\\n - v1-dependencies-node-{{ checksum \\"package.json\\" }}\\n # fallback to using the latest cache if no exact match is found\\n - v1-dependencies-node-\\n - run:\\n name: Install dependencies\\n command: yarn install\\n - save_cache:\\n name: Save node modules\\n paths:\\n - node_modules\\n key: v1-dependencies-node-{{ checksum \\"package.json\\" }}\\n\\n tests:\\n <<: *defaults\\n steps:\\n - checkout\\n - restore_cache:\\n keys:\\n - v1-dependencies-node-{{ checksum \\"package.json\\" }}\\n # fallback to using the latest cache if no exact match is found\\n - v1-dependencies-node-\\n - run:\\n name: Run tests\\n command: yarn ci:test # this command will be added to/found in your package.json scripts\\n\\nworkflows:\\n version: 2\\n test_and_release:\\n jobs:\\n - setup\\n - tests:\\n requires:\\n - setup\\n\\n {\\n ...\\n \\"scripts\\": {\\n ...\\n \\"ci:test\\": \\"\\" <<-- if you don\'t already have this one\\n },\\n ...\\n }\\n\\nbefore_all do\\n setup_circle_ci\\nend\\n\\nPROJECT = \\"YourProject\\"\\nXCODE_PROJECT = \\"#{PROJECT}.xcodeproj\\"\\n lane :beta do\\n increment_build_number(xcodeproj: \\"./#{XCODE_PROJECT}\\")\\n match(type: \\"appstore\\")\\n build_ios_app(\\n scheme: PROJECT,\\n workspace: \\"./YourProject.xcworkspace\\",\\n xcargs: \\"-UseNewBuildSystem=NO -allowProvisioningUpdates\\",\\n export_method: \\"app-store\\"\\n )\\n # Ship it!\\n upload_to_testflight(\\n skip_waiting_for_build_processing: true\\n )\\n commit_version_bump(\\n xcodeproj: \\"./#{XCODE_PROJECT}\\",\\n ignore: /tvOS/,\\n force: true,\\n message: \\"[skip ci] Version bump\\"\\n )\\n end\\n\\n {\\n ...\\n \\"scripts\\": {\\n ...\\n \\"ci:setup\\": \\"touch .env && echo \\\\\\"ENV_VAR=\\\\\\"$ENV_VAR >> .env\\",\\n },\\n ...\\n }\\n\\ndefaults: ...\\n\\nmac: &mac\\n macos:\\n xcode: \\"10.1.0\\"\\n working_directory: ~/repo\\n environment:\\n FL_OUTPUT_DIR: output\\n shell: /bin/bash --login -o pipefail\\n\\nversion: 2\\njobs:\\n setup: ...\\n\\n tests: ...\\n\\n deploy_ios:\\n <<: *mac\\n steps:\\n - checkout\\n - add_ssh_keys:\\n fingerprints: \u2014 \u201cSSH_FINGERPRINT_HERE\u201d\\n - run:\\n name: Git configuration\\n command: git config user.email \\"ci@your.domain\\" && git config user.name \\"CircleCI\\"\\n - run:\\n name: Set upstream branch\\n command: git branch --set-upstream-to origin ${CIRCLE_BRANCH}\\n # Node modules\\n - restore_cache:\\n name: Restore node modules\\n keys:\\n - v1-dependencies-mac-{{ checksum \\"package.json\\" }}\\n # fallback to using the latest cache if no exact match is found\\n - v1-dependencies-mac-\\n - run:\\n name: Install dependencies\\n command: NOYARNPOSTINSTALL=1 yarn install\\n - save_cache:\\n name: Save node modules\\n paths:\\n - node_modules\\n key: v1-dependencies-mac-{{ checksum \\"package.json\\" }}\\n\\n # Cocoapods\\n - run:\\n name: Fetch CocoaPods Specs\\n command: |\\n curl https://cocoapods-specs.circleci.com/fetch-cocoapods-repo-from-s3.sh | bash -s cf\\n - run:\\n working_directory: ios\\n name: Install CocoaPods\\n command: pod install --verbose\\n\\n # Gems\\n - restore_cache:\\n name: Restore gems\\n key: bundle-v1-{{ checksum \\"ios/Gemfile.lock\\" }}-{{ arch }}\\n - run:\\n name: Bundle Install\\n command: bundle install\\n working_directory: ios\\n - save_cache:\\n key: bundle-v1-{{ checksum \\"ios/Gemfile.lock\\" }}-{{ arch }}\\n paths:\\n - vendor/bundle\\n\\n # Misc setup\\n - run:\\n name: Misc setup\\n command: yarn ci:setup\\n\\n # Git grooming\\n - run:\\n name: Pull latest git\\n command: git stash && git pull && git stash pop\\n\\n # Run Fastlane\\n - run:\\n working_directory: ios\\n name: Fastlane\\n command: bundle exec fastlane ios beta\\n\\n # Git cleanup\\n - run:\\n name: Pull latest git\\n command: git stash && git pull && git stash pop\\n - run:\\n name: Push version bump commit\\n command: git push\\n - store_artifacts:\\n path: output\\n\\nworkflows:\\n version: 2\\n test_and_release:\\n jobs:\\n - setup\\n - tests:\\n requires:\\n - setup\\n - deploy_ios:\\n filters:\\n branches:\\n only: master\\n\\n\\n**Troubleshooting tips**\\n\\n- If you need to debug failed builds, you can use the \\"Rebuild with SSH\\" option in CircleCI. See [https://circleci.com/docs/2.0/ssh-access-jobs/](https://circleci.com/docs/2.0/ssh-access-jobs/) for more info.\\n- Tip: make sure you are logged in to Github/CircleCI as yourself (not the CI user) when you hit the button to rebuild with SSH.\\n- If you get a vague error saying `File main.jsbundle does not exist`, that means there was an error while building the app and you can view the more detailed message by inspecting the log files with the following command (while in SSH mode). Increase the number of lines from 50 as needed.\\n- `tail -50 ios/output/buildlogs/gym/YourProject-YourProject.log`\\n","lastUpdated":"47 seconds ago","title":"CircleCI CD Setup - React Native","publish_date":"2022-10-09","doc_name":"CircleCIRNSetup.md"},{"author":"Ellie Croce","content":"export const DEFAULT_API_CONFIG: ApiConfig = {\\n url: Config.API_URL,\\n timeout: 10000,\\n}\\n\\n\\nexport class Api {\\n apisauce: ApisauceInstance\\n config: ApiConfig\\n\\n constructor(config: ApiConfig = DEFAULT_API_CONFIG) {\\n this.config = config\\n this.apisauce = create({\\n baseURL: this.config.url,\\n timeout: this.config.timeout,\\n headers: {\\n Accept: \\"application/json\\",\\n },\\n })\\n }\\n\\n async getEpisodes(): Promise<{ kind: \\"ok\\"; episodes: EpisodeSnapshotIn[] } | GeneralApiProblem> {\\n ... // example API call that needs authenticating\\n }\\n\\n}\\n\\n// Singleton instance of the API for convenience\\nexport const api = new Api()\\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 .actions((store) => ({\\n setAuthToken(value?: string) {\\n store.authToken = value;\\n },\\n }));\\n\\nexport interface AuthenticationStore\\n extends Instance {}\\nexport interface AuthenticationStoreSnapshot\\n extends SnapshotOut {}\\n\\n\\t// We will need to import the api from the service\\n\\timport { api } from \\"app/services/api\\"\\n\\n ...\\n\\n // then update the Authentication Model actions\\n .actions((store) => ({\\n setAuthToken(value?: string) {\\n store.authToken = value;\\n },\\n distributeAuthToken(value?: string) {\\n // optionally grab the store\'s authToken if not passing a value\\n const token = value || store.authToken;\\n api.apiSauce.setHeader(\\"Authorization\\", `Bearer ${token}`);\\n },\\n })\\n\\n\\t// We can update `authenticationStore` from the useStores hook to include `distributeAuthToken`\\n\\tconst {\\n\\t authenticationStore: { authEmail, setAuthEmail, setAuthToken, distributeAuthToken, validationError },\\n\\t } = useStores()\\n\\n\\t ...\\n\\n function login() {\\n setIsSubmitted(true);\\n setAttemptsCount(attemptsCount + 1);\\n\\n if (validationError) return;\\n\\n // Make a request to your server to get an authentication token.\\n // If successful, reset the fields and set the token.\\n setIsSubmitted(false);\\n setAuthPassword(\\"\\");\\n setAuthEmail(\\"\\");\\n\\n // We\'ll mock this with a fake token.\\n const token = String(Date.now());\\n setAuthToken(token);\\n distributeAuthToken(token);\\n }\\n\\n","lastUpdated":"48 seconds ago","title":"Distributing Auth Token to APIs","publish_date":"2023-05-09","doc_name":"DistributingAuthTokenToAPI.md"},{"author":"Frank Calise","content":"npm install -g eas-cli\\n\\nnpx expo install expo-updates\\neas build:configure\\n\\neas update:configure\\n\\n{\\n \\"cli\\": {\\n \\"version\\": \\">= 3.0.0\\"\\n },\\n \\"build\\": {\\n \\"development\\": {\\n \\"developmentClient\\": true,\\n \\"distribution\\": \\"internal\\"\\n },\\n \\"preview\\": {\\n \\"channel\\": \\"preview\\",\\n \\"android\\": { \\"buildType\\": \\"apk\\" },\\n \\"ios\\": { \\"simulator\\": false }\\n },\\n \\"production\\": {}\\n },\\n \\"submit\\": {\\n \\"production\\": {}\\n }\\n}\\n\\n# via EAS\\neas build --profile preview -p android\\n\\n# locally\\neas build --profile preview -p android --local\\n\\n// error-line\\n\\n// success-line\\n\\n\\neas channel:create preview\\n\\neas update --branch preview --message \\"update login screen\\"\\n\\n{\\n // ...\\n \\"updates\\": {\\n \\"checkAutomatically\\": \\"ON_ERROR_RECOVERY\\"\\n // ...\\n }\\n}\\n\\n","lastUpdated":"48 seconds ago","title":"EAS Update","publish_date":"2023-01-06","doc_name":"EASUpdate.md"},{"author":"Frank Calise","content":"yarn add -D dotenv babel-plugin-inline-dotenv\\n\\n// babel.config.js\\n\\nconst plugins = [\\n [\\n \\"@babel/plugin-proposal-decorators\\",\\n {\\n legacy: true,\\n },\\n ],\\n [\\"@babel/plugin-proposal-optional-catch-binding\\"],\\n \\"inline-dotenv\\",\\n \\"react-native-reanimated/plugin\\", // NOTE: this must be last in the plugins\\n];\\n\\nconst expoConfig = {\\n presets: [\\"babel-preset-expo\\"],\\n env: {\\n production: {},\\n },\\n plugins,\\n};\\n\\nMY_VAR=\\"MY_VALUE\\"\\nKEEP_IN_MIND=\\"THESE ARE NOT SECURE\\"\\n\\nconsole.log(process.env.MY_VAR); // results in: MY_VALUE\\nconsole.log(process.env.KEEP_IN_MIND); // results in: THESE ARE NOT SECURE\\n\\n","lastUpdated":"48 seconds ago","title":"Environment Variables","publish_date":"2022-10-11","doc_name":"EnvironmentVariables.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":"48 seconds ago","title":"Generator for Component Tests","publish_date":"2022-10-10","doc_name":"GeneratorComponentTests.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":"48 seconds ago","title":"Maestro Setup","publish_date":"2023-02-01","doc_name":"MaestroSetup.md"},{"author":"Frank Calise","content":"npx ignite-cli new PizzaApp --yes\\ncd PizzaApp\\n\\nnpx expo install @shopify/flash-list\\n\\nimport { FlashList } from \\"@shopify/flash-list\\";\\n\\nreturn (\\n \\n // highlight-next-line\\n \\n data={episodeStore.episodesForList}\\n extraData={episodeStore.favorites.length + episodeStore.episodes.length}\\n contentContainerStyle={$flatListContentContainer}\\n refreshing={refreshing}\\n onRefresh={manualRefresh}\\n // ...\\n />\\n // ...\\n \\n);\\n\\n WARN estimatedItemSize FlashList prop is not defined - based on current configuration you can set\\n it to 187 to optimize list performance. Refer to FlashList documentation for more details.\\n\\n\\n data={episodeStore.episodesForList}\\n // highlight-next-line\\n estimatedItemSize={187}\\n // ...\\n/>\\n\\n","lastUpdated":"48 seconds ago","title":"Migrating to FlashList","publish_date":"2022-10-13","doc_name":"MigratingToFlashList.md"},{"author":"Frank Calise","content":"npx ignite-cli new PizzaApp --yes\\ncd PizzaApp\\n\\nyarn add react-native-mmkv\\n\\n// error-line\\nimport AsyncStorage from \\"@react-native-async-storage/async-storage\\";\\nimport { MMKV } from \\"react-native-mmkv\\";\\nconst storage = new MMKV();\\n\\n/**\\n * Loads a string from storage.\\n *\\n * @param key The key to fetch.\\n */\\n// error-line\\nexport async function loadString(key: string): Promise {\\nexport function loadString(key: string): string | null {\\n try {\\n // error-line\\n return await AsyncStorage.getItem(key)\\n return storage.getString(key);\\n } catch {\\n // not sure why this would fail... even reading the RN docs I\'m unclear\\n return null;\\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 */\\n// error-line\\nexport async function saveString(key: string, value: string): Promise {\\nexport function saveString(key: string, value: string): boolean {\\n try {\\n // error-line\\n await AsyncStorage.setItem(key, value)\\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 */\\n// error-line\\nexport async function load(key: string): Promise {\\nexport function load(key: string): any | null {\\n try {\\n // error-line\\n const almostThere = await AsyncStorage.getItem(key)\\n const almostThere = storage.getString(key);\\n return JSON.parse(almostThere);\\n } catch {\\n return null;\\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 */\\n// error-line\\nexport async function save(key: string, value: any): Promise {\\nexport function save(key: string, value: any): boolean {\\n try {\\n // error-line\\n await AsyncStorage.setItem(key, JSON.stringify(value))\\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 */\\n// error-line\\nexport async function remove(key: string): Promise {\\nexport function remove(key: string): void {\\n try {\\n // error-line\\n await AsyncStorage.removeItem(key)\\n storage.delete(key);\\n } catch {}\\n}\\n\\n/**\\n * Burn it all to the ground.\\n */\\n// error-line\\nexport async function clear(): Promise {\\nexport function clear(): void {\\n try {\\n // error-line\\n await AsyncStorage.clear()\\n storage.clearAll();\\n } catch {}\\n}\\n\\n","lastUpdated":"48 seconds ago","title":"Migrating to MMKV","publish_date":"2022-10-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":"48 seconds ago","title":"Patching/Building Android .aar From Source","publish_date":"2022-10-09","doc_name":"PatchingBuildingAndroid.md"},{"author":"Frank Calise","content":"npx ignite-cli new PizzaApp --yes\\ncd PizzaApp\\n\\nrm -rf android\\nrm -rf ios\\nrm index.js # Expo\'s entry point is App.js\\nrm metro.config.js # Expo will use the default\\n\\nyarn remove react-native-bootsplash\\n\\nyarn remove expo-modules-core\\n\\n{\\n \\"name\\": \\"ignite-eas\\",\\n \\"version\\": \\"0.0.1\\",\\n \\"private\\": true,\\n \\"main\\": \\"node_modules/expo/AppEntry.js\\",\\n \\"scripts\\": {\\n \\"compile\\": \\"tsc --noEmit -p . --pretty\\",\\n \\"format\\": \\"prettier --write \\\\\\"app/**/*.{js,jsx,json,md,ts,tsx}\\\\\\"\\",\\n \\"lint\\": \\"eslint App.js app test --fix --ext .js,.ts,.tsx && npm run format\\",\\n \\"patch\\": \\"patch-package\\",\\n \\"test\\": \\"jest\\",\\n \\"test:watch\\": \\"jest --watch\\",\\n \\"adb\\": \\"adb reverse tcp:9090 tcp:9090 && adb reverse tcp:3000 tcp:3000 && adb reverse tcp:9001 tcp:9001 && adb reverse tcp:8081 tcp:8081\\",\\n \\"postinstall\\": \\"node ./bin/postInstall\\",\\n \\"clean\\": \\"npx react-native-clean-project\\",\\n \\"clean-all\\": \\"npx react-native clean-project-auto\\",\\n \\"start\\": \\"expo start\\",\\n \\"android\\": \\"expo start --android\\",\\n \\"ios\\": \\"expo start --ios\\",\\n \\"web\\": \\"expo start --web\\",\\n \\"build:detox\\": \\"detox build -c ios.sim.expo\\",\\n \\"test:detox\\": \\"./bin/downloadExpoApp.sh && detox test --configuration ios.sim.expo\\"\\n },\\n // ... more config ...\\n \\"detox\\": {\\n \\"test-runner\\": \\"jest\\",\\n \\"runnerConfig\\": \\"./detox/config.json\\",\\n \\"specs\\": \\"detox\\",\\n \\"configurations\\": {\\n \\"ios.sim.expo\\": {\\n \\"binaryPath\\": \\"bin/Exponent.app\\",\\n \\"type\\": \\"ios.simulator\\",\\n \\"name\\": \\"iPhone 14\\"\\n }\\n }\\n }\\n // ... more config ...\\n}\\n\\n","lastUpdated":"48 seconds ago","title":"Pristine Expo Project","publish_date":"2022-10-11","doc_name":"PristineExpoProject.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":"48 seconds 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<\\n TextFieldProps,\\n \\"ref\\" | \\"onValueChange\\" | \\"onChange\\" | \\"value\\"\\n > {}\\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 =\\n 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 )}\\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 =\\n 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 \\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\\";\\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 disabled =\\n 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 \\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: ViewStyle = {\\n paddingHorizontal: spacing.large,\\n};\\n\\nconst $listItem: ViewStyle = {\\n paddingHorizontal: spacing.large,\\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\\";\\n// success-line\\nimport { colors, spacing } from \\"../theme\\";\\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\\n const disabled =\\n 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 \\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: ViewStyle = {\\n paddingHorizontal: spacing.large,\\n};\\n\\nconst $listItem: ViewStyle = {\\n paddingHorizontal: spacing.large,\\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":"48 seconds ago","title":"SelectField using `react-native-bottom-sheet`","publish_date":"2023-02-15","doc_name":"SelectFieldWithBottomSheet.mdx"},{"author":"Frank Calise","content":"npm install -g eas-cli\\n\\nbrew install cocoapods fastlane\\n\\nyarn add expo-dev-client\\n\\neas init\\n\u2714 Linked to project @infinitered/cookbook\\n\u2714 Linked app.json to project with ID 012aaaa3-4ce5-4bae-9f4d-2f842489f07a\\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":"48 seconds ago","title":"Staying With Expo","publish_date":"2022-10-11","doc_name":"StayingWithExpo.md"},{"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":"48 seconds 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":"48 seconds 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":"48 seconds 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":"48 seconds ago","title":"Using Screen Readers","publish_date":"2022-10-09","doc_name":"UsingScreenReaders.md"}]}},"docusaurus-plugin-content-docs":{"default":{"path":"/docs","versions":[{"name":"current","label":"Next","isLast":true,"path":"/docs","mainDocId":"intro","docs":[{"id":"intro","path":"/docs/intro","sidebar":"mainSidebar"},{"id":"recipes/AccessibilityFontSizes","path":"/docs/recipes/AccessibilityFontSizes","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/EnvironmentVariables","path":"/docs/recipes/EnvironmentVariables","sidebar":"mainSidebar"},{"id":"recipes/GeneratorComponentTests","path":"/docs/recipes/GeneratorComponentTests","sidebar":"mainSidebar"},{"id":"recipes/MaestroSetup","path":"/docs/recipes/MaestroSetup","sidebar":"mainSidebar"},{"id":"recipes/MigratingToFlashList","path":"/docs/recipes/MigratingToFlashList","sidebar":"mainSidebar"},{"id":"recipes/MigratingToMMKV","path":"/docs/recipes/MigratingToMMKV","sidebar":"mainSidebar"},{"id":"recipes/PatchingBuildingAndroid","path":"/docs/recipes/PatchingBuildingAndroid","sidebar":"mainSidebar"},{"id":"recipes/PristineExpoProject","path":"/docs/recipes/PristineExpoProject","sidebar":"mainSidebar"},{"id":"recipes/SampleYAMLCircleCI","path":"/docs/recipes/SampleYAMLCircleCI","sidebar":"mainSidebar"},{"id":"recipes/SelectFieldWithBottomSheet","path":"/docs/recipes/SelectFieldWithBottomSheet","sidebar":"mainSidebar"},{"id":"recipes/StayingWithExpo","path":"/docs/recipes/StayingWithExpo","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/UsingScreenReaders","path":"/docs/recipes/UsingScreenReaders","sidebar":"mainSidebar"}],"draftIds":[],"sidebars":{"mainSidebar":{"link":{"path":"/docs/intro","label":"intro"}}}}],"breadcrumbs":true}}}'),i=JSON.parse('{"defaultLocale":"en","locales":["en"],"path":"i18n","currentLocale":"en","localeConfigs":{"en":{"label":"English","direction":"ltr","htmlLang":"en","calendar":"gregory","path":"en"}}}');var l=n(7529);const s=JSON.parse('{"docusaurusVersion":"2.0.1","siteVersion":"0.0.0","pluginVersions":{"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"2.0.1"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"2.0.1"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"2.0.1"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"2.0.1"},"example-code-snippets":{"type":"local"},"@cmfcmf/docusaurus-search-local":{"type":"package","name":"@cmfcmf/docusaurus-search-local","version":"0.11.0"}}}'),c={siteConfig:o.default,siteMetadata:s,globalData:a,i18n:i,codeTranslations:l},u=r.createContext(c);function d(e){let{children:t}=e;return r.createElement(u.Provider,{value:c},t)}},4763:(e,t,n)=>{"use strict";n.d(t,{Z:()=>u});var r=n(7294),o=n(412),a=n(5742),i=n(5911);function l(e){let{error:t,tryAgain:n}=e;return r.createElement("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"center",height:"50vh",width:"100%",fontSize:"20px"}},r.createElement("h1",null,"This page crashed."),r.createElement("p",null,t.message),r.createElement("button",{type:"button",onClick:n},"Try again"))}function s(e){let{error:t,tryAgain:n}=e;return r.createElement(u,{fallback:()=>r.createElement(l,{error:t,tryAgain:n})},r.createElement(a.Z,null,r.createElement("title",null,"Page Error")),r.createElement(i.Z,null,r.createElement(l,{error:t,tryAgain:n})))}const c=e=>r.createElement(s,e);class u extends r.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){o.Z.canUseDOM&&this.setState({error:e})}render(){const{children:e}=this.props,{error:t}=this.state;if(t){var n;const e={error:t,tryAgain:()=>this.setState({error:null})};return(null!=(n=this.props.fallback)?n:c)(e)}return null!=e?e:null}}},412:(e,t,n)=>{"use strict";n.d(t,{Z:()=>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}},5742:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(7294),o=n(405);function a(e){return r.createElement(o.ql,e)}},9960:(e,t,n)=>{"use strict";n.d(t,{Z:()=>f});var r=n(7462),o=n(7294),a=n(3727),i=n(8780),l=n(2263),s=n(3919),c=n(412);const u=o.createContext({collectLink:()=>{}});var d=n(4996);function p(e,t){var n,p;let{isNavLink:f,to:m,href:h,activeClassName:g,isActive:v,"data-noBrokenLinkCheck":b,autoAddBaseUrl:y=!0,...w}=e;const{siteConfig:{trailingSlash:S,baseUrl:k}}=(0,l.Z)(),{withBaseUrl:E}=(0,d.C)(),_=(0,o.useContext)(u),x=(0,o.useRef)(null);(0,o.useImperativeHandle)(t,(()=>x.current));const O=m||h;const T=(0,s.Z)(O),P=null==O?void 0:O.replace("pathname://","");let C=void 0!==P?(I=P,y&&(e=>e.startsWith("/"))(I)?E(I):I):void 0;var I;C&&T&&(C=(0,i.applyTrailingSlash)(C,{trailingSlash:S,baseUrl:k}));const A=(0,o.useRef)(!1),R=f?a.OL:a.rU,L=c.Z.canUseIntersectionObserver,N=(0,o.useRef)();(0,o.useEffect)((()=>(!L&&T&&null!=C&&window.docusaurus.prefetch(C),()=>{L&&N.current&&N.current.disconnect()})),[N,C,L,T]);const j=null!=(n=null==(p=C)?void 0:p.startsWith("#"))&&n,D=!C||!T||j;return D||b||_.collectLink(C),D?o.createElement("a",(0,r.Z)({ref:x,href:C},O&&!T&&{target:"_blank",rel:"noopener noreferrer"},w)):o.createElement(R,(0,r.Z)({},w,{onMouseEnter:()=>{A.current||null==C||(window.docusaurus.preload(C),A.current=!0)},innerRef:e=>{x.current=e,L&&e&&T&&(N.current=new window.IntersectionObserver((t=>{t.forEach((t=>{e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(N.current.unobserve(e),N.current.disconnect(),null!=C&&window.docusaurus.prefetch(C))}))})),N.current.observe(e))},to:C},f&&{isActive:v,activeClassName:g}))}const f=o.forwardRef(p)},5999:(e,t,n)=>{"use strict";n.d(t,{Z:()=>s,I:()=>l});var r=n(7294);function o(e,t){const n=e.split(/(\{\w+\})/).map(((e,n)=>{if(n%2==1){const n=null==t?void 0:t[e.slice(1,-1)];if(void 0!==n)return n}return e}));return n.some((e=>(0,r.isValidElement)(e)))?n.map(((e,t)=>(0,r.isValidElement)(e)?r.cloneElement(e,{key:t}):e)).filter((e=>""!==e)):n.join("")}var a=n(7529);function i(e){var t,n;let{id:r,message:o}=e;if(void 0===r&&void 0===o)throw new Error("Docusaurus translation declarations must have at least a translation id or a default translation message");return null!=(t=null!=(n=a[null!=r?r:o])?n:o)?t:r}function l(e,t){let{message:n,id:r}=e;return o(i({message:n,id:r}),t)}function s(e){let{children:t,id:n,values:a}=e;if(t&&"string"!=typeof t)throw console.warn("Illegal children",t),new Error("The Docusaurus component only accept simple string values");const l=i({message:t,id:n});return r.createElement(r.Fragment,null,o(l,a))}},9935:(e,t,n)=>{"use strict";n.d(t,{m:()=>r});const r="default"},3919:(e,t,n)=>{"use strict";function r(e){return/^(?:\w*:|\/\/)/.test(e)}function o(e){return void 0!==e&&!r(e)}n.d(t,{Z:()=>o,b:()=>r})},4996:(e,t,n)=>{"use strict";n.d(t,{C:()=>a,Z:()=>i});var r=n(2263),o=n(3919);function a(){const{siteConfig:{baseUrl:e,url:t}}=(0,r.Z)();return{withBaseUrl:(n,r)=>function(e,t,n,r){let{forcePrependBaseUrl:a=!1,absolute:i=!1}=void 0===r?{}:r;if(!n||n.startsWith("#")||(0,o.b)(n))return n;if(a)return t+n.replace(/^\//,"");if(n===t.replace(/\/$/,""))return t;const l=n.startsWith(t)?n:t+n.replace(/^\//,"");return i?e+l:l}(t,e,n,r)}}function i(e,t){void 0===t&&(t={});const{withBaseUrl:n}=a();return n(e,t)}},2263:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(7294),o=n(8940);function a(){return(0,r.useContext)(o._)}},8084:(e,t,n)=>{"use strict";n.d(t,{OD:()=>a,eZ:()=>i});var r=n(2263),o=n(9935);function a(e,t){void 0===t&&(t={});const n=function(){const{globalData:e}=(0,r.Z)();return e}()[e];if(!n&&t.failfast)throw new Error('Docusaurus plugin global data not found for "'+e+'" plugin.');return n}function i(e,t,n){void 0===t&&(t=o.m),void 0===n&&(n={});const r=a(e),i=null==r?void 0:r[t];if(!i&&n.failfast)throw new Error('Docusaurus plugin global data not found for "'+e+'" plugin with id "'+t+'".');return i}},2389:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(7294),o=n(8934);function a(){return(0,r.useContext)(o._)}},9670:(e,t,n)=>{"use strict";n.d(t,{Z:()=>r});function r(e){const t={};return function e(n,r){Object.entries(n).forEach((n=>{let[o,a]=n;const i=r?r+"."+o:o;var l;"object"==typeof(l=a)&&l&&Object.keys(l).length>0?e(a,i):t[i]=a}))}(e),t}},226:(e,t,n)=>{"use strict";n.d(t,{_:()=>o,z:()=>a});var r=n(7294);const o=r.createContext(null);function a(e){let{children:t,value:n}=e;const a=r.useContext(o),i=(0,r.useMemo)((()=>function(e){let{parent:t,value:n}=e;if(!t){if(!n)throw new Error("Unexpected: no Docusaurus route context found");if(!("plugin"in n))throw new Error("Unexpected: Docusaurus topmost route context has no `plugin` attribute");return n}const r={...t.data,...null==n?void 0:n.data};return{plugin:t.plugin,data:r}}({parent:a,value:n})),[a,n]);return r.createElement(o.Provider,{value:i},t)}},4104:(e,t,n)=>{"use strict";n.d(t,{Iw:()=>h,gA:()=>d,WS:()=>p,_r:()=>c,Jo:()=>g,zh:()=>u,yW:()=>m,gB:()=>f});var r=n(6775),o=n(8084);const a=e=>e.versions.find((e=>e.isLast));function i(e,t){const n=a(e);return[...e.versions.filter((e=>e!==n)),n].find((e=>!!(0,r.LX)(t,{path:e.path,exact:!1,strict:!1})))}function l(e,t){const n=i(e,t),o=null==n?void 0:n.docs.find((e=>!!(0,r.LX)(t,{path:e.path,exact:!0,strict:!1})));return{activeVersion:n,activeDoc:o,alternateDocVersions:o?function(t){const n={};return e.versions.forEach((e=>{e.docs.forEach((r=>{r.id===t&&(n[e.name]=r)}))})),n}(o.id):{}}}const s={},c=()=>{var e;return null!=(e=(0,o.OD)("docusaurus-plugin-content-docs"))?e:s},u=e=>(0,o.eZ)("docusaurus-plugin-content-docs",e,{failfast:!0});function d(e){void 0===e&&(e={});const t=c(),{pathname:n}=(0,r.TH)();return function(e,t,n){void 0===n&&(n={});const o=Object.entries(e).sort(((e,t)=>t[1].path.localeCompare(e[1].path))).find((e=>{let[,n]=e;return!!(0,r.LX)(t,{path:n.path,exact:!1,strict:!1})})),a=o?{pluginId:o[0],pluginData:o[1]}:void 0;if(!a&&n.failfast)throw new Error("Can't find active docs plugin for \""+t+'" 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}(t,n,e)}function p(e){void 0===e&&(e={});const t=d(e),{pathname:n}=(0,r.TH)();if(!t)return;return{activePlugin:t,activeVersion:i(t.pluginData,n)}}function f(e){return u(e).versions}function m(e){const t=u(e);return a(t)}function h(e){const t=u(e),{pathname:n}=(0,r.TH)();return l(t,n)}function g(e){const t=u(e),{pathname:n}=(0,r.TH)();return function(e,t){const n=a(e);return{latestDocSuggestion:l(e,t).alternateDocVersions[n.name],latestVersionSuggestion:n}}(t,n)}},8320:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>a});var r=n(4865),o=n.n(r);o().configure({showSpinner:!1});const a={onRouteUpdate(e){let{location:t,previousLocation:n}=e;if(n&&t.pathname!==n.pathname){const e=window.setTimeout((()=>{o().start()}),200);return()=>window.clearTimeout(e)}},onRouteDidUpdate(){o().done()}}},3310:(e,t,n)=>{"use strict";n.r(t);var r=n(7410),o=n(6809);!function(e){const{themeConfig:{prism:t}}=o.default,{additionalLanguages:r}=t;globalThis.Prism=e,r.forEach((e=>{n(6726)("./prism-"+e)})),delete globalThis.Prism}(r.Z)},9471:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(7294);const o="iconExternalLink_nPIU";function a(e){let{width:t=13.5,height:n=13.5}=e;return r.createElement("svg",{width:t,height:n,"aria-hidden":"true",viewBox:"0 0 24 24",className:o},r.createElement("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"}))}},5911:(e,t,n)=>{"use strict";n.d(t,{Z:()=>ea});var r=n(7294),o=n(6010),a=n(4763),i=n(1944),l=n(5281),s=n(9727),c=n(5999),u=n(6775),d=n(5936);function p(e){e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")}const f="skipToContent_fXgn";function m(){const{containerRef:e,handleSkip:t}=function(){const e=(0,r.useRef)(null),{action:t}=(0,u.k6)(),n=(0,r.useCallback)((e=>{var t;e.preventDefault();const n=null!=(t=document.querySelector("main:first-of-type"))?t:document.querySelector("."+l.k.wrapper.main);n&&p(n)}),[]);return(0,d.S)((n=>{let{location:r}=n;e.current&&!r.hash&&"PUSH"===t&&p(e.current)})),{containerRef:e,handleSkip:n}}();return r.createElement("div",{ref:e,role:"region"},r.createElement("a",{href:"#",className:f,onClick:t},r.createElement(c.Z,{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"},"Skip to main content")))}var h=n(6668),g=n(9689),v=n(7462);function b(e){let{width:t=21,height:n=21,color:o="currentColor",strokeWidth:a=1.2,className:i,...l}=e;return r.createElement("svg",(0,v.Z)({viewBox:"0 0 15 15",width:t,height:n},l),r.createElement("g",{stroke:o,strokeWidth:a},r.createElement("path",{d:"M.75.75l13.5 13.5M14.25.75L.75 14.25"})))}const y="announcementBar_mb4j",w="announcementBarPlaceholder_vyr4",S="announcementBarClose_gvF7",k="announcementBarContent_xLdY";function E(){const{isActive:e,close:t}=(0,g.nT)(),{announcementBar:n}=(0,h.L)();if(!e)return null;const{content:a,backgroundColor:i,textColor:l,isCloseable:s}=n;return r.createElement("div",{className:y,style:{backgroundColor:i,color:l},role:"banner"},s&&r.createElement("div",{className:w}),r.createElement("div",{className:k,dangerouslySetInnerHTML:{__html:a}}),s?r.createElement("button",{type:"button",className:(0,o.Z)("clean-btn close",S),onClick:t,"aria-label":(0,c.I)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"})},r.createElement(b,{width:14,height:14,strokeWidth:3.1})):null)}var _=n(2961),x=n(2466);var O=n(902),T=n(3102);const P=r.createContext(null);function C(e){let{children:t}=e;const n=function(){const e=(0,_.e)(),t=(0,T.HY)(),[n,o]=(0,r.useState)(!1),a=null!==t.component,i=(0,O.D9)(a);return(0,r.useEffect)((()=>{a&&!i&&o(!0)}),[a,i]),(0,r.useEffect)((()=>{a?e.shown||o(!0):o(!1)}),[e.shown,a]),(0,r.useMemo)((()=>[n,o]),[n])}();return r.createElement(P.Provider,{value:n},t)}function I(e){if(e.component){const t=e.component;return r.createElement(t,e.props)}}function A(){const e=(0,r.useContext)(P);if(!e)throw new O.i6("NavbarSecondaryMenuDisplayProvider");const[t,n]=e,o=(0,r.useCallback)((()=>n(!1)),[n]),a=(0,T.HY)();return(0,r.useMemo)((()=>({shown:t,hide:o,content:I(a)})),[o,a,t])}function R(e){let{header:t,primaryMenu:n,secondaryMenu:a}=e;const{shown:i}=A();return r.createElement("div",{className:"navbar-sidebar"},t,r.createElement("div",{className:(0,o.Z)("navbar-sidebar__items",{"navbar-sidebar__items--show-secondary":i})},r.createElement("div",{className:"navbar-sidebar__item menu"},n),r.createElement("div",{className:"navbar-sidebar__item menu"},a)))}var L=n(2949),N=n(2389);function j(e){return r.createElement("svg",(0,v.Z)({viewBox:"0 0 24 24",width:24,height:24},e),r.createElement("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 D(e){return r.createElement("svg",(0,v.Z)({viewBox:"0 0 24 24",width:24,height:24},e),r.createElement("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 F={toggle:"toggle_vylO",toggleButton:"toggleButton_gllP",darkToggleIcon:"darkToggleIcon_wfgR",lightToggleIcon:"lightToggleIcon_pyhR",toggleButtonDisabled:"toggleButtonDisabled_aARS"};function M(e){let{className:t,value:n,onChange:a}=e;const i=(0,N.Z)(),l=(0,c.I)({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"===n?(0,c.I)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"}):(0,c.I)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"})});return r.createElement("div",{className:(0,o.Z)(F.toggle,t)},r.createElement("button",{className:(0,o.Z)("clean-btn",F.toggleButton,!i&&F.toggleButtonDisabled),type:"button",onClick:()=>a("dark"===n?"light":"dark"),disabled:!i,title:l,"aria-label":l},r.createElement(j,{className:(0,o.Z)(F.toggleIcon,F.lightToggleIcon)}),r.createElement(D,{className:(0,o.Z)(F.toggleIcon,F.darkToggleIcon)})))}const B=r.memo(M);function U(e){let{className:t}=e;const n=(0,h.L)().colorMode.disableSwitch,{colorMode:o,setColorMode:a}=(0,L.I)();return n?null:r.createElement(B,{className:t,value:o,onChange:a})}var z=n(1327);function V(){return r.createElement(z.Z,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function H(){const e=(0,_.e)();return r.createElement("button",{type:"button",className:"clean-btn navbar-sidebar__close",onClick:()=>e.toggle()},r.createElement(b,{color:"var(--ifm-color-emphasis-600)"}))}function q(){return r.createElement("div",{className:"navbar-sidebar__brand"},r.createElement(V,null),r.createElement(U,{className:"margin-right--md"}),r.createElement(H,null))}var $=n(9960),G=n(4996),W=n(3919);function Q(e,t){return void 0!==e&&void 0!==t&&new RegExp(e,"gi").test(t)}var Z=n(9471);function Y(e){let{activeBasePath:t,activeBaseRegex:n,to:o,href:a,label:i,html:l,isDropdownLink:s,prependBaseUrlToHref:c,...u}=e;const d=(0,G.Z)(o),p=(0,G.Z)(t),f=(0,G.Z)(a,{forcePrependBaseUrl:!0}),m=i&&a&&!(0,W.Z)(a),h=l?{dangerouslySetInnerHTML:{__html:l}}:{children:r.createElement(r.Fragment,null,i,m&&r.createElement(Z.Z,s&&{width:12,height:12}))};return a?r.createElement($.Z,(0,v.Z)({href:c?f:a},u,h)):r.createElement($.Z,(0,v.Z)({to:d,isNavLink:!0},(t||n)&&{isActive:(e,t)=>n?Q(n,t.pathname):t.pathname.startsWith(p)},u,h))}function K(e){let{className:t,isDropdownItem:n=!1,...a}=e;const i=r.createElement(Y,(0,v.Z)({className:(0,o.Z)(n?"dropdown__link":"navbar__item navbar__link",t),isDropdownLink:n},a));return n?r.createElement("li",null,i):i}function X(e){let{className:t,isDropdownItem:n,...a}=e;return r.createElement("li",{className:"menu__list-item"},r.createElement(Y,(0,v.Z)({className:(0,o.Z)("menu__link",t)},a)))}function J(e){var t;let{mobile:n=!1,position:o,...a}=e;const i=n?X:K;return r.createElement(i,(0,v.Z)({},a,{activeClassName:null!=(t=a.activeClassName)?t:n?"menu__link--active":"navbar__link--active"}))}var ee=n(6043),te=n(8596),ne=n(2263);function re(e,t){return e.some((e=>function(e,t){return!!(0,te.Mg)(e.to,t)||!!Q(e.activeBaseRegex,t)||!(!e.activeBasePath||!t.startsWith(e.activeBasePath))}(e,t)))}function oe(e){var t;let{items:n,position:a,className:i,onClick:l,...s}=e;const c=(0,r.useRef)(null),[u,d]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{const e=e=>{c.current&&!c.current.contains(e.target)&&d(!1)};return document.addEventListener("mousedown",e),document.addEventListener("touchstart",e),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e)}}),[c]),r.createElement("div",{ref:c,className:(0,o.Z)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===a,"dropdown--show":u})},r.createElement(Y,(0,v.Z)({"aria-haspopup":"true","aria-expanded":u,role:"button",href:s.to?void 0:"#",className:(0,o.Z)("navbar__link",i)},s,{onClick:s.to?void 0:e=>e.preventDefault(),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),d(!u))}}),null!=(t=s.children)?t:s.label),r.createElement("ul",{className:"dropdown__menu"},n.map(((e,t)=>r.createElement(ho,(0,v.Z)({isDropdownItem:!0,onKeyDown:e=>{if(t===n.length-1&&"Tab"===e.key){e.preventDefault(),d(!1);const t=c.current.nextElementSibling;if(t){(t instanceof HTMLAnchorElement?t:t.querySelector("a")).focus()}}},activeClassName:"dropdown__link--active"},e,{key:t}))))))}function ae(e){var t;let{items:n,className:a,position:i,onClick:l,...s}=e;const c=function(){const{siteConfig:{baseUrl:e}}=(0,ne.Z)(),{pathname:t}=(0,u.TH)();return t.replace(e,"/")}(),d=re(n,c),{collapsed:p,toggleCollapsed:f,setCollapsed:m}=(0,ee.u)({initialState:()=>!d});return(0,r.useEffect)((()=>{d&&m(!d)}),[c,d,m]),r.createElement("li",{className:(0,o.Z)("menu__list-item",{"menu__list-item--collapsed":p})},r.createElement(Y,(0,v.Z)({role:"button",className:(0,o.Z)("menu__link menu__link--sublist menu__link--sublist-caret",a)},s,{onClick:e=>{e.preventDefault(),f()}}),null!=(t=s.children)?t:s.label),r.createElement(ee.z,{lazy:!0,as:"ul",className:"menu__list",collapsed:p},n.map(((e,t)=>r.createElement(ho,(0,v.Z)({mobile:!0,isDropdownItem:!0,onClick:l,activeClassName:"menu__link--active"},e,{key:t}))))))}function ie(e){let{mobile:t=!1,...n}=e;const o=t?ae:oe;return r.createElement(o,n)}var le=n(4711);function se(e){let{width:t=20,height:n=20,...o}=e;return r.createElement("svg",(0,v.Z)({viewBox:"0 0 24 24",width:t,height:n,"aria-hidden":!0},o),r.createElement("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 ce="iconLanguage_nlXk";var ue=n(3935);function de(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function pe(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function fe(e,t,n){var r,o=t.initialState;return{getState:function(){return o},dispatch:function(r,a){var i=function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var tt,nt,rt,ot=null,at=(tt=-1,nt=-1,rt=void 0,function(e){var t=++tt;return Promise.resolve(e).then((function(e){return rt&&t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var pt=/((gt|sm)-|galaxy nexus)|samsung[- ]/i;var ft=["props","refresh","store"],mt=["inputElement","formElement","panelElement"],ht=["inputElement"],gt=["inputElement","maxLength"],vt=["item","source"];function bt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function yt(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function kt(e){var t=e.props,n=e.refresh,r=e.store,o=St(e,ft);return{getEnvironmentProps:function(e){var n=e.inputElement,o=e.formElement,a=e.panelElement;function i(e){!r.getState().isOpen&&r.pendingRequests.isEmpty()||e.target===n||!1===[o,a].some((function(t){return n=t,r=e.target,n===r||n.contains(r);var n,r}))&&(r.dispatch("blur",null),t.debug||r.pendingRequests.cancelAll())}return yt({onTouchStart:i,onMouseDown:i,onTouchMove:function(e){!1!==r.getState().isOpen&&n===t.environment.document.activeElement&&e.target!==n&&n.blur()}},St(e,mt))},getRootProps:function(e){return yt({role:"combobox","aria-expanded":r.getState().isOpen,"aria-haspopup":"listbox","aria-owns":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label")},e)},getFormProps:function(e){e.inputElement;return yt({action:"",noValidate:!0,role:"search",onSubmit:function(a){var i;a.preventDefault(),t.onSubmit(yt({event:a,refresh:n,state:r.getState()},o)),r.dispatch("submit",null),null===(i=e.inputElement)||void 0===i||i.blur()},onReset:function(a){var i;a.preventDefault(),t.onReset(yt({event:a,refresh:n,state:r.getState()},o)),r.dispatch("reset",null),null===(i=e.inputElement)||void 0===i||i.focus()}},St(e,ht))},getLabelProps:function(e){return yt({htmlFor:"".concat(t.id,"-input"),id:"".concat(t.id,"-label")},e)},getInputProps:function(e){var a;function i(e){(t.openOnFocus||Boolean(r.getState().query))&&it(yt({event:e,props:t,query:r.getState().completion||r.getState().query,refresh:n,store:r},o)),r.dispatch("focus",null)}var l=e||{},s=(l.inputElement,l.maxLength),c=void 0===s?512:s,u=St(l,gt),d=Ze(r.getState()),p=function(e){return Boolean(e&&e.match(pt))}((null===(a=t.environment.navigator)||void 0===a?void 0:a.userAgent)||""),f=null!=d&&d.itemUrl&&!p?"go":"search";return yt({"aria-autocomplete":"both","aria-activedescendant":r.getState().isOpen&&null!==r.getState().activeItemId?"".concat(t.id,"-item-").concat(r.getState().activeItemId):void 0,"aria-controls":r.getState().isOpen?"".concat(t.id,"-list"):void 0,"aria-labelledby":"".concat(t.id,"-label"),value:r.getState().completion||r.getState().query,id:"".concat(t.id,"-input"),autoComplete:"off",autoCorrect:"off",autoCapitalize:"off",enterKeyHint:f,spellCheck:"false",autoFocus:t.autoFocus,placeholder:t.placeholder,maxLength:c,type:"search",onChange:function(e){it(yt({event:e,props:t,query:e.currentTarget.value.slice(0,c),refresh:n,store:r},o))},onKeyDown:function(e){!function(e){var t=e.event,n=e.props,r=e.refresh,o=e.store,a=dt(e,lt);if("ArrowUp"===t.key||"ArrowDown"===t.key){var i=function(){var e=n.environment.document.getElementById("".concat(n.id,"-item-").concat(o.getState().activeItemId));e&&(e.scrollIntoViewIfNeeded?e.scrollIntoViewIfNeeded(!1):e.scrollIntoView(!1))},l=function(){var e=Ze(o.getState());if(null!==o.getState().activeItemId&&e){var n=e.item,i=e.itemInputValue,l=e.itemUrl,s=e.source;s.onActive(ct({event:t,item:n,itemInputValue:i,itemUrl:l,refresh:r,source:s,state:o.getState()},a))}};t.preventDefault(),!1===o.getState().isOpen&&(n.openOnFocus||Boolean(o.getState().query))?it(ct({event:t,props:n,query:o.getState().query,refresh:r,store:o},a)).then((function(){o.dispatch(t.key,{nextActiveItemId:n.defaultActiveItemId}),l(),setTimeout(i,0)})):(o.dispatch(t.key,{}),l(),i())}else if("Escape"===t.key)t.preventDefault(),o.dispatch(t.key,null),o.pendingRequests.cancelAll();else if("Tab"===t.key)o.dispatch("blur",null),o.pendingRequests.cancelAll();else if("Enter"===t.key){if(null===o.getState().activeItemId||o.getState().collections.every((function(e){return 0===e.items.length})))return void(n.debug||o.pendingRequests.cancelAll());t.preventDefault();var s=Ze(o.getState()),c=s.item,u=s.itemInputValue,d=s.itemUrl,p=s.source;if(t.metaKey||t.ctrlKey)void 0!==d&&(p.onSelect(ct({event:t,item:c,itemInputValue:u,itemUrl:d,refresh:r,source:p,state:o.getState()},a)),n.navigator.navigateNewTab({itemUrl:d,item:c,state:o.getState()}));else if(t.shiftKey)void 0!==d&&(p.onSelect(ct({event:t,item:c,itemInputValue:u,itemUrl:d,refresh:r,source:p,state:o.getState()},a)),n.navigator.navigateNewWindow({itemUrl:d,item:c,state:o.getState()}));else if(t.altKey);else{if(void 0!==d)return p.onSelect(ct({event:t,item:c,itemInputValue:u,itemUrl:d,refresh:r,source:p,state:o.getState()},a)),void n.navigator.navigate({itemUrl:d,item:c,state:o.getState()});it(ct({event:t,nextState:{isOpen:!1},props:n,query:u,refresh:r,store:o},a)).then((function(){p.onSelect(ct({event:t,item:c,itemInputValue:u,itemUrl:d,refresh:r,source:p,state:o.getState()},a))}))}}}(yt({event:e,props:t,refresh:n,store:r},o))},onFocus:i,onBlur:Se,onClick:function(n){e.inputElement!==t.environment.document.activeElement||r.getState().isOpen||i(n)}},u)},getPanelProps:function(e){return yt({onMouseDown:function(e){e.preventDefault()},onMouseLeave:function(){r.dispatch("mouseleave",null)}},e)},getListProps:function(e){return yt({role:"listbox","aria-labelledby":"".concat(t.id,"-label"),id:"".concat(t.id,"-list")},e)},getItemProps:function(e){var a=e.item,i=e.source,l=St(e,vt);return yt({id:"".concat(t.id,"-item-").concat(a.__autocomplete_id),role:"option","aria-selected":r.getState().activeItemId===a.__autocomplete_id,onMouseMove:function(e){if(a.__autocomplete_id!==r.getState().activeItemId){r.dispatch("mousemove",a.__autocomplete_id);var t=Ze(r.getState());if(null!==r.getState().activeItemId&&t){var i=t.item,l=t.itemInputValue,s=t.itemUrl,c=t.source;c.onActive(yt({event:e,item:i,itemInputValue:l,itemUrl:s,refresh:n,source:c,state:r.getState()},o))}}},onMouseDown:function(e){e.preventDefault()},onClick:function(e){var l=i.getItemInputValue({item:a,state:r.getState()}),s=i.getItemUrl({item:a,state:r.getState()});(s?Promise.resolve():it(yt({event:e,nextState:{isOpen:!1},props:t,query:l,refresh:n,store:r},o))).then((function(){i.onSelect(yt({event:e,item:a,itemInputValue:l,itemUrl:s,refresh:n,source:i,state:r.getState()},o))}))}},l)}}}var Et="1.7.4",_t=[{segment:"autocomplete-core",version:Et}];function xt(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Ot(e){for(var t=1;t=n?null===r?null:0:o}function At(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Rt(e){for(var t=1;t0},reshape:function(e){return e.sources}},e),{},{id:null!==(n=e.id)&&void 0!==n?n:we(),plugins:o,initialState:Pe({activeItemId:null,query:"",completion:null,collections:[],isOpen:!1,status:"idle",context:{}},e.initialState),onStateChange:function(t){var n;null===(n=e.onStateChange)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onStateChange)||void 0===n?void 0:n.call(e,t)}))},onSubmit:function(t){var n;null===(n=e.onSubmit)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onSubmit)||void 0===n?void 0:n.call(e,t)}))},onReset:function(t){var n;null===(n=e.onReset)||void 0===n||n.call(e,t),o.forEach((function(e){var n;return null===(n=e.onReset)||void 0===n?void 0:n.call(e,t)}))},getSources:function(n){return Promise.all([].concat(xe(o.map((function(e){return e.getSources}))),[e.getSources]).filter(Boolean).map((function(e){return _e(e,n)}))).then((function(e){return me(e)})).then((function(e){return e.map((function(e){return Pe(Pe({},e),{},{onSelect:function(n){e.onSelect(n),t.forEach((function(e){var t;return null===(t=e.onSelect)||void 0===t?void 0:t.call(e,n)}))},onActive:function(n){e.onActive(n),t.forEach((function(e){var t;return null===(t=e.onActive)||void 0===t?void 0:t.call(e,n)}))}})}))}))},navigator:Pe({navigate:function(e){var t=e.itemUrl;r.location.assign(t)},navigateNewTab:function(e){var t=e.itemUrl,n=r.open(t,"_blank","noopener");null==n||n.focus()},navigateNewWindow:function(e){var t=e.itemUrl;r.open(t,"_blank","noopener")}},e.navigator)})}(e,t),r=fe(Nt,n,(function(e){var t=e.prevState,r=e.state;n.onStateChange(Dt({prevState:t,state:r,refresh:i},o))})),o=function(e){var t=e.store;return{setActiveItemId:function(e){t.dispatch("setActiveItemId",e)},setQuery:function(e){t.dispatch("setQuery",e)},setCollections:function(e){var n=0,r=e.map((function(e){return ge(ge({},e),{},{items:me(e.items).map((function(e){return ge(ge({},e),{},{__autocomplete_id:n++})}))})}));t.dispatch("setCollections",r)},setIsOpen:function(e){t.dispatch("setIsOpen",e)},setStatus:function(e){t.dispatch("setStatus",e)},setContext:function(e){t.dispatch("setContext",e)}}}({store:r}),a=kt(Dt({props:n,refresh:i,store:r},o));function i(){return it(Dt({event:new Event("input"),nextState:{isOpen:r.getState().isOpen},props:n,query:r.getState().query,refresh:i,store:r},o))}return n.plugins.forEach((function(e){var n;return null===(n=e.subscribe)||void 0===n?void 0:n.call(e,Dt(Dt({},o),{},{refresh:i,onSelect:function(e){t.push({onSelect:e})},onActive:function(e){t.push({onActive:e})}}))})),function(e){var t,n,r=e.metadata,o=e.environment;if(null===(t=o.navigator)||void 0===t||null===(n=t.userAgent)||void 0===n?void 0:n.includes("Algolia Crawler")){var a=o.document.createElement("meta"),i=o.document.querySelector("head");a.name="algolia:metadata",setTimeout((function(){a.content=JSON.stringify(r),i.appendChild(a)}),0)}}({metadata:Pt({plugins:n.plugins,options:e}),environment:n.environment}),Dt(Dt({refresh:i},a),o)}function Bt(e){return{current:e}}function Ut(e,t){var n=void 0;return function(){for(var r=arguments.length,o=new Array(r),a=0;a=5&&((o||!e&&5===r)&&(i.push(r,0,o,n),r=6),e&&(i.push(r,e,0,n),r=6)),o=""},s=0;s"===t?(r=1,o=""):o=t+o[0]:a?t===a?a="":o+=t:'"'===t||"'"===t?a=t:">"===t?(l(),r=1):r&&("="===t?(r=5,n=o,o=""):"/"===t&&(r<5||">"===e[s][c+1])?(l(),3===r&&(i=i[0]),r=i,(i=i[0]).push(2,0,r),r=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(l(),r=2):o+=t),3===r&&"!--"===o&&(r=4,i=i[0])}return l(),i}(e)),t),arguments,[])).length>1?t:t[0]}var qt=function(e){var t=e.environment,n=t.document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("class","aa-SubmitIcon"),n.setAttribute("viewBox","0 0 24 24"),n.setAttribute("width","20"),n.setAttribute("height","20"),n.setAttribute("fill","currentColor");var r=t.document.createElementNS("http://www.w3.org/2000/svg","path");return r.setAttribute("d","M16.041 15.856c-0.034 0.026-0.067 0.055-0.099 0.087s-0.060 0.064-0.087 0.099c-1.258 1.213-2.969 1.958-4.855 1.958-1.933 0-3.682-0.782-4.95-2.050s-2.050-3.017-2.050-4.95 0.782-3.682 2.050-4.95 3.017-2.050 4.95-2.050 3.682 0.782 4.95 2.050 2.050 3.017 2.050 4.95c0 1.886-0.745 3.597-1.959 4.856zM21.707 20.293l-3.675-3.675c1.231-1.54 1.968-3.493 1.968-5.618 0-2.485-1.008-4.736-2.636-6.364s-3.879-2.636-6.364-2.636-4.736 1.008-6.364 2.636-2.636 3.879-2.636 6.364 1.008 4.736 2.636 6.364 3.879 2.636 6.364 2.636c2.125 0 4.078-0.737 5.618-1.968l3.675 3.675c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414z"),n.appendChild(r),n},$t=function(e){var t=e.environment,n=t.document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("class","aa-ClearIcon"),n.setAttribute("viewBox","0 0 24 24"),n.setAttribute("width","18"),n.setAttribute("height","18"),n.setAttribute("fill","currentColor");var r=t.document.createElementNS("http://www.w3.org/2000/svg","path");return r.setAttribute("d","M5.293 6.707l5.293 5.293-5.293 5.293c-0.391 0.391-0.391 1.024 0 1.414s1.024 0.391 1.414 0l5.293-5.293 5.293 5.293c0.391 0.391 1.024 0.391 1.414 0s0.391-1.024 0-1.414l-5.293-5.293 5.293-5.293c0.391-0.391 0.391-1.024 0-1.414s-1.024-0.391-1.414 0l-5.293 5.293-5.293-5.293c-0.391-0.391-1.024-0.391-1.414 0s-0.391 1.024 0 1.414z"),n.appendChild(r),n},Gt=function(e){var t=e.environment.document.createElementNS("http://www.w3.org/2000/svg","svg");return t.setAttribute("class","aa-LoadingIcon"),t.setAttribute("viewBox","0 0 100 100"),t.setAttribute("width","20"),t.setAttribute("height","20"),t.innerHTML='\n \n',t},Wt=["ontouchstart","ontouchend","ontouchmove","ontouchcancel"];function Qt(e,t,n){e[t]=null===n?"":"number"!=typeof n?n:n+"px"}function Zt(e){this._listeners[e.type](e)}function Yt(e,t,n){var r,o,a=e[t];if("style"===t)if("string"==typeof n)e.style=n;else if(null===n)e.style="";else for(t in n)a&&n[t]===a[t]||Qt(e.style,t,n[t]);else"o"===t[0]&&"n"===t[1]?(r=t!==(t=t.replace(/Capture$/,"")),((o=t.toLowerCase())in e||Wt.includes(o))&&(t=o),t=t.slice(2),e._listeners||(e._listeners={}),e._listeners[t]=n,n?a||e.addEventListener(t,Zt,r):e.removeEventListener(t,Zt,r)):"list"!==t&&"tagName"!==t&&"form"!==t&&"type"!==t&&"size"!==t&&"download"!==t&&"href"!==t&&t in e?e[t]=null==n?"":n:"function"!=typeof n&&"dangerouslySetInnerHTML"!==t&&(null==n||!1===n&&!/^ar/.test(t)?e.removeAttribute(t):e.setAttribute(t,n))}function Kt(e){return"onChange"===e?"onInput":e}function Xt(e,t){for(var n in t)Yt(e,Kt(n),t[n])}function Jt(e,t){for(var n in t)"o"===n[0]&&"n"===n[1]||Yt(e,Kt(n),t[n])}var en=["children"];function tn(e){return function(e){if(Array.isArray(e))return nn(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return nn(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return nn(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function nn(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function on(e){return function(t,n){var r=n.children,o=void 0===r?[]:r,a=rn(n,en),i=e.document.createElement(t);return Xt(i,a),i.append.apply(i,tn(o)),i}}var an=["autocompleteScopeApi","environment","classNames","getInputProps","getInputPropsCore","isDetached","state"];function ln(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function sn(e){for(var t=1;t=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function dn(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function pn(e){for(var t=1;t2&&(i.children=arguments.length>3?hn.call(arguments,2):n),"function"==typeof e&&null!=e.defaultProps)for(a in e.defaultProps)void 0===i[a]&&(i[a]=e.defaultProps[a]);return On(e,i,r,o,null)}function On(e,t,n,r,o){var a={type:e,props:t,key:n,ref:r,__k:null,__:null,__b:0,__e:null,__d:void 0,__c:null,__h:null,constructor:void 0,__v:null==o?++vn:o};return null==o&&null!=gn.vnode&&gn.vnode(a),a}function Tn(e){return e.children}function Pn(e,t){this.props=e,this.context=t}function Cn(e,t){if(null==t)return e.__?Cn(e.__,e.__.__k.indexOf(e)+1):null;for(var n;t0?On(f.type,f.props,f.key,f.ref?f.ref:null,f.__v):f)){if(f.__=n,f.__b=n.__b+1,null===(p=v[u])||p&&f.key==p.key&&f.type===p.type)v[u]=void 0;else for(d=0;d0&&void 0!==arguments[0]?arguments[0]:[];return{get:function(){return e},add:function(t){var n=e[e.length-1];(null==n?void 0:n.isHighlighted)===t.isHighlighted?e[e.length-1]={value:n.value+t.value,isHighlighted:n.isHighlighted}:e.push(t)}}}(n?[{value:n,isHighlighted:!1}]:[]);return t.forEach((function(e){var t=e.split("__/aa-highlight__");r.add({value:t[0],isHighlighted:!0}),""!==t[1]&&r.add({value:t[1],isHighlighted:!1})})),r.get()}function Zn(e){return function(e){if(Array.isArray(e))return Yn(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return Yn(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Yn(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function Yn(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n",""":'"',"'":"'"},er=new RegExp(/\w/i),tr=/&(amp|quot|lt|gt|#39);/g,nr=RegExp(tr.source);function rr(e,t){var n,r,o,a=e[t],i=(null===(n=e[t+1])||void 0===n?void 0:n.isHighlighted)||!0,l=(null===(r=e[t-1])||void 0===r?void 0:r.isHighlighted)||!0;return er.test((o=a.value)&&nr.test(o)?o.replace(tr,(function(e){return Jn[e]})):o)||l!==i?a.isHighlighted:l}function or(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function ar(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}var Sr={clearButton:"aa-ClearButton",detachedCancelButton:"aa-DetachedCancelButton",detachedContainer:"aa-DetachedContainer",detachedFormContainer:"aa-DetachedFormContainer",detachedOverlay:"aa-DetachedOverlay",detachedSearchButton:"aa-DetachedSearchButton",detachedSearchButtonIcon:"aa-DetachedSearchButtonIcon",detachedSearchButtonPlaceholder:"aa-DetachedSearchButtonPlaceholder",form:"aa-Form",input:"aa-Input",inputWrapper:"aa-InputWrapper",inputWrapperPrefix:"aa-InputWrapperPrefix",inputWrapperSuffix:"aa-InputWrapperSuffix",item:"aa-Item",label:"aa-Label",list:"aa-List",loadingIndicator:"aa-LoadingIndicator",panel:"aa-Panel",panelLayout:"aa-PanelLayout aa-Panel--scrollable",root:"aa-Autocomplete",source:"aa-Source",sourceFooter:"aa-SourceFooter",sourceHeader:"aa-SourceHeader",sourceNoResults:"aa-SourceNoResults",submitButton:"aa-SubmitButton"},kr=function(e,t){var n=e.children;(0,e.render)(n,t)},Er={createElement:xn,Fragment:Tn,render:Gn};function _r(e){var t=e.panelPlacement,n=e.container,r=e.form,o=e.environment,a=n.getBoundingClientRect(),i=(o.pageYOffset||o.document.documentElement.scrollTop||o.document.body.scrollTop||0)+a.top+a.height;switch(t){case"start":return{top:i,left:a.left};case"end":return{top:i,right:o.document.documentElement.clientWidth-(a.left+a.width)};case"full-width":return{top:i,left:0,right:0,width:"unset",maxWidth:"unset"};case"input-wrapper-width":var l=r.getBoundingClientRect();return{top:i,left:l.left,right:o.document.documentElement.clientWidth-(l.left+l.width),width:"unset",maxWidth:"unset"};default:throw new Error("[Autocomplete] The `panelPlacement` value ".concat(JSON.stringify(t)," is not valid."))}}function xr(){return xr=Object.assign||function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=new Array(t);ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function Hr(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function qr(e){for(var t=1;t0;if(!p.value.core.openOnFocus&&!t.query)return n;var r=Boolean(c.current||p.value.renderer.renderNoResults);return!n&&r||n},__autocomplete_metadata:{userAgents:Cr,options:e}}))})),h=Bt(qr({collections:[],completion:null,context:{},isOpen:!1,query:"",activeItemId:null,status:"idle"},p.value.core.initialState)),g={getEnvironmentProps:p.value.renderer.getEnvironmentProps,getFormProps:p.value.renderer.getFormProps,getInputProps:p.value.renderer.getInputProps,getItemProps:p.value.renderer.getItemProps,getLabelProps:p.value.renderer.getLabelProps,getListProps:p.value.renderer.getListProps,getPanelProps:p.value.renderer.getPanelProps,getRootProps:p.value.renderer.getRootProps},v={setActiveItemId:m.value.setActiveItemId,setQuery:m.value.setQuery,setCollections:m.value.setCollections,setIsOpen:m.value.setIsOpen,setStatus:m.value.setStatus,setContext:m.value.setContext,refresh:m.value.refresh},b=l((function(){return Ht.bind(p.value.renderer.renderer.createElement)})),y=l((function(){return mn({autocomplete:m.value,autocompleteScopeApi:v,classNames:p.value.renderer.classNames,environment:p.value.core.environment,isDetached:f.value,placeholder:p.value.core.placeholder,propGetters:g,setIsModalOpen:E,state:h.current,translations:p.value.renderer.translations})}));function w(){Xt(y.value.panel,{style:f.value?{}:_r({panelPlacement:p.value.renderer.panelPlacement,container:y.value.root,form:y.value.form,environment:p.value.core.environment})})}function S(e){h.current=e;var t={autocomplete:m.value,autocompleteScopeApi:v,classNames:p.value.renderer.classNames,components:p.value.renderer.components,container:p.value.renderer.container,html:b.value,dom:y.value,panelContainer:f.value?y.value.detachedContainer:p.value.renderer.panelContainer,propGetters:g,state:h.current,renderer:p.value.renderer.renderer},n=!be(e)&&!c.current&&p.value.renderer.renderNoResults||p.value.renderer.render;!function(e){var t=e.autocomplete,n=e.autocompleteScopeApi,r=e.dom,o=e.propGetters,a=e.state;Jt(r.root,o.getRootProps(Tr({state:a,props:t.getRootProps({})},n))),Jt(r.input,o.getInputProps(Tr({state:a,props:t.getInputProps({inputElement:r.input}),inputElement:r.input},n))),Xt(r.label,{hidden:"stalled"===a.status}),Xt(r.loadingIndicator,{hidden:"stalled"!==a.status}),Xt(r.clearButton,{hidden:!a.query})}(t),function(e,t){var n=t.autocomplete,r=t.autocompleteScopeApi,o=t.classNames,a=t.html,i=t.dom,l=t.panelContainer,s=t.propGetters,c=t.state,u=t.components,d=t.renderer;if(c.isOpen){l.contains(i.panel)||"loading"===c.status||l.appendChild(i.panel),i.panel.classList.toggle("aa-Panel--stalled","stalled"===c.status);var p=c.collections.filter((function(e){var t=e.source,n=e.items;return t.templates.noResults||n.length>0})).map((function(e,t){var i=e.source,l=e.items;return d.createElement("section",{key:t,className:o.source,"data-autocomplete-source-id":i.sourceId},i.templates.header&&d.createElement("div",{className:o.sourceHeader},i.templates.header({components:u,createElement:d.createElement,Fragment:d.Fragment,items:l,source:i,state:c,html:a})),i.templates.noResults&&0===l.length?d.createElement("div",{className:o.sourceNoResults},i.templates.noResults({components:u,createElement:d.createElement,Fragment:d.Fragment,source:i,state:c,html:a})):d.createElement("ul",xr({className:o.list},s.getListProps(Tr({state:c,props:n.getListProps({})},r))),l.map((function(e){var t=n.getItemProps({item:e,source:i});return d.createElement("li",xr({key:t.id,className:o.item},s.getItemProps(Tr({state:c,props:t},r))),i.templates.item({components:u,createElement:d.createElement,Fragment:d.Fragment,item:e,state:c,html:a}))}))),i.templates.footer&&d.createElement("div",{className:o.sourceFooter},i.templates.footer({components:u,createElement:d.createElement,Fragment:d.Fragment,items:l,source:i,state:c,html:a})))})),f=d.createElement(d.Fragment,null,d.createElement("div",{className:o.panelLayout},p),d.createElement("div",{className:"aa-GradientBottom"})),m=p.reduce((function(e,t){return e[t.props["data-autocomplete-source-id"]]=t,e}),{});e(Tr(Tr({children:f,state:c,sections:p,elements:m},d),{},{components:u,html:a},r),i.panel)}else l.contains(i.panel)&&l.removeChild(i.panel)}(n,t)}function k(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};o();var t=p.value.renderer,n=t.components,r=Vr(t,zr);u.current=Nr(r,p.value.core,{components:Ur(n,(function(e){return!e.value.hasOwnProperty("__autocomplete_componentName")})),initialState:h.current},e),s(),a(),m.value.refresh().then((function(){S(h.current)}))}function E(e){requestAnimationFrame((function(){var t=p.value.core.environment.document.body.contains(y.value.detachedOverlay);e!==t&&(e?(p.value.core.environment.document.body.appendChild(y.value.detachedOverlay),p.value.core.environment.document.body.classList.add("aa-Detached"),y.value.input.focus()):(p.value.core.environment.document.body.removeChild(y.value.detachedOverlay),p.value.core.environment.document.body.classList.remove("aa-Detached"),m.value.setQuery(""),m.value.refresh()))}))}return r((function(){var e=m.value.getEnvironmentProps({formElement:y.value.form,panelElement:y.value.panel,inputElement:y.value.input});return Xt(p.value.core.environment,e),function(){Xt(p.value.core.environment,Object.keys(e).reduce((function(e,t){return qr(qr({},e),{},$r({},t,void 0))}),{}))}})),r((function(){var e=f.value?p.value.core.environment.document.body:p.value.renderer.panelContainer,t=f.value?y.value.detachedOverlay:y.value.panel;return f.value&&h.current.isOpen&&E(!0),S(h.current),function(){e.contains(t)&&e.removeChild(t)}})),r((function(){var e=p.value.renderer.container;return e.appendChild(y.value.root),function(){e.removeChild(y.value.root)}})),r((function(){var e=Ut((function(e){S(e.state)}),0);return d.current=function(t){var n=t.state,r=t.prevState;(f.value&&r.isOpen!==n.isOpen&&E(n.isOpen),f.value||!n.isOpen||r.isOpen||w(),n.query!==r.query)&&p.value.core.environment.document.querySelectorAll(".aa-Panel--scrollable").forEach((function(e){0!==e.scrollTop&&(e.scrollTop=0)}));e({state:n})},function(){d.current=void 0}})),r((function(){var e=Ut((function(){var e=f.value;f.value=p.value.core.environment.matchMedia(p.value.renderer.detachedMediaQuery).matches,e!==f.value?k({}):requestAnimationFrame(w)}),20);return p.value.core.environment.addEventListener("resize",e),function(){p.value.core.environment.removeEventListener("resize",e)}})),r((function(){if(!f.value)return function(){};function e(e){y.value.detachedContainer.classList.toggle("aa-DetachedContainer--modal",e)}function t(t){e(t.matches)}var n=p.value.core.environment.matchMedia(getComputedStyle(p.value.core.environment.document.documentElement).getPropertyValue("--aa-detached-modal-media-query"));e(n.matches);var r=Boolean(n.addEventListener);return r?n.addEventListener("change",t):n.addListener(t),function(){r?n.removeEventListener("change",t):n.removeListener(t)}})),r((function(){return requestAnimationFrame(w),function(){}})),qr(qr({},v),{},{update:k,destroy:function(){o()}})}var Wr=n(5742);const Qr=n(8965),Zr=Qr,Yr=(n(8965),e=>Qr.tokenizer(e).map((e=>e.str))),Kr=Zr;var Xr=n(813),Jr=n.n(Xr);function eo(){var e;const t=(0,u.TH)(),n=(0,u.k6)(),{siteConfig:{baseUrl:o}}=(0,ne.Z)(),[a,i]=(0,r.useState)({terms:[],isDocsOrBlog:!1});return(0,r.useEffect)((()=>{var e;if(null==(e=t.state)||!e.cmfcmfhighlight||0===t.state.cmfcmfhighlight.terms.length)return;i(t.state.cmfcmfhighlight);const{cmfcmfhighlight:r,...o}=t.state;n.replace({...t,state:o})}),[null==(e=t.state)?void 0:e.cmfcmfhighlight,n,t]),(0,r.useEffect)((()=>{if(0===a.terms.length)return;const e=a.isDocsOrBlog?document.getElementsByTagName("article")[0]:document.getElementsByTagName("main")[0];if(!e)return;const t=new(Jr())(e),n={ignoreJoiners:!0};return t.mark(a.terms,n),()=>t.unmark(n)}),[a,o]),null}var to=n(8084),no=n(3320);function ro(e){let{document:t}=e;const[n,r]=t.sectionRoute.split("#");let o=n;return r&&(o+="#"+r),o}const oo={documents:[],index:Kr((function(){this.ref("id"),this.field("title"),this.field("content")}))};const ao=()=>{const e=(0,N.Z)(),[t,n]=(0,r.useState)((()=>!!e&&"dark"===document.documentElement.getAttribute("data-theme")));(0,r.useEffect)((()=>{const e=new MutationObserver((()=>{n("dark"===document.documentElement.getAttribute("data-theme"))}));return e.observe(document.documentElement,{attributes:!0,attributeFilter:["data-theme"]}),()=>e.disconnect()}),[]);const{siteConfig:{baseUrl:o}}=(0,ne.Z)(),{titleBoost:a,contentBoost:i,tagsBoost:l,parentCategoriesBoost:s,indexDocSidebarParentCategories:d,maxSearchResults:p}=(0,to.eZ)("@cmfcmf/docusaurus-search-local"),f=(0,u.k6)(),{tags:m}=(0,no._q)(),h=(0,r.useRef)(m);(0,r.useEffect)((()=>{h.current=m}),[m]);const g=(0,r.useRef)({}),v=async e=>{const t=g.current[e];switch(null==t?void 0:t.state){case"ready":return t;case void 0:{const t=[];g.current[e]={state:"loading",callbacks:t};const n=await async function(e,t){{let r;try{const n=await fetch(e+"search-index-"+t+".json");if(!n.ok)return oo;r=await n.json()}catch(n){return oo}return{documents:r.documents,index:Kr.Index.load(r.index)}}}(o,e);return t.forEach((e=>e(n))),g.current[e]={state:"ready",...n}}case"loading":return new Promise((e=>{t.callbacks.push(e)}))}},b=(0,c.I)({message:"cmfcmf/d-s-l.searchBar.placeholder",description:"Placeholder shown in the searchbar"}),y=(0,r.useRef)(null),w=(0,r.useRef)(null);return(0,r.useEffect)((()=>{if(y.current)return w.current=Gr({container:y.current,placeholder:b,renderer:{createElement:r.createElement,Fragment:r.Fragment},render(e,t){let{children:n}=e;(0,ue.render)(n,t)},navigator:{navigate(e){let{item:t,itemUrl:n}=e;f.push(n,{cmfcmfhighlight:{terms:t.terms,isDocsOrBlog:"docs"===t.document.type||"blog"===t.document.type}})}},detachedMediaQuery:"",defaultActiveItemId:0,translations:{clearButtonTitle:(0,c.I)({message:"cmfcmf/d-s-l.searchBar.clearButtonTitle",description:"Title of the button to clear the current search input"}),detachedCancelButtonText:(0,c.I)({message:"cmfcmf/d-s-l.searchBar.detachedCancelButtonText",description:"Text of the button to close the detached search window"}),submitButtonTitle:(0,c.I)({message:"cmfcmf/d-s-l.searchBar.submitButtonTitle",description:"Title of the button to submit a new search"})},getSources(e){let{query:t}=e;return[{sourceId:"search-results",templates:{item(e){let{item:t}=e;const n=ro(t);return r.createElement("a",{href:n,className:"aa-ItemLink",onClick:e=>{e.preventDefault(),f.push(n,{cmfcmfhighlight:{terms:t.terms,isDocsOrBlog:"docs"===t.document.type||"blog"===t.document.type}})}},r.createElement("div",{className:"aa-ItemContent"},r.createElement("div",{className:"aa-ItemContentBody"},r.createElement("div",{className:"aa-ItemContentTitle"},t.document.sectionTitle),t.document.pageTitle!==t.document.sectionTitle&&r.createElement("div",{className:"aa-ItemContentDescription"},t.document.pageTitle))),r.createElement("div",{className:"aa-ItemActions"},r.createElement("button",{className:"aa-ItemActionButton aa-DesktopOnly aa-ActiveOnly",type:"button",title:"Select"},r.createElement("svg",{viewBox:"0 0 24 24",width:"20",height:"20",fill:"currentColor"},r.createElement("path",{d:"M18.984 6.984h2.016v6h-15.188l3.609 3.609-1.406 1.406-6-6 6-6 1.406 1.406-3.609 3.609h13.172v-4.031z"})))))},noResults:()=>r.createElement("div",{className:"aa-ItemContent"},r.createElement("div",{className:"aa-ItemContentBody"},(0,c.I)({message:"cmfcmf/d-s-l.searchBar.noResults",description:"message shown if no results are found"})))},getItemUrl(e){let{item:t}=e;return ro(t)},async getItems(){const e=h.current,n=await Promise.all(e.map((e=>v(e)))),r=Yr(t);return n.flatMap((e=>{let{index:t,documents:n}=e;return t.query((e=>{e.term(r,{fields:["title"],boost:a}),e.term(r,{fields:["title"],boost:a,wildcard:Kr.Query.wildcard.TRAILING}),e.term(r,{fields:["content"],boost:i}),e.term(r,{fields:["content"],boost:i,wildcard:Kr.Query.wildcard.TRAILING}),e.term(r,{fields:["tags"],boost:l}),e.term(r,{fields:["tags"],boost:l,wildcard:Kr.Query.wildcard.TRAILING}),d&&(e.term(r,{fields:["sidebarParentCategories"],boost:s}),e.term(r,{fields:["sidebarParentCategories"],boost:s,wildcard:Kr.Query.wildcard.TRAILING}))})).slice(0,p).map((e=>({document:n.find((t=>t.id.toString()===e.ref)),score:e.score,terms:r})))})).sort(((e,t)=>t.score-e.score)).slice(0,p)}}]}}),()=>{var e;return null==(e=w.current)?void 0:e.destroy()}}),[p]),r.createElement(r.Fragment,null,r.createElement(Wr.Z,null,r.createElement("body",{"data-theme":t?"dark":"light"})),r.createElement(eo,null),r.createElement("div",{className:"dsla-search-wrapper"},r.createElement("div",{className:"dsla-search-field",ref:y,"data-tags":m.join(",")})))},io="searchBox_ZlJk";function lo(e){let{children:t,className:n}=e;return r.createElement("div",{className:(0,o.Z)(n,io)},t)}var so=n(4104),co=n(2802);var uo=n(373);const po=e=>e.docs.find((t=>t.id===e.mainDocId));const fo={default:J,localeDropdown:function(e){let{mobile:t,dropdownItemsBefore:n,dropdownItemsAfter:o,...a}=e;const{i18n:{currentLocale:i,locales:l,localeConfigs:s}}=(0,ne.Z)(),u=(0,le.l)(),d=[...n,...l.map((e=>{const n="pathname://"+u.createUrl({locale:e,fullyQualified:!1});return{label:s[e].label,to:n,target:"_self",autoAddBaseUrl:!1,className:e===i?t?"menu__link--active":"dropdown__link--active":""}})),...o],p=t?(0,c.I)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):s[i].label;return r.createElement(ie,(0,v.Z)({},a,{mobile:t,label:r.createElement(r.Fragment,null,r.createElement(se,{className:ce}),p),items:d}))},search:function(e){let{mobile:t,className:n}=e;return t?null:r.createElement(lo,{className:n},r.createElement(ao,null))},dropdown:ie,html:function(e){let{value:t,className:n,mobile:a=!1,isDropdownItem:i=!1}=e;const l=i?"li":"div";return r.createElement(l,{className:(0,o.Z)({navbar__item:!a&&!i,"menu__list-item":a},n),dangerouslySetInnerHTML:{__html:t}})},doc:function(e){let{docId:t,label:n,docsPluginId:o,...a}=e;const{activeDoc:i}=(0,so.Iw)(o),l=(0,co.vY)(t,o);return null===l?null:r.createElement(J,(0,v.Z)({exact:!0},a,{isActive:()=>(null==i?void 0:i.path)===l.path||!(null==i||!i.sidebar)&&i.sidebar===l.sidebar,label:null!=n?n:l.id,to:l.path}))},docSidebar:function(e){let{sidebarId:t,label:n,docsPluginId:o,...a}=e;const{activeDoc:i}=(0,so.Iw)(o),l=(0,co.oz)(t,o).link;if(!l)throw new Error('DocSidebarNavbarItem: Sidebar with ID "'+t+"\" doesn't have anything to be linked to.");return r.createElement(J,(0,v.Z)({exact:!0},a,{isActive:()=>(null==i?void 0:i.sidebar)===t,label:null!=n?n:l.label,to:l.path}))},docsVersion:function(e){let{label:t,to:n,docsPluginId:o,...a}=e;const i=(0,co.lO)(o)[0],l=null!=t?t:i.label,s=null!=n?n:(e=>e.docs.find((t=>t.id===e.mainDocId)))(i).path;return r.createElement(J,(0,v.Z)({},a,{label:l,to:s}))},docsVersionDropdown:function(e){let{mobile:t,docsPluginId:n,dropdownActiveClassDisabled:o,dropdownItemsBefore:a,dropdownItemsAfter:i,...l}=e;const s=(0,so.Iw)(n),u=(0,so.gB)(n),{savePreferredVersionName:d}=(0,uo.J)(n),p=u.map((e=>{var t;const n=null!=(t=s.alternateDocVersions[e.name])?t:po(e);return{label:e.label,to:n.path,isActive:()=>e===s.activeVersion,onClick:()=>d(e.name)}})),f=[...a,...p,...i],m=(0,co.lO)(n)[0],h=t&&f.length>1?(0,c.I)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):m.label,g=t&&f.length>1?void 0:po(m).path;return f.length<=1?r.createElement(J,(0,v.Z)({},l,{mobile:t,label:h,to:g,isActive:o?()=>!1:void 0})):r.createElement(ie,(0,v.Z)({},l,{mobile:t,label:h,to:g,items:f,isActive:o?()=>!1:void 0}))}},mo=fo;function ho(e){let{type:t,...n}=e;const o=function(e,t){return e&&"default"!==e?e:"items"in t?"dropdown":"default"}(t,n),a=mo[o];if(!a)throw new Error('No NavbarItem component found for type "'+t+'".');return r.createElement(a,n)}function go(){const e=(0,_.e)(),t=(0,h.L)().navbar.items;return r.createElement("ul",{className:"menu__list"},t.map(((t,n)=>r.createElement(ho,(0,v.Z)({mobile:!0},t,{onClick:()=>e.toggle(),key:n})))))}function vo(e){return r.createElement("button",(0,v.Z)({},e,{type:"button",className:"clean-btn navbar-sidebar__back"}),r.createElement(c.Z,{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)"},"\u2190 Back to main menu"))}function bo(){const e=0===(0,h.L)().navbar.items.length,t=A();return r.createElement(r.Fragment,null,!e&&r.createElement(vo,{onClick:()=>t.hide()}),t.content)}function yo(){const e=(0,_.e)();var t;return void 0===(t=e.shown)&&(t=!0),(0,r.useEffect)((()=>(document.body.style.overflow=t?"hidden":"visible",()=>{document.body.style.overflow="visible"})),[t]),e.shouldRender?r.createElement(R,{header:r.createElement(q,null),primaryMenu:r.createElement(go,null),secondaryMenu:r.createElement(bo,null)}):null}const wo="navbarHideable_m1mJ",So="navbarHidden_jGov";function ko(e){return r.createElement("div",(0,v.Z)({role:"presentation"},e,{className:(0,o.Z)("navbar-sidebar__backdrop",e.className)}))}function Eo(e){let{children:t}=e;const{navbar:{hideOnScroll:n,style:a}}=(0,h.L)(),i=(0,_.e)(),{navbarRef:l,isNavbarVisible:s}=function(e){const[t,n]=(0,r.useState)(e),o=(0,r.useRef)(!1),a=(0,r.useRef)(0),i=(0,r.useCallback)((e=>{null!==e&&(a.current=e.getBoundingClientRect().height)}),[]);return(0,x.RF)(((t,r)=>{let{scrollY:i}=t;if(!e)return;if(i=l?n(!1):i+c{if(e)return t.location.hash?(o.current=!0,void n(!1)):void n(!0)})),{navbarRef:i,isNavbarVisible:t}}(n);return r.createElement("nav",{ref:l,className:(0,o.Z)("navbar","navbar--fixed-top",n&&[wo,!s&&So],{"navbar--dark":"dark"===a,"navbar--primary":"primary"===a,"navbar-sidebar--show":i.shown})},t,r.createElement(ko,{onClick:i.toggle}),r.createElement(yo,null))}function _o(e){let{width:t=30,height:n=30,className:o,...a}=e;return r.createElement("svg",(0,v.Z)({className:o,width:t,height:n,viewBox:"0 0 30 30","aria-hidden":"true"},a),r.createElement("path",{stroke:"currentColor",strokeLinecap:"round",strokeMiterlimit:"10",strokeWidth:"2",d:"M4 7h22M4 15h22M4 23h22"}))}function xo(){const e=(0,_.e)();return r.createElement("button",{onClick:e.toggle,onKeyDown:e.toggle,"aria-label":"Navigation bar toggle",className:"navbar__toggle clean-btn",type:"button",tabIndex:0},r.createElement(_o,null))}const Oo="colorModeToggle_DEke";function To(e){let{items:t}=e;return r.createElement(r.Fragment,null,t.map(((e,t)=>r.createElement(ho,(0,v.Z)({},e,{key:t})))))}function Po(e){let{left:t,right:n}=e;return r.createElement("div",{className:"navbar__inner"},r.createElement("div",{className:"navbar__items"},t),r.createElement("div",{className:"navbar__items navbar__items--right"},n))}function Co(){const e=(0,_.e)(),t=(0,h.L)().navbar.items,[n,o]=function(e){function t(e){var t;return"left"===(null!=(t=e.position)?t:"right")}return[e.filter(t),e.filter((e=>!t(e)))]}(t),a=t.find((e=>"search"===e.type));return r.createElement(Po,{left:r.createElement(r.Fragment,null,!e.disabled&&r.createElement(xo,null),r.createElement(V,null),r.createElement(To,{items:n})),right:r.createElement(r.Fragment,null,r.createElement(To,{items:o}),r.createElement(U,{className:Oo}),!a&&r.createElement(lo,null,r.createElement(ao,null)))})}function Io(){return r.createElement(Eo,null,r.createElement(Co,null))}function Ao(e){let{item:t}=e;const{to:n,href:o,label:a,prependBaseUrlToHref:i,...l}=t,s=(0,G.Z)(n),c=(0,G.Z)(o,{forcePrependBaseUrl:!0});return r.createElement($.Z,(0,v.Z)({className:"footer__link-item"},o?{href:i?c:o}:{to:s},l),a,o&&!(0,W.Z)(o)&&r.createElement(Z.Z,null))}function Ro(e){var t;let{item:n}=e;return n.html?r.createElement("li",{className:"footer__item",dangerouslySetInnerHTML:{__html:n.html}}):r.createElement("li",{key:null!=(t=n.href)?t:n.to,className:"footer__item"},r.createElement(Ao,{item:n}))}function Lo(e){let{column:t}=e;return r.createElement("div",{className:"col footer__col"},r.createElement("div",{className:"footer__title"},t.title),r.createElement("ul",{className:"footer__items clean-list"},t.items.map(((e,t)=>r.createElement(Ro,{key:t,item:e})))))}function No(e){let{columns:t}=e;return r.createElement("div",{className:"row footer__links"},t.map(((e,t)=>r.createElement(Lo,{key:t,column:e}))))}function jo(){return r.createElement("span",{className:"footer__link-separator"},"\xb7")}function Do(e){let{item:t}=e;return t.html?r.createElement("span",{className:"footer__link-item",dangerouslySetInnerHTML:{__html:t.html}}):r.createElement(Ao,{item:t})}function Fo(e){let{links:t}=e;return r.createElement("div",{className:"footer__links text--center"},r.createElement("div",{className:"footer__links"},t.map(((e,n)=>r.createElement(r.Fragment,{key:n},r.createElement(Do,{item:e}),t.length!==n+1&&r.createElement(jo,null))))))}function Mo(e){let{links:t}=e;return function(e){return"title"in e[0]}(t)?r.createElement(No,{columns:t}):r.createElement(Fo,{links:t})}var Bo=n(941);const Uo="footerLogoLink_BH7S";function zo(e){var t;let{logo:n}=e;const{withBaseUrl:a}=(0,G.C)(),i={light:a(n.src),dark:a(null!=(t=n.srcDark)?t:n.src)};return r.createElement(Bo.Z,{className:(0,o.Z)("footer__logo",n.className),alt:n.alt,sources:i,width:n.width,height:n.height,style:n.style})}function Vo(e){let{logo:t}=e;return t.href?r.createElement($.Z,{href:t.href,className:Uo,target:t.target},r.createElement(zo,{logo:t})):r.createElement(zo,{logo:t})}function Ho(e){let{copyright:t}=e;return r.createElement("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:t}})}function qo(e){let{style:t,links:n,logo:a,copyright:i}=e;return r.createElement("footer",{className:(0,o.Z)("footer",{"footer--dark":"dark"===t})},r.createElement("div",{className:"container container-fluid"},n,(a||i)&&r.createElement("div",{className:"footer__bottom text--center"},a&&r.createElement("div",{className:"margin-bottom--sm"},a),i)))}function $o(){const{footer:e}=(0,h.L)();if(!e)return null;const{copyright:t,links:n,logo:o,style:a}=e;return r.createElement(qo,{style:a,links:n&&n.length>0&&r.createElement(Mo,{links:n}),logo:o&&r.createElement(Vo,{logo:o}),copyright:t&&r.createElement(Ho,{copyright:t})})}const Go=r.memo($o);var Wo=n(12);const Qo="docusaurus.tab.",Zo=r.createContext(void 0);const Yo=(0,O.Qc)([L.S,g.pl,function(e){let{children:t}=e;const n=function(){const[e,t]=(0,r.useState)({}),n=(0,r.useCallback)(((e,t)=>{(0,Wo.W)("docusaurus.tab."+e).set(t)}),[]);(0,r.useEffect)((()=>{try{const e={};(0,Wo._)().forEach((t=>{if(t.startsWith(Qo)){const n=t.substring(Qo.length);e[n]=(0,Wo.W)(t).get()}})),t(e)}catch(e){console.error(e)}}),[]);const o=(0,r.useCallback)(((e,r)=>{t((t=>({...t,[e]:r}))),n(e,r)}),[n]);return(0,r.useMemo)((()=>({tabGroupChoices:e,setTabGroupChoices:o})),[e,o])}();return r.createElement(Zo.Provider,{value:n},t)},x.OC,uo.L5,i.VC,function(e){let{children:t}=e;return r.createElement(T.n2,null,r.createElement(_.M,null,r.createElement(C,null,t)))}]);function Ko(e){let{children:t}=e;return r.createElement(Yo,null,t)}function Xo(e){let{error:t,tryAgain:n}=e;return r.createElement("main",{className:"container margin-vert--xl"},r.createElement("div",{className:"row"},r.createElement("div",{className:"col col--6 col--offset-3"},r.createElement("h1",{className:"hero__title"},r.createElement(c.Z,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed"},"This page crashed.")),r.createElement("p",null,t.message),r.createElement("div",null,r.createElement("button",{type:"button",onClick:n},r.createElement(c.Z,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again when the page crashed"},"Try again"))))))}const Jo="mainWrapper_z2l0";function ea(e){const{children:t,noFooter:n,wrapperClassName:c,title:u,description:d}=e;return(0,s.t)(),r.createElement(Ko,null,r.createElement(i.d,{title:u,description:d}),r.createElement(m,null),r.createElement(E,null),r.createElement(Io,null),r.createElement("div",{className:(0,o.Z)(l.k.wrapper.main,Jo,c)},r.createElement(a.Z,{fallback:e=>r.createElement(Xo,e)},t)),!n&&r.createElement(Go,null))}},1327:(e,t,n)=>{"use strict";n.d(t,{Z:()=>d});var r=n(7462),o=n(7294),a=n(9960),i=n(4996),l=n(2263),s=n(6668),c=n(941);function u(e){let{logo:t,alt:n,imageClassName:r}=e;const a={light:(0,i.Z)(t.src),dark:(0,i.Z)(t.srcDark||t.src)},l=o.createElement(c.Z,{className:t.className,sources:a,height:t.height,width:t.width,alt:n,style:t.style});return r?o.createElement("div",{className:r},l):l}function d(e){var t;const{siteConfig:{title:n}}=(0,l.Z)(),{navbar:{title:c,logo:d}}=(0,s.L)(),{imageClassName:p,titleClassName:f,...m}=e,h=(0,i.Z)((null==d?void 0:d.href)||"/"),g=c?"":n,v=null!=(t=null==d?void 0:d.alt)?t:g;return o.createElement(a.Z,(0,r.Z)({to:h},m,(null==d?void 0:d.target)&&{target:d.target}),d&&o.createElement(u,{logo:d,alt:v,imageClassName:p}),null!=c&&o.createElement("b",{className:f},c))}},197:(e,t,n)=>{"use strict";n.d(t,{Z:()=>a});var r=n(7294),o=n(5742);function a(e){let{locale:t,version:n,tag:a}=e;const i=t;return r.createElement(o.Z,null,t&&r.createElement("meta",{name:"docusaurus_locale",content:t}),n&&r.createElement("meta",{name:"docusaurus_version",content:n}),a&&r.createElement("meta",{name:"docusaurus_tag",content:a}),i&&r.createElement("meta",{name:"docsearch:language",content:i}),n&&r.createElement("meta",{name:"docsearch:version",content:n}),a&&r.createElement("meta",{name:"docsearch:docusaurus_tag",content:a}))}},941:(e,t,n)=>{"use strict";n.d(t,{Z:()=>c});var r=n(7462),o=n(7294),a=n(6010),i=n(2389),l=n(2949);const s={themedImage:"themedImage_ToTc","themedImage--light":"themedImage--light_HNdA","themedImage--dark":"themedImage--dark_i4oU"};function c(e){const t=(0,i.Z)(),{colorMode:n}=(0,l.I)(),{sources:c,className:u,alt:d,...p}=e,f=t?"dark"===n?["dark"]:["light"]:["light","dark"];return o.createElement(o.Fragment,null,f.map((e=>o.createElement("img",(0,r.Z)({key:e,src:c[e],alt:d,className:(0,a.Z)(s.themedImage,s["themedImage--"+e],u)},p)))))}},6043:(e,t,n)=>{"use strict";n.d(t,{u:()=>i,z:()=>m});var r=n(7462),o=n(7294),a=n(412);function i(e){let{initialState:t}=e;const[n,r]=(0,o.useState)(null!=t&&t),a=(0,o.useCallback)((()=>{r((e=>!e))}),[]);return{collapsed:n,setCollapsed:r,toggleCollapsed:a}}const l={display:"none",overflow:"hidden",height:"0px"},s={display:"block",overflow:"visible",height:"auto"};function c(e,t){const n=t?l:s;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function u(e){let{collapsibleRef:t,collapsed:n,animation:r}=e;const a=(0,o.useRef)(!1);(0,o.useEffect)((()=>{const e=t.current;function o(){var t,n;const o=e.scrollHeight,a=null!=(t=null==r?void 0:r.duration)?t:function(e){const t=e/36;return Math.round(10*(4+15*t**.25+t/5))}(o);return{transition:"height "+a+"ms "+(null!=(n=null==r?void 0:r.easing)?n:"ease-in-out"),height:o+"px"}}function i(){const t=o();e.style.transition=t.transition,e.style.height=t.height}if(!a.current)return c(e,n),void(a.current=!0);return e.style.willChange="height",function(){const t=requestAnimationFrame((()=>{n?(i(),requestAnimationFrame((()=>{e.style.height=l.height,e.style.overflow=l.overflow}))):(e.style.display="block",requestAnimationFrame((()=>{i()})))}));return()=>cancelAnimationFrame(t)}()}),[t,n,r])}function d(e){if(!a.Z.canUseDOM)return e?l:s}function p(e){let{as:t="div",collapsed:n,children:r,animation:a,onCollapseTransitionEnd:i,className:l,disableSSRStyle:s}=e;const p=(0,o.useRef)(null);return u({collapsibleRef:p,collapsed:n,animation:a}),o.createElement(t,{ref:p,style:s?void 0:d(n),onTransitionEnd:e=>{"height"===e.propertyName&&(c(p.current,n),null==i||i(n))},className:l},r)}function f(e){let{collapsed:t,...n}=e;const[a,i]=(0,o.useState)(!t),[l,s]=(0,o.useState)(t);return(0,o.useLayoutEffect)((()=>{t||i(!0)}),[t]),(0,o.useLayoutEffect)((()=>{a&&s(t)}),[a,t]),a?o.createElement(p,(0,r.Z)({},n,{collapsed:l})):null}function m(e){let{lazy:t,...n}=e;const r=t?f:p;return o.createElement(r,n)}},9689:(e,t,n)=>{"use strict";n.d(t,{nT:()=>m,pl:()=>f});var r=n(7294),o=n(2389),a=n(12),i=n(902),l=n(6668);const s=(0,a.W)("docusaurus.announcement.dismiss"),c=(0,a.W)("docusaurus.announcement.id"),u=()=>"true"===s.get(),d=e=>s.set(String(e)),p=r.createContext(null);function f(e){let{children:t}=e;const n=function(){const{announcementBar:e}=(0,l.L)(),t=(0,o.Z)(),[n,a]=(0,r.useState)((()=>!!t&&u()));(0,r.useEffect)((()=>{a(u())}),[]);const i=(0,r.useCallback)((()=>{d(!0),a(!0)}),[]);return(0,r.useEffect)((()=>{if(!e)return;const{id:t}=e;let n=c.get();"annoucement-bar"===n&&(n="announcement-bar");const r=t!==n;c.set(t),r&&d(!1),!r&&u()||a(!1)}),[e]),(0,r.useMemo)((()=>({isActive:!!e&&!n,close:i})),[e,n,i])}();return r.createElement(p.Provider,{value:n},t)}function m(){const e=(0,r.useContext)(p);if(!e)throw new i.i6("AnnouncementBarProvider");return e}},2949:(e,t,n)=>{"use strict";n.d(t,{I:()=>g,S:()=>h});var r=n(7294),o=n(412),a=n(902),i=n(12),l=n(6668);const s=r.createContext(void 0),c="theme",u=(0,i.W)(c),d="light",p="dark",f=e=>e===p?p:d;function m(){const{colorMode:{defaultMode:e,disableSwitch:t,respectPrefersColorScheme:n}}=(0,l.L)(),[a,i]=(0,r.useState)((e=>o.Z.canUseDOM?f(document.documentElement.getAttribute("data-theme")):f(e))(e));(0,r.useEffect)((()=>{t&&u.del()}),[t]);const s=(0,r.useCallback)((function(t,r){void 0===r&&(r={});const{persist:o=!0}=r;t?(i(t),o&&(e=>{u.set(f(e))})(t)):(i(n?window.matchMedia("(prefers-color-scheme: dark)").matches?p:d:e),u.del())}),[n,e]);(0,r.useEffect)((()=>{document.documentElement.setAttribute("data-theme",f(a))}),[a]),(0,r.useEffect)((()=>{if(t)return;const e=e=>{if(e.key!==c)return;const t=u.get();null!==t&&s(f(t))};return window.addEventListener("storage",e),()=>window.removeEventListener("storage",e)}),[t,s]);const m=(0,r.useRef)(!1);return(0,r.useEffect)((()=>{if(t&&!n)return;const e=window.matchMedia("(prefers-color-scheme: dark)"),r=()=>{window.matchMedia("print").matches||m.current?m.current=window.matchMedia("print").matches:s(null)};return e.addListener(r),()=>e.removeListener(r)}),[s,t,n]),(0,r.useMemo)((()=>({colorMode:a,setColorMode:s,get isDarkTheme(){return a===p},setLightTheme(){s(d)},setDarkTheme(){s(p)}})),[a,s])}function h(e){let{children:t}=e;const n=m();return r.createElement(s.Provider,{value:n},t)}function g(){const e=(0,r.useContext)(s);if(null==e)throw new a.i6("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},373:(e,t,n)=>{"use strict";n.d(t,{J:()=>y,L5:()=>v,Oh:()=>w});var r=n(7294),o=n(4104),a=n(9935),i=n(6668),l=n(2802),s=n(902),c=n(12);const u=e=>"docs-preferred-version-"+e,d=(e,t,n)=>{(0,c.W)(u(e),{persistence:t}).set(n)},p=(e,t)=>(0,c.W)(u(e),{persistence:t}).get(),f=(e,t)=>{(0,c.W)(u(e),{persistence:t}).del()};const m=r.createContext(null);function h(){const e=(0,o._r)(),t=(0,i.L)().docs.versionPersistence,n=(0,r.useMemo)((()=>Object.keys(e)),[e]),[a,l]=(0,r.useState)((()=>(e=>Object.fromEntries(e.map((e=>[e,{preferredVersionName:null}]))))(n)));(0,r.useEffect)((()=>{l(function(e){let{pluginIds:t,versionPersistence:n,allDocsData:r}=e;function o(e){const t=p(e,n);return r[e].versions.some((e=>e.name===t))?{preferredVersionName:t}:(f(e,n),{preferredVersionName:null})}return Object.fromEntries(t.map((e=>[e,o(e)])))}({allDocsData:e,versionPersistence:t,pluginIds:n}))}),[e,t,n]);return[a,(0,r.useMemo)((()=>({savePreferredVersion:function(e,n){d(e,t,n),l((t=>({...t,[e]:{preferredVersionName:n}})))}})),[t])]}function g(e){let{children:t}=e;const n=h();return r.createElement(m.Provider,{value:n},t)}function v(e){let{children:t}=e;return l.cE?r.createElement(g,null,t):r.createElement(r.Fragment,null,t)}function b(){const e=(0,r.useContext)(m);if(!e)throw new s.i6("DocsPreferredVersionContextProvider");return e}function y(e){var t;void 0===e&&(e=a.m);const n=(0,o.zh)(e),[i,l]=b(),{preferredVersionName:s}=i[e];return{preferredVersion:null!=(t=n.versions.find((e=>e.name===s)))?t:null,savePreferredVersionName:(0,r.useCallback)((t=>{l.savePreferredVersion(e,t)}),[l,e])}}function w(){const e=(0,o._r)(),[t]=b();function n(n){var r;const o=e[n],{preferredVersionName:a}=t[n];return null!=(r=o.versions.find((e=>e.name===a)))?r:null}const r=Object.keys(e);return Object.fromEntries(r.map((e=>[e,n(e)])))}},1116:(e,t,n)=>{"use strict";n.d(t,{V:()=>s,b:()=>l});var r=n(7294),o=n(902);const a=Symbol("EmptyContext"),i=r.createContext(a);function l(e){let{children:t,name:n,items:o}=e;const a=(0,r.useMemo)((()=>n&&o?{name:n,items:o}:null),[n,o]);return r.createElement(i.Provider,{value:a},t)}function s(){const e=(0,r.useContext)(i);if(e===a)throw new o.i6("DocsSidebarProvider");return e}},2961:(e,t,n)=>{"use strict";n.d(t,{M:()=>p,e:()=>f});var r=n(7294),o=n(3102),a=n(7524),i=n(6775),l=n(902);function s(e){!function(e){const t=(0,i.k6)(),n=(0,l.zX)(e);(0,r.useEffect)((()=>t.block(((e,t)=>n(e,t)))),[t,n])}(((t,n)=>{if("POP"===n)return e(t,n)}))}var c=n(6668);const u=r.createContext(void 0);function d(){const e=function(){const e=(0,o.HY)(),{items:t}=(0,c.L)().navbar;return 0===t.length&&!e.component}(),t=(0,a.i)(),n=!e&&"mobile"===t,[i,l]=(0,r.useState)(!1);s((()=>{if(i)return l(!1),!1}));const u=(0,r.useCallback)((()=>{l((e=>!e))}),[]);return(0,r.useEffect)((()=>{"desktop"===t&&l(!1)}),[t]),(0,r.useMemo)((()=>({disabled:e,shouldRender:n,toggle:u,shown:i})),[e,n,u,i])}function p(e){let{children:t}=e;const n=d();return r.createElement(u.Provider,{value:n},t)}function f(){const e=r.useContext(u);if(void 0===e)throw new l.i6("NavbarMobileSidebarProvider");return e}},3102:(e,t,n)=>{"use strict";n.d(t,{HY:()=>l,Zo:()=>s,n2:()=>i});var r=n(7294),o=n(902);const a=r.createContext(null);function i(e){let{children:t}=e;const n=(0,r.useState)({component:null,props:null});return r.createElement(a.Provider,{value:n},t)}function l(){const e=(0,r.useContext)(a);if(!e)throw new o.i6("NavbarSecondaryMenuContentProvider");return e[0]}function s(e){let{component:t,props:n}=e;const i=(0,r.useContext)(a);if(!i)throw new o.i6("NavbarSecondaryMenuContentProvider");const[,l]=i,s=(0,o.Ql)(n);return(0,r.useEffect)((()=>{l({component:t,props:s})}),[l,t,s]),(0,r.useEffect)((()=>()=>l({component:null,props:null})),[l]),null}},9727:(e,t,n)=>{"use strict";n.d(t,{h:()=>o,t:()=>a});var r=n(7294);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)}}),[])}},7524:(e,t,n)=>{"use strict";n.d(t,{i:()=>c});var r=n(7294),o=n(412);const a="desktop",i="mobile",l="ssr";function s(){return o.Z.canUseDOM?window.innerWidth>996?a:i:l}function c(){const[e,t]=(0,r.useState)((()=>s()));return(0,r.useEffect)((()=>{function e(){t(s())}return window.addEventListener("resize",e),()=>{window.removeEventListener("resize",e),clearTimeout(undefined)}}),[]),e}},5281:(e,t,n)=>{"use strict";n.d(t,{k:()=>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",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:{}}},2802:(e,t,n)=>{"use strict";n.d(t,{Wl:()=>p,_F:()=>m,cE:()=>d,hI:()=>y,lO:()=>g,vY:()=>b,oz:()=>v,s1:()=>h});var r=n(7294),o=n(6775),a=n(8790),i=n(4104),l=n(373),s=n(1116);function c(e){return Array.from(new Set(e))}var u=n(8596);const d=!!i._r;function p(e){if(e.href)return e.href;for(const t of e.items){if("link"===t.type)return t.href;if("category"===t.type){const e=p(t);if(e)return e}}}const f=(e,t)=>void 0!==e&&(0,u.Mg)(e,t);function m(e,t){return"link"===e.type?f(e.href,t):"category"===e.type&&(f(e.href,t)||((e,t)=>e.some((e=>m(e,t))))(e.items,t))}function h(){var e;const t=(0,s.V)(),{pathname:n}=(0,o.TH)();if(!1===(null==(e=(0,i.gA)())?void 0:e.pluginData.breadcrumbs)||!t)return null;const r=[];return function e(t){for(const o of t)if("category"===o.type&&((0,u.Mg)(o.href,n)||e(o.items))||"link"===o.type&&(0,u.Mg)(o.href,n))return r.push(o),!0;return!1}(t.items),r.reverse()}function g(e){const{activeVersion:t}=(0,i.Iw)(e),{preferredVersion:n}=(0,l.J)(e),o=(0,i.yW)(e);return(0,r.useMemo)((()=>c([t,n,o].filter(Boolean))),[t,n,o])}function v(e,t){const n=g(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.sidebars?Object.entries(e.sidebars):[])),r=t.find((t=>t[0]===e));if(!r)throw new Error("Can't find any sidebar with id \""+e+'" in version'+(n.length>1?"s":"")+" "+n.map((e=>e.name)).join(", ")+'".\n Available sidebar ids are:\n - '+Object.keys(t).join("\n- "));return r[1]}),[e,n])}function b(e,t){const n=g(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.docs)),r=t.find((t=>t.id===e));if(!r){if(n.flatMap((e=>e.draftIds)).includes(e))return null;throw new Error("DocNavbarItem: couldn't find any doc with id \""+e+'" in version'+(n.length>1?"s":"")+" "+n.map((e=>e.name)).join(", ")+'".\nAvailable doc ids are:\n- '+c(t.map((e=>e.id))).join("\n- "))}return r}),[e,n])}function y(e){let{route:t,versionMetadata:n}=e;const r=(0,o.TH)(),i=t.routes,l=i.find((e=>(0,o.LX)(r.pathname,e)));if(!l)return null;const s=l.sidebar,c=s?n.docsSidebars[s]:void 0;return{docElement:(0,a.H)(i),sidebarName:s,sidebarItems:c}}},1944:(e,t,n)=>{"use strict";n.d(t,{FG:()=>p,d:()=>u,VC:()=>f});var r=n(7294),o=n(6010),a=n(5742),i=n(226);function l(){const e=r.useContext(i._);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}var s=n(4996),c=n(2263);function u(e){let{title:t,description:n,keywords:o,image:i,children:l}=e;const u=function(e){const{siteConfig:t}=(0,c.Z)(),{title:n,titleDelimiter:r}=t;return null!=e&&e.trim().length?e.trim()+" "+r+" "+n:n}(t),{withBaseUrl:d}=(0,s.C)(),p=i?d(i,{absolute:!0}):void 0;return r.createElement(a.Z,null,t&&r.createElement("title",null,u),t&&r.createElement("meta",{property:"og:title",content:u}),n&&r.createElement("meta",{name:"description",content:n}),n&&r.createElement("meta",{property:"og:description",content:n}),o&&r.createElement("meta",{name:"keywords",content:Array.isArray(o)?o.join(","):o}),p&&r.createElement("meta",{property:"og:image",content:p}),p&&r.createElement("meta",{name:"twitter:image",content:p}),l)}const d=r.createContext(void 0);function p(e){let{className:t,children:n}=e;const i=r.useContext(d),l=(0,o.Z)(i,t);return r.createElement(d.Provider,{value:l},r.createElement(a.Z,null,r.createElement("html",{className:l})),n)}function f(e){let{children:t}=e;const n=l(),a="plugin-"+n.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,"");const i="plugin-id-"+n.plugin.id;return r.createElement(p,{className:(0,o.Z)(a,i)},t)}},902:(e,t,n)=>{"use strict";n.d(t,{D9:()=>i,Qc:()=>c,Ql:()=>s,i6:()=>l,zX:()=>a});var r=n(7294);const o=n(412).Z.canUseDOM?r.useLayoutEffect:r.useEffect;function a(e){const t=(0,r.useRef)(e);return o((()=>{t.current=e}),[e]),(0,r.useCallback)((function(){return t.current(...arguments)}),[])}function i(e){const t=(0,r.useRef)();return o((()=>{t.current=e})),t.current}class l extends Error{constructor(e,t){var n,r,o,a;super(),this.name="ReactContextError",this.message="Hook "+(null!=(n=null==(r=this.stack)||null==(o=r.split("\n")[1])||null==(a=o.match(/at (?:\w+\.)?(?\w+)/))?void 0:a.groups.name)?n:"")+" is called outside the <"+e+">. "+(null!=t?t:"")}}function s(e){const t=Object.entries(e);return t.sort(((e,t)=>e[0].localeCompare(t[0]))),(0,r.useMemo)((()=>e),t.flat())}function c(e){return t=>{let{children:n}=t;return r.createElement(r.Fragment,null,e.reduceRight(((e,t)=>r.createElement(t,null,e)),n))}}},8596:(e,t,n)=>{"use strict";n.d(t,{Mg:()=>i,Ns:()=>l});var r=n(7294),o=n(723),a=n(2263);function i(e,t){const n=e=>{var t;return null==(t=!e||e.endsWith("/")?e:e+"/")?void 0:t.toLowerCase()};return n(e)===n(t)}function l(){const{baseUrl:e}=(0,a.Z)().siteConfig;return(0,r.useMemo)((()=>function(e){let{baseUrl:t,routes:n}=e;function r(e){return e.path===t&&!0===e.exact}function o(e){return e.path===t&&!e.exact}return function e(t){if(0===t.length)return;return t.find(r)||e(t.filter(o).flatMap((e=>{var t;return null!=(t=e.routes)?t:[]})))}(n)}({routes:o.Z,baseUrl:e})),[e])}},2466:(e,t,n)=>{"use strict";n.d(t,{Ct:()=>p,OC:()=>s,RF:()=>d});var r=n(7294),o=n(412),a=n(2389),i=n(902);const l=r.createContext(void 0);function s(e){let{children:t}=e;const n=function(){const e=(0,r.useRef)(!0);return(0,r.useMemo)((()=>({scrollEventsEnabledRef:e,enableScrollEvents:()=>{e.current=!0},disableScrollEvents:()=>{e.current=!1}})),[])}();return r.createElement(l.Provider,{value:n},t)}function c(){const e=(0,r.useContext)(l);if(null==e)throw new i.i6("ScrollControllerProvider");return e}const u=()=>o.Z.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null;function d(e,t){void 0===t&&(t=[]);const{scrollEventsEnabledRef:n}=c(),o=(0,r.useRef)(u()),a=(0,i.zX)(e);(0,r.useEffect)((()=>{const e=()=>{if(!n.current)return;const e=u();a(e,o.current),o.current=e},t={passive:!0};return e(),window.addEventListener("scroll",e,t),()=>window.removeEventListener("scroll",e,t)}),[a,n,...t])}function p(){const e=(0,r.useRef)(null),t=(0,a.Z)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:n=>{e.current=t?function(e){return window.scrollTo({top:e,behavior:"smooth"}),()=>{}}(n):function(e){let t=null;const n=document.documentElement.scrollTop>e;return function r(){const o=document.documentElement.scrollTop;(n&&o>e||!n&&ot&&cancelAnimationFrame(t)}(n)},cancelScroll:()=>null==e.current?void 0:e.current()}}},3320:(e,t,n)=>{"use strict";n.d(t,{HX:()=>i,_q:()=>s,os:()=>l});var r=n(4104),o=n(2263),a=n(373);const i="default";function l(e,t){return"docs-"+e+"-"+t}function s(){const{i18n:e}=(0,o.Z)(),t=(0,r._r)(),n=(0,r.WS)(),s=(0,a.Oh)();const c=[i,...Object.keys(t).map((function(e){var r;const o=(null==n?void 0:n.activePlugin.pluginId)===e?n.activeVersion:void 0,a=s[e],i=t[e].versions.find((e=>e.isLast));return l(e,(null!=(r=null!=o?o:a)?r:i).name)}))];return{locale:e.currentLocale,tags:c}}},12:(e,t,n)=>{"use strict";n.d(t,{W:()=>l,_:()=>s});const r="localStorage";function o(e){if(void 0===e&&(e=r),"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(n){return t=n,a||(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.",t),a=!0),null}var t}let a=!1;const i={get:()=>null,set:()=>{},del:()=>{}};function l(e,t){if("undefined"==typeof window)return function(e){function t(){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:t,set:t,del:t}}(e);const n=o(null==t?void 0:t.persistence);return null===n?i:{get:()=>{try{return n.getItem(e)}catch(t){return console.error("Docusaurus storage error, can't get key="+e,t),null}},set:t=>{try{n.setItem(e,t)}catch(r){console.error("Docusaurus storage error, can't set "+e+"="+t,r)}},del:()=>{try{n.removeItem(e)}catch(t){console.error("Docusaurus storage error, can't delete key="+e,t)}}}}function s(e){void 0===e&&(e=r);const t=o(e);if(!t)return[];const n=[];for(let r=0;r{"use strict";n.d(t,{l:()=>a});var r=n(2263),o=n(6775);function a(){const{siteConfig:{baseUrl:e,url:t},i18n:{defaultLocale:n,currentLocale:a}}=(0,r.Z)(),{pathname:i}=(0,o.TH)(),l=a===n?e:e.replace("/"+a+"/","/"),s=i.replace(e,"");return{createUrl:function(e){let{locale:r,fullyQualified:o}=e;return""+(o?t:"")+function(e){return e===n?""+l:""+l+e+"/"}(r)+s}}}},5936:(e,t,n)=>{"use strict";n.d(t,{S:()=>i});var r=n(7294),o=n(6775),a=n(902);function i(e){const t=(0,o.TH)(),n=(0,a.D9)(t),i=(0,a.zX)(e);(0,r.useEffect)((()=>{n&&t!==n&&i({location:t,previousLocation:n})}),[i,t,n])}},6668:(e,t,n)=>{"use strict";n.d(t,{L:()=>o});var r=n(2263);function o(){return(0,r.Z)().siteConfig.themeConfig}},8802:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const{trailingSlash:n,baseUrl:r}=t;if(e.startsWith("#"))return e;if(void 0===n)return e;const[o]=e.split(/[#?]/),a="/"===o||o===r?o:(i=o,n?function(e){return e.endsWith("/")?e:e+"/"}(i):function(e){return e.endsWith("/")?e.slice(0,-1):e}(i));var i;return e.replace(o,a)}},8780:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.applyTrailingSlash=t.blogPostContainerID=void 0,t.blogPostContainerID="post-content";var o=n(8802);Object.defineProperty(t,"applyTrailingSlash",{enumerable:!0,get:function(){return r(o).default}})},6010:(e,t,n)=>{"use strict";function r(e){var t,n,o="";if("string"==typeof e||"number"==typeof e)o+=e;else if("object"==typeof e)if(Array.isArray(e))for(t=0;to});const o=function(){for(var e,t,n=0,o="";n{"use strict";n.d(t,{lX:()=>w,q_:()=>O,ob:()=>f,PP:()=>P,Ep:()=>p});var r=n(7462);function o(e){return"/"===e.charAt(0)}function a(e,t){for(var n=t,r=n+1,o=e.length;r=0;p--){var f=i[p];"."===f?a(i,p):".."===f?(a(i,p),d++):d&&(a(i,p),d--)}if(!c)for(;d--;d)i.unshift("..");!c||""===i[0]||i[0]&&o(i[0])||i.unshift("");var m=i.join("/");return n&&"/"!==m.substr(-1)&&(m+="/"),m};var l=n(2177);function s(e){return"/"===e.charAt(0)?e:"/"+e}function c(e){return"/"===e.charAt(0)?e.substr(1):e}function u(e,t){return function(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))}(e,t)?e.substr(t.length):e}function d(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function p(e){var t=e.pathname,n=e.search,r=e.hash,o=t||"/";return n&&"?"!==n&&(o+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(o+="#"===r.charAt(0)?r:"#"+r),o}function f(e,t,n,o){var a;"string"==typeof e?(a=function(e){var t=e||"/",n="",r="",o=t.indexOf("#");-1!==o&&(r=t.substr(o),t=t.substr(0,o));var a=t.indexOf("?");return-1!==a&&(n=t.substr(a),t=t.substr(0,a)),{pathname:t,search:"?"===n?"":n,hash:"#"===r?"":r}}(e),a.state=t):(void 0===(a=(0,r.Z)({},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!==t&&void 0===a.state&&(a.state=t));try{a.pathname=decodeURI(a.pathname)}catch(l){throw l instanceof URIError?new URIError('Pathname "'+a.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):l}return n&&(a.key=n),o?a.pathname?"/"!==a.pathname.charAt(0)&&(a.pathname=i(a.pathname,o.pathname)):a.pathname=o.pathname:a.pathname||(a.pathname="/"),a}function m(){var e=null;var t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,r,o){if(null!=e){var a="function"==typeof e?e(t,n):e;"string"==typeof a?"function"==typeof r?r(a,o):o(!0):o(!1!==a)}else o(!0)},appendListener:function(e){var n=!0;function r(){n&&e.apply(void 0,arguments)}return t.push(r),function(){n=!1,t=t.filter((function(e){return e!==r}))}},notifyListeners:function(){for(var e=arguments.length,n=new Array(e),r=0;rt?n.splice(t,n.length-t,o):n.push(o),d({action:r,location:o,index:t,entries:n})}}))},replace:function(e,t){var r="REPLACE",o=f(e,t,h(),w.location);u.confirmTransitionTo(o,r,n,(function(e){e&&(w.entries[w.index]=o,d({action:r,location:o}))}))},go:y,goBack:function(){y(-1)},goForward:function(){y(1)},canGo:function(e){var t=w.index+e;return t>=0&&t{"use strict";var r=n(9864),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},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},l={};function s(e){return r.isMemo(e)?i:l[e.$$typeof]||o}l[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},l[r.Memo]=i;var c=Object.defineProperty,u=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,p=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,m=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(m){var o=f(n);o&&o!==m&&e(t,o,r)}var i=u(n);d&&(i=i.concat(d(n)));for(var l=s(t),h=s(n),g=0;g{"use strict";e.exports=function(e,t,n,r,o,a,i,l){if(!e){var s;if(void 0===t)s=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,o,a,i,l],u=0;(s=new Error(t.replace(/%s/g,(function(){return c[u++]})))).name="Invariant Violation"}throw s.framesToPop=1,s}}},5826:e=>{e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},813:function(e){e.exports=function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1])||arguments[1],o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=o,this.iframesTimeout=a}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach((function(t){var n=e.filter((function(e){return e.contains(t)})).length>0;-1!==e.indexOf(t)||n||e.push(t)})),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var o=e.contentWindow;if(r=o.document,!o||!r)throw new Error("iframe inaccessible")}catch(a){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,o=!1,a=null,i=function i(){if(!o){o=!0,clearTimeout(a);try{r.isIframeBlank(e)||(e.removeEventListener("load",i),r.getIframeContents(e,t,n))}catch(l){n()}}};e.addEventListener("load",i),a=setTimeout(i,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(r){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,(function(){return!0}),(function(e){r++,n.waitForIframes(e.querySelector("html"),(function(){--r||t()}))}),(function(e){e||t()}))}},{key:"forEachIframe",value:function(t,n,r){var o=this,a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},i=t.querySelectorAll("iframe"),l=i.length,s=0;i=Array.prototype.slice.call(i);var c=function(){--l<=0&&a(s)};l||c(),i.forEach((function(t){e.matches(t,o.exclude)?c():o.onIframeReady(t,(function(e){n(t)&&(s++,r(e)),c()}),c)}))}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:(null===t||e.nextNode())&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var o=!1,a=!1;return r.forEach((function(e,t){e.val===n&&(o=t,a=e.handled)})),this.compareNodeIframe(e,t,n)?(!1!==o||a?!1===o||a||(r[o].handled=!0):r.push({val:n,handled:!0}),!0):(!1===o&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var o=this;e.forEach((function(e){e.handled||o.getIframeContents(e.val,(function(e){o.createInstanceOnIframe(e).forEachNode(t,n,r)}))}))}},{key:"iterateThroughNodes",value:function(e,t,n,r,o){for(var a=this,i=this.createIterator(t,e,r),l=[],s=[],c=void 0,u=void 0,d=function(){var e=a.getIteratorNode(i);return u=e.prevNode,c=e.node};d();)this.iframes&&this.forEachIframe(t,(function(e){return a.checkIframeFilter(c,u,e,l)}),(function(t){a.createInstanceOnIframe(t).forEachNode(e,(function(e){return s.push(e)}),r)})),s.push(c);s.forEach((function(e){n(e)})),this.iframes&&this.handleOpenIframes(l,e,n,r),o()}},{key:"forEachNode",value:function(e,t,n){var r=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},a=this.getContexts(),i=a.length;i||o(),a.forEach((function(a){var l=function(){r.iterateThroughNodes(e,a,t,n,(function(){--i<=0&&o()}))};r.iframes?r.waitForIframes(a,l):l()}))}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var o=!1;return n.every((function(t){return!r.call(e,t)||(o=!0,!1)})),o}return!1}}]),e}(),a=function(){function a(e){t(this,a),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(a,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createRegExp",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),e=this.createAccuracyRegExp(e)}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var o in t)if(t.hasOwnProperty(o)){var a=t[o],i="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o),l="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(a):this.escapeStr(a);""!==i&&""!==l&&(e=e.replace(new RegExp("("+this.escapeStr(i)+"|"+this.escapeStr(l)+")","gm"+n),r+"("+this.processSynomyms(i)+"|"+this.processSynomyms(l)+")"+r))}return e}},{key:"processSynomyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,(function(e){return"\\"===e.charAt(0)?"?":"\x01"}))).replace(/(?:\\)*\*/g,(function(e){return"\\"===e.charAt(0)?"*":"\x02"}))}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,(function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"}))}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["a\xe0\xe1\u1ea3\xe3\u1ea1\u0103\u1eb1\u1eaf\u1eb3\u1eb5\u1eb7\xe2\u1ea7\u1ea5\u1ea9\u1eab\u1ead\xe4\xe5\u0101\u0105","A\xc0\xc1\u1ea2\xc3\u1ea0\u0102\u1eb0\u1eae\u1eb2\u1eb4\u1eb6\xc2\u1ea6\u1ea4\u1ea8\u1eaa\u1eac\xc4\xc5\u0100\u0104","c\xe7\u0107\u010d","C\xc7\u0106\u010c","d\u0111\u010f","D\u0110\u010e","e\xe8\xe9\u1ebb\u1ebd\u1eb9\xea\u1ec1\u1ebf\u1ec3\u1ec5\u1ec7\xeb\u011b\u0113\u0119","E\xc8\xc9\u1eba\u1ebc\u1eb8\xca\u1ec0\u1ebe\u1ec2\u1ec4\u1ec6\xcb\u011a\u0112\u0118","i\xec\xed\u1ec9\u0129\u1ecb\xee\xef\u012b","I\xcc\xcd\u1ec8\u0128\u1eca\xce\xcf\u012a","l\u0142","L\u0141","n\xf1\u0148\u0144","N\xd1\u0147\u0143","o\xf2\xf3\u1ecf\xf5\u1ecd\xf4\u1ed3\u1ed1\u1ed5\u1ed7\u1ed9\u01a1\u1edf\u1ee1\u1edb\u1edd\u1ee3\xf6\xf8\u014d","O\xd2\xd3\u1ece\xd5\u1ecc\xd4\u1ed2\u1ed0\u1ed4\u1ed6\u1ed8\u01a0\u1ede\u1ee0\u1eda\u1edc\u1ee2\xd6\xd8\u014c","r\u0159","R\u0158","s\u0161\u015b\u0219\u015f","S\u0160\u015a\u0218\u015e","t\u0165\u021b\u0163","T\u0164\u021a\u0162","u\xf9\xfa\u1ee7\u0169\u1ee5\u01b0\u1eeb\u1ee9\u1eed\u1eef\u1ef1\xfb\xfc\u016f\u016b","U\xd9\xda\u1ee6\u0168\u1ee4\u01af\u1eea\u1ee8\u1eec\u1eee\u1ef0\xdb\xdc\u016e\u016a","y\xfd\u1ef3\u1ef7\u1ef9\u1ef5\xff","Y\xdd\u1ef2\u1ef6\u1ef8\u1ef4\u0178","z\u017e\u017c\u017a","Z\u017d\u017b\u0179"]:["a\xe0\xe1\u1ea3\xe3\u1ea1\u0103\u1eb1\u1eaf\u1eb3\u1eb5\u1eb7\xe2\u1ea7\u1ea5\u1ea9\u1eab\u1ead\xe4\xe5\u0101\u0105A\xc0\xc1\u1ea2\xc3\u1ea0\u0102\u1eb0\u1eae\u1eb2\u1eb4\u1eb6\xc2\u1ea6\u1ea4\u1ea8\u1eaa\u1eac\xc4\xc5\u0100\u0104","c\xe7\u0107\u010dC\xc7\u0106\u010c","d\u0111\u010fD\u0110\u010e","e\xe8\xe9\u1ebb\u1ebd\u1eb9\xea\u1ec1\u1ebf\u1ec3\u1ec5\u1ec7\xeb\u011b\u0113\u0119E\xc8\xc9\u1eba\u1ebc\u1eb8\xca\u1ec0\u1ebe\u1ec2\u1ec4\u1ec6\xcb\u011a\u0112\u0118","i\xec\xed\u1ec9\u0129\u1ecb\xee\xef\u012bI\xcc\xcd\u1ec8\u0128\u1eca\xce\xcf\u012a","l\u0142L\u0141","n\xf1\u0148\u0144N\xd1\u0147\u0143","o\xf2\xf3\u1ecf\xf5\u1ecd\xf4\u1ed3\u1ed1\u1ed5\u1ed7\u1ed9\u01a1\u1edf\u1ee1\u1edb\u1edd\u1ee3\xf6\xf8\u014dO\xd2\xd3\u1ece\xd5\u1ecc\xd4\u1ed2\u1ed0\u1ed4\u1ed6\u1ed8\u01a0\u1ede\u1ee0\u1eda\u1edc\u1ee2\xd6\xd8\u014c","r\u0159R\u0158","s\u0161\u015b\u0219\u015fS\u0160\u015a\u0218\u015e","t\u0165\u021b\u0163T\u0164\u021a\u0162","u\xf9\xfa\u1ee7\u0169\u1ee5\u01b0\u1eeb\u1ee9\u1eed\u1eef\u1ef1\xfb\xfc\u016f\u016bU\xd9\xda\u1ee6\u0168\u1ee4\u01af\u1eea\u1ee8\u1eec\u1eee\u1ef0\xdb\xdc\u016e\u016a","y\xfd\u1ef3\u1ef7\u1ef9\u1ef5\xffY\xdd\u1ef2\u1ef6\u1ef8\u1ef4\u0178","z\u017e\u017c\u017aZ\u017d\u017b\u0179"],r=[];return e.split("").forEach((function(o){n.every((function(n){if(-1!==n.indexOf(o)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0}))})),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n="!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\xa1\xbf",r=this.opt.accuracy,o="string"==typeof r?r:r.value,a="string"==typeof r?[]:r.limiters,i="";switch(a.forEach((function(e){i+="|"+t.escapeStr(e)})),o){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(i="\\s"+(i||this.escapeStr(n)))+"]*"+e+"[^"+i+"]*)";case"exactly":return"(^|\\s"+i+")("+e+")(?=$|\\s"+i+")"}}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach((function(e){t.opt.separateWordSearch?e.split(" ").forEach((function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)})):e.trim()&&-1===n.indexOf(e)&&n.push(e)})),{keywords:n.sort((function(e,t){return t.length-e.length})),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort((function(e,t){return e.start-t.start})).forEach((function(e){var o=t.callNoMatchOnInvalidRanges(e,r),a=o.start,i=o.end;o.valid&&(e.start=a,e.length=i-a,n.push(e),r=i)})),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,o=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?o=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:o}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,o=!0,a=n.length,i=t-a,l=parseInt(e.start,10)-i;return(r=(l=l>a?a:l)+parseInt(e.length,10))>a&&(r=a,this.log("End range automatically set to the max value of "+a)),l<0||r-l<0||l>a||r>a?(o=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(l,r).replace(/\s+/g,"")&&(o=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:l,end:r,valid:o}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,(function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})}),(function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}),(function(){e({value:n,nodes:r})}))}},{key:"matchesExclude",value:function(e){return o.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",o=e.splitText(t),a=o.splitText(n-t),i=document.createElement(r);return i.setAttribute("data-markjs","true"),this.opt.className&&i.setAttribute("class",this.opt.className),i.textContent=o.textContent,o.parentNode.replaceChild(i,o),a}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,o){var a=this;e.nodes.every((function(i,l){var s=e.nodes[l+1];if(void 0===s||s.start>t){if(!r(i.node))return!1;var c=t-i.start,u=(n>i.end?i.end:n)-i.start,d=e.value.substr(0,i.start),p=e.value.substr(u+i.start);if(i.node=a.wrapRangeInTextNode(i.node,c,u),e.value=d+p,e.nodes.forEach((function(t,n){n>=l&&(e.nodes[n].start>0&&n!==l&&(e.nodes[n].start-=u),e.nodes[n].end-=u)})),n-=u,o(i.node.previousSibling,i.start),!(n>i.end))return!1;t=i.end}return!0}))}},{key:"wrapMatches",value:function(e,t,n,r,o){var a=this,i=0===t?0:t+1;this.getTextNodes((function(t){t.nodes.forEach((function(t){t=t.node;for(var o=void 0;null!==(o=e.exec(t.textContent))&&""!==o[i];)if(n(o[i],t)){var l=o.index;if(0!==i)for(var s=1;s{"use strict";n.r(t)},2295:(e,t,n)=>{"use strict";n.r(t)},4865:function(e,t,n){var r,o;r=function(){var e,t,n={version:"0.2.0"},r=n.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,t,n){return en?n:e}function a(e){return 100*(-1+e)}function i(e,t,n){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 "+t+"ms "+n,o}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(r[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=o(e,r.minimum,1),n.status=1===e?null:e;var a=n.render(!t),c=a.querySelector(r.barSelector),u=r.speed,d=r.easing;return a.offsetWidth,l((function(t){""===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),s(c,i(e,u,d)),1===e?(s(a,{transition:"none",opacity:1}),a.offsetWidth,setTimeout((function(){s(a,{transition:"all "+u+"ms linear",opacity:0}),setTimeout((function(){n.remove(),t()}),u)}),u)):setTimeout(t,u)})),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout((function(){n.status&&(n.trickle(),e())}),r.trickleSpeed)};return r.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*o(Math.random()*t,.1,.95)),t=o(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},e=0,t=0,n.promise=function(r){return r&&"resolved"!==r.state()?(0===t&&n.start(),e++,t++,r.always((function(){0==--t?(e=0,n.done()):n.set((e-t)/e)})),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");u(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=r.template;var o,i=t.querySelector(r.barSelector),l=e?"-100":a(n.status||0),c=document.querySelector(r.parent);return s(i,{transition:"all 0 linear",transform:"translate3d("+l+"%,0,0)"}),r.showSpinner||(o=t.querySelector(r.spinnerSelector))&&f(o),c!=document.body&&u(c,"nprogress-custom-parent"),c.appendChild(t),t},n.remove=function(){d(document.documentElement,"nprogress-busy"),d(document.querySelector(r.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&f(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var l=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),s=function(){var e=["Webkit","O","Moz","ms"],t={};function n(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,(function(e,t){return t.toUpperCase()}))}function r(t){var n=document.body.style;if(t in n)return t;for(var r,o=e.length,a=t.charAt(0).toUpperCase()+t.slice(1);o--;)if((r=e[o]+a)in n)return r;return t}function o(e){return e=n(e),t[e]||(t[e]=r(e))}function a(e,t,n){t=o(t),e.style[t]=n}return function(e,t){var n,r,o=arguments;if(2==o.length)for(n in t)void 0!==(r=t[n])&&t.hasOwnProperty(n)&&a(e,n,r);else a(e,o[1],o[2])}}();function c(e,t){return("string"==typeof e?e:p(e)).indexOf(" "+t+" ")>=0}function u(e,t){var n=p(e),r=n+t;c(n,t)||(e.className=r.substring(1))}function d(e,t){var n,r=p(e);c(e,t)&&(n=r.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function p(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function f(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n},void 0===(o="function"==typeof r?r.call(t,n,t,e):r)||(e.exports=o)},7418:e=>{"use strict";var t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;function o(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(o){return!1}}()?Object.assign:function(e,a){for(var i,l,s=o(e),c=1;c{"use strict";n.d(t,{Z:()=>a});var r=function(){var e=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,n={},r={util:{encode:function e(t){return t instanceof o?new o(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=d.reach);E+=k.value.length,k=k.next){var _=k.value;if(t.length>e.length)return;if(!(_ instanceof o)){var x,O=1;if(b){if(!(x=a(S,E,e,v))||x.index>=e.length)break;var T=x.index,P=x.index+x[0].length,C=E;for(C+=k.value.length;T>=C;)C+=(k=k.next).value.length;if(E=C-=k.value.length,k.value instanceof o)continue;for(var I=k;I!==t.tail&&(Cd.reach&&(d.reach=N);var j=k.prev;if(R&&(j=s(t,j,R),E+=R.length),c(t,j,O),k=s(t,j,new o(p,g?r.tokenize(A,g):A,y,A)),L&&s(t,k,L),O>1){var D={cause:p+","+m,reach:N};i(e,t,n,k.prev,E,D),d&&D.reach>d.reach&&(d.reach=D.reach)}}}}}}function l(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function s(e,t,n){var r=t.next,o={value:n,prev:t,next:r};return t.next=o,r.prev=o,e.length++,o}function c(e,t,n){for(var r=t.next,o=0;o"+a.content+""},r}(),o=r;r.default=r,o.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},o.languages.markup.tag.inside["attr-value"].inside.entity=o.languages.markup.entity,o.languages.markup.doctype.inside["internal-subset"].inside=o.languages.markup,o.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(o.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:o.languages[t]},n.cdata=/^$/i;var r={"included-cdata":{pattern://i,inside:n}};r["language-"+t]={pattern:/[\s\S]+/,inside:o.languages[t]};var a={};a[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:r},o.languages.insertBefore("markup","cdata",a)}}),Object.defineProperty(o.languages.markup.tag,"addAttribute",{value:function(e,t){o.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:o.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),o.languages.html=o.languages.markup,o.languages.mathml=o.languages.markup,o.languages.svg=o.languages.markup,o.languages.xml=o.languages.extend("markup",{}),o.languages.ssml=o.languages.xml,o.languages.atom=o.languages.xml,o.languages.rss=o.languages.xml,function(e){var t="\\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",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},r={bash:n,environment:{pattern:RegExp("\\$"+t),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("(\\{)"+t),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+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},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:n}},{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("\\$?"+t),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|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|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|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}},n.inside=e.languages.bash;for(var o=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],a=r.variable[1].inside,i=0;i]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},o.languages.c=o.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),o.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),o.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},o.languages.c.string],char:o.languages.c.char,comment:o.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:o.languages.c}}}}),o.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete o.languages.c.boolean,function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n=/\b(?!)\w+(?:\s*\.\s*\w+)*\b/.source.replace(//g,(function(){return t.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!)\w+/.source.replace(//g,(function(){return t.source}))),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/(?:\s*:\s*)?|:\s*/.source.replace(//g,(function(){return n}))+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(o),function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),n.tag.addAttribute("style","css"))}(o),function(e){var t,n=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;e.languages.css.selector={pattern:e.languages.css.selector.pattern,lookbehind:!0,inside:t={"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+/,class:/\.[-\w]+/,id:/#[-\w]+/,attribute:{pattern:RegExp("\\[(?:[^[\\]\"']|"+n.source+")*\\]"),greedy:!0,inside:{punctuation:/^\[|\]$/,"case-sensitivity":{pattern:/(\s)[si]$/i,lookbehind:!0,alias:"keyword"},namespace:{pattern:/^(\s*)(?:(?!\s)[-*\w\xA0-\uFFFF])*\|(?!=)/,lookbehind:!0,inside:{punctuation:/\|$/}},"attr-name":{pattern:/^(\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+/,lookbehind:!0},"attr-value":[n,{pattern:/(=\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+(?=\s*$)/,lookbehind:!0}],operator:/[|~*^$]?=/}},"n-th":[{pattern:/(\(\s*)[+-]?\d*[\dn](?:\s*[+-]\s*\d+)?(?=\s*\))/,lookbehind:!0,inside:{number:/[\dn]+/,operator:/[+-]/}},{pattern:/(\(\s*)(?:even|odd)(?=\s*\))/i,lookbehind:!0}],combinator:/>|\+|~|\|\|/,punctuation:/[(),]/}},e.languages.css.atrule.inside["selector-function-argument"].inside=t,e.languages.insertBefore("css","property",{variable:{pattern:/(^|[^-\w\xA0-\uFFFF])--(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*/i,lookbehind:!0}});var r={pattern:/(\b\d+)(?:%|[a-z]+(?![\w-]))/,lookbehind:!0},o={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0};e.languages.insertBefore("css","function",{operator:{pattern:/(\s)[+\-*\/](?=\s)/,lookbehind:!0},hexcode:{pattern:/\B#[\da-f]{3,8}\b/i,alias:"color"},color:[{pattern:/(^|[^\w-])(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)(?![\w-])/i,lookbehind:!0},{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:r,number:o,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:r,number:o})}(o),o.languages.javascript=o.languages.extend("clike",{"class-name":[o.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),o.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,o.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:o.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:o.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:o.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:o.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:o.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),o.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:o.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),o.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),o.languages.markup&&(o.languages.markup.tag.addInlined("script","javascript"),o.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),o.languages.js=o.languages.javascript,function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:e.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(o),function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+n.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+n.source+")?)",o=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*/.source.replace(//g,(function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source})),a=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function i(e,t){t=(t||"").replace(/m/g,"")+"m";var n=/([:\-,[{]\s*(?:\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<>/g,(function(){return r})).replace(/<>/g,(function(){return e}));return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<>/g,(function(){return r}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\s*:\s)/.source.replace(/<>/g,(function(){return r})).replace(/<>/g,(function(){return"(?:"+o+"|"+a+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:i(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:i(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:i(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:i(a),lookbehind:!0,greedy:!0},number:{pattern:i(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(o),function(e){var t=/(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;function n(e){return e=e.replace(//g,(function(){return t})),RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+"(?:"+e+")")}var r=/(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source,o=/\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g,(function(){return r})),a=/\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source;e.languages.markdown=e.languages.extend("markup",{}),e.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:e.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+o+a+"(?:"+o+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+o+a+")(?:"+o+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(r),inside:e.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+o+")"+a+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+o+"$"),inside:{"table-header":{pattern:RegExp(r),alias:"important",inside:e.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n(/\b__(?:(?!_)|_(?:(?!_))+_)+__\b|\*\*(?:(?!\*)|\*(?:(?!\*))+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n(/\b_(?:(?!_)|__(?:(?!_))+__)+_\b|\*(?:(?!\*)|\*\*(?:(?!\*))+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n(/(~~?)(?:(?!~))+\2/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:n(/!?\[(?:(?!\]))+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\]))+\])/.source),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach((function(t){["url","bold","italic","strike","code-snippet"].forEach((function(n){t!==n&&(e.languages.markdown[t].inside.content.inside[n]=e.languages.markdown[n])}))})),e.hooks.add("after-tokenize",(function(e){"markdown"!==e.language&&"md"!==e.language||function e(t){if(t&&"string"!=typeof t)for(var n=0,r=t.length;n",quot:'"'},s=String.fromCodePoint||String.fromCharCode;e.languages.md=e.languages.markdown}(o),o.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:o.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:false|true)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/\b[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"atom-input":{pattern:/\b[A-Z]\w*Input\b/,alias:"class-name"},scalar:/\b(?:Boolean|Float|ID|Int|String)\b/,constant:/\b[A-Z][A-Z_\d]*\b/,"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-mutation":{pattern:/(\bmutation\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-query":{pattern:/(\bquery\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,"property-query":/\w+(?=\s*\()/,object:/\w+(?=\s*\{)/,punctuation:/[!(){}\[\]:=,]/,property:/\w+/},o.hooks.add("after-tokenize",(function(e){if("graphql"===e.language)for(var t=e.tokens.filter((function(e){return"string"!=typeof e&&"comment"!==e.type&&"scalar"!==e.type})),n=0;n0)){var l=p(/^\{$/,/^\}$/);if(-1===l)continue;for(var s=n;s=0&&f(c,"variable-input")}}}}function u(e){return t[n+e]}function d(e,t){t=t||0;for(var n=0;n?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/},function(e){var t=e.languages.javascript["template-string"],n=t.pattern.source,r=t.inside.interpolation,o=r.inside["interpolation-punctuation"],a=r.pattern.source;function i(t,r){if(e.languages[t])return{pattern:RegExp("((?:"+r+")\\s*)"+n),lookbehind:!0,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},"embedded-code":{pattern:/[\s\S]+/,alias:t}}}}function l(e,t){return"___"+t.toUpperCase()+"_"+e+"___"}function s(t,n,r){var o={code:t,grammar:n,language:r};return e.hooks.run("before-tokenize",o),o.tokens=e.tokenize(o.code,o.grammar),e.hooks.run("after-tokenize",o),o.tokens}function c(t){var n={};n["interpolation-punctuation"]=o;var a=e.tokenize(t,n);if(3===a.length){var i=[1,1];i.push.apply(i,s(a[1],e.languages.javascript,"javascript")),a.splice.apply(a,i)}return new e.Token("interpolation",a,r.alias,t)}function u(t,n,r){var o=e.tokenize(t,{interpolation:{pattern:RegExp(a),lookbehind:!0}}),i=0,u={},d=s(o.map((function(e){if("string"==typeof e)return e;for(var n,o=e.content;-1!==t.indexOf(n=l(i++,r)););return u[n]=o,n})).join(""),n,r),p=Object.keys(u);return i=0,function e(t){for(var n=0;n=p.length)return;var r=t[n];if("string"==typeof r||"string"==typeof r.content){var o=p[i],a="string"==typeof r?r:r.content,l=a.indexOf(o);if(-1!==l){++i;var s=a.substring(0,l),d=c(u[o]),f=a.substring(l+o.length),m=[];if(s&&m.push(s),m.push(d),f){var h=[f];e(h),m.push.apply(m,h)}"string"==typeof r?(t.splice.apply(t,[n,1].concat(m)),n+=m.length-1):r.content=m}}else{var g=r.content;Array.isArray(g)?e(g):e([g])}}}(d),new e.Token(r,d,"language-"+r,t)}e.languages.javascript["template-string"]=[i("css",/\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/.source),i("html",/\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source),i("svg",/\bsvg/.source),i("markdown",/\b(?:markdown|md)/.source),i("graphql",/\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source),i("sql",/\bsql/.source),t].filter(Boolean);var d={javascript:!0,js:!0,typescript:!0,ts:!0,jsx:!0,tsx:!0};function p(e){return"string"==typeof e?e:Array.isArray(e)?e.map(p).join(""):p(e.content)}e.hooks.add("after-tokenize",(function(t){t.language in d&&function t(n){for(var r=0,o=n.length;r]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var t=e.languages.extend("typescript",{});delete t["class-name"],e.languages.typescript["class-name"].inside=t,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:t}}}}),e.languages.ts=e.languages.typescript}(o),function(e){function t(e,t){return RegExp(e.replace(//g,(function(){return/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/.source})),t)}e.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+e.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),e.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+e.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),e.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|(?:Weak)?(?:Map|Set)|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),e.languages.insertBefore("javascript","keyword",{imports:{pattern:t(/(\bimport\b\s*)(?:(?:\s*,\s*(?:\*\s*as\s+|\{[^{}]*\}))?|\*\s*as\s+|\{[^{}]*\})(?=\s*\bfrom\b)/.source),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:t(/(\bexport\b\s*)(?:\*(?:\s*as\s+)?(?=\s*\bfrom\b)|\{[^{}]*\})/.source),lookbehind:!0,inside:e.languages.javascript}}),e.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|finally|for|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),e.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),e.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:t(/(\.\s*)#?/.source),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|(?:local|session)Storage|location|navigator|performance|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var n=["function","function-variable","method","method-variable","property-access"],r=0;r*\.{3}(?:[^{}]|)*\})/.source;function a(e,t){return e=e.replace(//g,(function(){return n})).replace(//g,(function(){return r})).replace(//g,(function(){return o})),RegExp(e,t)}o=a(o).source,e.languages.jsx=e.languages.extend("markup",t),e.languages.jsx.tag.pattern=a(/<\/?(?:[\w.:-]+(?:+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|))?|))**\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:a(//.source),inside:e.languages.jsx}},e.languages.jsx.tag),e.languages.insertBefore("inside","special-attr",{script:{pattern:a(/=/.source),alias:"language-javascript",inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:e.languages.jsx}}},e.languages.jsx.tag);var i=function(e){return e?"string"==typeof e?e:"string"==typeof e.content?e.content:e.content.map(i).join(""):""},l=function(t){for(var n=[],r=0;r0&&n[n.length-1].tagName===i(o.content[0].content[1])&&n.pop():"/>"===o.content[o.content.length-1].content||n.push({tagName:i(o.content[0].content[1]),openedBraces:0}):n.length>0&&"punctuation"===o.type&&"{"===o.content?n[n.length-1].openedBraces++:n.length>0&&n[n.length-1].openedBraces>0&&"punctuation"===o.type&&"}"===o.content?n[n.length-1].openedBraces--:a=!0),(a||"string"==typeof o)&&n.length>0&&0===n[n.length-1].openedBraces){var s=i(o);r0&&("string"==typeof t[r-1]||"plain-text"===t[r-1].type)&&(s=i(t[r-1])+s,t.splice(r-1,1),r--),t[r]=new e.Token("plain-text",s,null,s)}o.content&&"string"!=typeof o.content&&l(o.content)}};e.hooks.add("after-tokenize",(function(e){"jsx"!==e.language&&"tsx"!==e.language||l(e.tokens)}))}(o),function(e){e.languages.diff={coord:[/^(?:\*{3}|-{3}|\+{3}).*$/m,/^@@.*@@$/m,/^\d.*$/m]};var t={"deleted-sign":"-","deleted-arrow":"<","inserted-sign":"+","inserted-arrow":">",unchanged:" ",diff:"!"};Object.keys(t).forEach((function(n){var r=t[n],o=[];/^\w+$/.test(n)||o.push(/\w+/.exec(n)[0]),"diff"===n&&o.push("bold"),e.languages.diff[n]={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(n)[0]}}}})),Object.defineProperty(e.languages.diff,"PREFIXES",{value:t})}(o),o.languages.git={comment:/^#.*/m,deleted:/^[-\u2013].*/m,inserted:/^\+.*/m,string:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s--?\w+/}},coord:/^@@.*@@$/m,"commit-sha1":/^commit \w{40}$/m},o.languages.go=o.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),o.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete o.languages.go["class-name"],function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,o,a){if(n.language===r){var i=n.tokenStack=[];n.code=n.code.replace(o,(function(e){if("function"==typeof a&&!a(e))return e;for(var o,l=i.length;-1!==n.code.indexOf(o=t(r,l));)++l;return i[l]=e,o})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var o=0,a=Object.keys(n.tokenStack);!function i(l){for(var s=0;s=a.length);s++){var c=l[s];if("string"==typeof c||c.content&&"string"==typeof c.content){var u=a[o],d=n.tokenStack[u],p="string"==typeof c?c:c.content,f=t(r,u),m=p.indexOf(f);if(m>-1){++o;var h=p.substring(0,m),g=new e.Token(r,e.tokenize(d,n.grammar),"language-"+r,d),v=p.substring(m+f.length),b=[];h&&b.push.apply(b,i([h])),b.push(g),v&&b.push.apply(b,i([v])),"string"==typeof c?l.splice.apply(l,[s,1].concat(b)):c.content=b}}else c.content&&i(c.content)}return l}(n.tokens)}}}})}(o),function(e){e.languages.handlebars={comment:/\{\{![\s\S]*?\}\}/,delimiter:{pattern:/^\{\{\{?|\}\}\}?$/,alias:"punctuation"},string:/(["'])(?:\\.|(?!\1)[^\\\r\n])*\1/,number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee][+-]?\d+)?/,boolean:/\b(?:false|true)\b/,block:{pattern:/^(\s*(?:~\s*)?)[#\/]\S+?(?=\s*(?:~\s*)?$|\s)/,lookbehind:!0,alias:"keyword"},brackets:{pattern:/\[[^\]]+\]/,inside:{punctuation:/\[|\]/,variable:/[\s\S]+/}},punctuation:/[!"#%&':()*+,.\/;<=>@\[\\\]^`{|}~]/,variable:/[^!"#%&'()*+,\/;<=>@\[\\\]^`{|}~\s]+/},e.hooks.add("before-tokenize",(function(t){e.languages["markup-templating"].buildPlaceholders(t,"handlebars",/\{\{\{[\s\S]+?\}\}\}|\{\{[\s\S]+?\}\}/g)})),e.hooks.add("after-tokenize",(function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"handlebars")})),e.languages.hbs=e.languages.handlebars}(o),o.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"}},o.languages.webmanifest=o.languages.json,o.languages.less=o.languages.extend("css",{comment:[/\/\*[\s\S]*?\*\//,{pattern:/(^|[^\\])\/\/.*/,lookbehind:!0}],atrule:{pattern:/@[\w-](?:\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{punctuation:/[:()]/}},selector:{pattern:/(?:@\{[\w-]+\}|[^{};\s@])(?:@\{[\w-]+\}|\((?:[^(){}]|\([^(){}]*\))*\)|[^(){};@\s]|\s+(?!\s))*?(?=\s*\{)/,inside:{variable:/@+[\w-]+/}},property:/(?:@\{[\w-]+\}|[\w-])+(?:\+_?)?(?=\s*:)/,operator:/[+\-*\/]/}),o.languages.insertBefore("less","property",{variable:[{pattern:/@[\w-]+\s*:/,inside:{punctuation:/:/}},/@@?[\w-]+/],"mixin-usage":{pattern:/([{;]\s*)[.#](?!\d)[\w-].*?(?=[(;])/,lookbehind:!0,alias:"function"}}),o.languages.makefile={comment:{pattern:/(^|[^\\])#(?:\\(?:\r\n|[\s\S])|[^\\\r\n])*/,lookbehind:!0},string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"builtin-target":{pattern:/\.[A-Z][^:#=\s]+(?=\s*:(?!=))/,alias:"builtin"},target:{pattern:/^(?:[^:=\s]|[ \t]+(?![\s:]))+(?=\s*:(?!=))/m,alias:"symbol",inside:{variable:/\$+(?:(?!\$)[^(){}:#=\s]+|(?=[({]))/}},variable:/\$+(?:(?!\$)[^(){}:#=\s]+|\([@*%<^+?][DF]\)|(?=[({]))/,keyword:/-include\b|\b(?:define|else|endef|endif|export|ifn?def|ifn?eq|include|override|private|sinclude|undefine|unexport|vpath)\b/,function:{pattern:/(\()(?:abspath|addsuffix|and|basename|call|dir|error|eval|file|filter(?:-out)?|findstring|firstword|flavor|foreach|guile|if|info|join|lastword|load|notdir|or|origin|patsubst|realpath|shell|sort|strip|subst|suffix|value|warning|wildcard|word(?:list|s)?)(?=[ \t])/,lookbehind:!0},operator:/(?:::|[?:+!])?=|[|@]/,punctuation:/[:;(){}]/},o.languages.objectivec=o.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete o.languages.objectivec["class-name"],o.languages.objc=o.languages.objectivec,o.languages.ocaml={comment:{pattern:/\(\*[\s\S]*?\*\)/,greedy:!0},char:{pattern:/'(?:[^\\\r\n']|\\(?:.|[ox]?[0-9a-f]{1,3}))'/i,greedy:!0},string:[{pattern:/"(?:\\(?:[\s\S]|\r\n)|[^\\\r\n"])*"/,greedy:!0},{pattern:/\{([a-z_]*)\|[\s\S]*?\|\1\}/,greedy:!0}],number:[/\b(?:0b[01][01_]*|0o[0-7][0-7_]*)\b/i,/\b0x[a-f0-9][a-f0-9_]*(?:\.[a-f0-9_]*)?(?:p[+-]?\d[\d_]*)?(?!\w)/i,/\b\d[\d_]*(?:\.[\d_]*)?(?:e[+-]?\d[\d_]*)?(?!\w)/i],directive:{pattern:/\B#\w+/,alias:"property"},label:{pattern:/\B~\w+/,alias:"property"},"type-variable":{pattern:/\B'\w+/,alias:"function"},variant:{pattern:/`\w+/,alias:"symbol"},keyword:/\b(?:as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|match|method|module|mutable|new|nonrec|object|of|open|private|rec|sig|struct|then|to|try|type|val|value|virtual|when|where|while|with)\b/,boolean:/\b(?:false|true)\b/,"operator-like-punctuation":{pattern:/\[[<>|]|[>|]\]|\{<|>\}/,alias:"punctuation"},operator:/\.[.~]|:[=>]|[=<>@^|&+\-*\/$%!?~][!$%&*+\-.\/:<=>?@^|~]*|\b(?:and|asr|land|lor|lsl|lsr|lxor|mod|or)\b/,punctuation:/;;|::|[(){}\[\].,:;#]|\b_\b/},o.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},o.languages.python["string-interpolation"].inside.interpolation.inside.rest=o.languages.python,o.languages.py=o.languages.python,o.languages.reason=o.languages.extend("clike",{string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},"class-name":/\b[A-Z]\w*/,keyword:/\b(?:and|as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|method|module|mutable|new|nonrec|object|of|open|or|private|rec|sig|struct|switch|then|to|try|type|val|virtual|when|while|with)\b/,operator:/\.{3}|:[:=]|\|>|->|=(?:==?|>)?|<=?|>=?|[|^?'#!~`]|[+\-*\/]\.?|\b(?:asr|land|lor|lsl|lsr|lxor|mod)\b/}),o.languages.insertBefore("reason","class-name",{char:{pattern:/'(?:\\x[\da-f]{2}|\\o[0-3][0-7][0-7]|\\\d{3}|\\.|[^'\\\r\n])'/,greedy:!0},constructor:/\b[A-Z]\w*\b(?!\s*\.)/,label:{pattern:/\b[a-z]\w*(?=::)/,alias:"symbol"}}),delete o.languages.reason.function,function(e){e.languages.sass=e.languages.extend("css",{comment:{pattern:/^([ \t]*)\/[\/*].*(?:(?:\r?\n|\r)\1[ \t].+)*/m,lookbehind:!0,greedy:!0}}),e.languages.insertBefore("sass","atrule",{"atrule-line":{pattern:/^(?:[ \t]*)[@+=].+/m,greedy:!0,inside:{atrule:/(?:@[\w-]+|[+=])/}}}),delete e.languages.sass.atrule;var t=/\$[-\w]+|#\{\$[-\w]+\}/,n=[/[+*\/%]|[=!]=|<=?|>=?|\b(?:and|not|or)\b/,{pattern:/(\s)-(?=\s)/,lookbehind:!0}];e.languages.insertBefore("sass","property",{"variable-line":{pattern:/^[ \t]*\$.+/m,greedy:!0,inside:{punctuation:/:/,variable:t,operator:n}},"property-line":{pattern:/^[ \t]*(?:[^:\s]+ *:.*|:[^:\s].*)/m,greedy:!0,inside:{property:[/[^:\s]+(?=\s*:)/,{pattern:/(:)[^:\s]+/,lookbehind:!0}],punctuation:/:/,variable:t,operator:n,important:e.languages.sass.important}}}),delete e.languages.sass.property,delete e.languages.sass.important,e.languages.insertBefore("sass","punctuation",{selector:{pattern:/^([ \t]*)\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*(?:,(?:\r?\n|\r)\1[ \t]+\S(?:,[^,\r\n]+|[^,\r\n]*)(?:,[^,\r\n]+)*)*/m,lookbehind:!0,greedy:!0}})}(o),o.languages.scss=o.languages.extend("css",{comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},atrule:{pattern:/@[\w-](?:\([^()]+\)|[^()\s]|\s+(?!\s))*?(?=\s+[{;])/,inside:{rule:/@[\w-]+/}},url:/(?:[-a-z]+-)?url(?=\()/i,selector:{pattern:/(?=\S)[^@;{}()]?(?:[^@;{}()\s]|\s+(?!\s)|#\{\$[-\w]+\})+(?=\s*\{(?:\}|\s|[^}][^:{}]*[:{][^}]))/,inside:{parent:{pattern:/&/,alias:"important"},placeholder:/%[-\w]+/,variable:/\$[-\w]+|#\{\$[-\w]+\}/}},property:{pattern:/(?:[-\w]|\$[-\w]|#\{\$[-\w]+\})+(?=\s*:)/,inside:{variable:/\$[-\w]+|#\{\$[-\w]+\}/}}}),o.languages.insertBefore("scss","atrule",{keyword:[/@(?:content|debug|each|else(?: if)?|extend|for|forward|function|if|import|include|mixin|return|use|warn|while)\b/i,{pattern:/( )(?:from|through)(?= )/,lookbehind:!0}]}),o.languages.insertBefore("scss","important",{variable:/\$[-\w]+|#\{\$[-\w]+\}/}),o.languages.insertBefore("scss","function",{"module-modifier":{pattern:/\b(?:as|hide|show|with)\b/i,alias:"keyword"},placeholder:{pattern:/%[-\w]+/,alias:"selector"},statement:{pattern:/\B!(?:default|optional)\b/i,alias:"keyword"},boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"},operator:{pattern:/(\s)(?:[-+*\/%]|[=!]=|<=?|>=?|and|not|or)(?=\s)/,lookbehind:!0}}),o.languages.scss.atrule.inside.rest=o.languages.scss,function(e){var t={pattern:/(\b\d+)(?:%|[a-z]+)/,lookbehind:!0},n={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0},r={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0},url:{pattern:/\burl\((["']?).*?\1\)/i,greedy:!0},string:{pattern:/("|')(?:(?!\1)[^\\\r\n]|\\(?:\r\n|[\s\S]))*\1/,greedy:!0},interpolation:null,func:null,important:/\B!(?:important|optional)\b/i,keyword:{pattern:/(^|\s+)(?:(?:else|for|if|return|unless)(?=\s|$)|@[\w-]+)/,lookbehind:!0},hexcode:/#[\da-f]{3,6}/i,color:[/\b(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)\b/i,{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:t,number:n,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:t,boolean:/\b(?:false|true)\b/,operator:[/~|[+!\/%<>?=]=?|[-:]=|\*[*=]?|\.{2,3}|&&|\|\||\B-\B|\b(?:and|in|is(?: a| defined| not|nt)?|not|or)\b/],number:n,punctuation:/[{}()\[\];:,]/};r.interpolation={pattern:/\{[^\r\n}:]+\}/,alias:"variable",inside:{delimiter:{pattern:/^\{|\}$/,alias:"punctuation"},rest:r}},r.func={pattern:/[\w-]+\([^)]*\).*/,inside:{function:/^[^(]+/,rest:r}},e.languages.stylus={"atrule-declaration":{pattern:/(^[ \t]*)@.+/m,lookbehind:!0,inside:{atrule:/^@[\w-]+/,rest:r}},"variable-declaration":{pattern:/(^[ \t]*)[\w$-]+\s*.?=[ \t]*(?:\{[^{}]*\}|\S.*|$)/m,lookbehind:!0,inside:{variable:/^\S+/,rest:r}},statement:{pattern:/(^[ \t]*)(?:else|for|if|return|unless)[ \t].+/m,lookbehind:!0,inside:{keyword:/^\S+/,rest:r}},"property-declaration":{pattern:/((?:^|\{)([ \t]*))(?:[\w-]|\{[^}\r\n]+\})+(?:\s*:\s*|[ \t]+)(?!\s)[^{\r\n]*(?:;|[^{\r\n,]$(?!(?:\r?\n|\r)(?:\{|\2[ \t])))/m,lookbehind:!0,inside:{property:{pattern:/^[^\s:]+/,inside:{interpolation:r.interpolation}},rest:r}},selector:{pattern:/(^[ \t]*)(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\)|(?![\w-]))|\{[^}\r\n]+\})+)(?:(?:\r?\n|\r)(?:\1(?:(?=\S)(?:[^{}\r\n:()]|::?[\w-]+(?:\([^)\r\n]*\)|(?![\w-]))|\{[^}\r\n]+\})+)))*(?:,$|\{|(?=(?:\r?\n|\r)(?:\{|\1[ \t])))/m,lookbehind:!0,inside:{interpolation:r.interpolation,comment:r.comment,punctuation:/[{},]/}},func:r.func,string:r.string,comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0,greedy:!0},interpolation:r.interpolation,punctuation:/[{}()\[\];:.]/}}(o),function(e){var t=e.util.clone(e.languages.typescript);e.languages.tsx=e.languages.extend("jsx",t),delete e.languages.tsx.parameter,delete e.languages.tsx["literal-property"];var n=e.languages.tsx.tag;n.pattern=RegExp(/(^|[^\w$]|(?=<\/))/.source+"(?:"+n.pattern.source+")",n.pattern.flags),n.lookbehind=!0}(o),o.languages.wasm={comment:[/\(;[\s\S]*?;\)/,{pattern:/;;.*/,greedy:!0}],string:{pattern:/"(?:\\[\s\S]|[^"\\])*"/,greedy:!0},keyword:[{pattern:/\b(?:align|offset)=/,inside:{operator:/=/}},{pattern:/\b(?:(?:f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|neg?|nearest|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|sqrt|store(?:8|16|32)?|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))?|memory\.(?:grow|size))\b/,inside:{punctuation:/\./}},/\b(?:anyfunc|block|br(?:_if|_table)?|call(?:_indirect)?|data|drop|elem|else|end|export|func|get_(?:global|local)|global|if|import|local|loop|memory|module|mut|nop|offset|param|result|return|select|set_(?:global|local)|start|table|tee_local|then|type|unreachable)\b/],variable:/\$[\w!#$%&'*+\-./:<=>?@\\^`|~]+/,number:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/,punctuation:/[()]/};const a=o},9901:e=>{e.exports&&(e.exports={core:{meta:{path:"components/prism-core.js",option:"mandatory"},core:"Core"},themes:{meta:{path:"themes/{id}.css",link:"index.html?theme={id}",exclusive:!0},prism:{title:"Default",option:"default"},"prism-dark":"Dark","prism-funky":"Funky","prism-okaidia":{title:"Okaidia",owner:"ocodia"},"prism-twilight":{title:"Twilight",owner:"remybach"},"prism-coy":{title:"Coy",owner:"tshedor"},"prism-solarizedlight":{title:"Solarized Light",owner:"hectormatos2011 "},"prism-tomorrow":{title:"Tomorrow Night",owner:"Rosey"}},languages:{meta:{path:"components/prism-{id}",noCSS:!0,examplesPath:"examples/prism-{id}",addCheckAll:!0},markup:{title:"Markup",alias:["html","xml","svg","mathml","ssml","atom","rss"],aliasTitles:{html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",ssml:"SSML",atom:"Atom",rss:"RSS"},option:"default"},css:{title:"CSS",option:"default",modify:"markup"},clike:{title:"C-like",option:"default"},javascript:{title:"JavaScript",require:"clike",modify:"markup",optional:"regex",alias:"js",option:"default"},abap:{title:"ABAP",owner:"dellagustin"},abnf:{title:"ABNF",owner:"RunDevelopment"},actionscript:{title:"ActionScript",require:"javascript",modify:"markup",owner:"Golmote"},ada:{title:"Ada",owner:"Lucretia"},agda:{title:"Agda",owner:"xy-ren"},al:{title:"AL",owner:"RunDevelopment"},antlr4:{title:"ANTLR4",alias:"g4",owner:"RunDevelopment"},apacheconf:{title:"Apache Configuration",owner:"GuiTeK"},apex:{title:"Apex",require:["clike","sql"],owner:"RunDevelopment"},apl:{title:"APL",owner:"ngn"},applescript:{title:"AppleScript",owner:"Golmote"},aql:{title:"AQL",owner:"RunDevelopment"},arduino:{title:"Arduino",require:"cpp",alias:"ino",owner:"dkern"},arff:{title:"ARFF",owner:"Golmote"},armasm:{title:"ARM Assembly",alias:"arm-asm",owner:"RunDevelopment"},arturo:{title:"Arturo",alias:"art",optional:["bash","css","javascript","markup","markdown","sql"],owner:"drkameleon"},asciidoc:{alias:"adoc",title:"AsciiDoc",owner:"Golmote"},aspnet:{title:"ASP.NET (C#)",require:["markup","csharp"],owner:"nauzilus"},asm6502:{title:"6502 Assembly",owner:"kzurawel"},asmatmel:{title:"Atmel AVR Assembly",owner:"cerkit"},autohotkey:{title:"AutoHotkey",owner:"aviaryan"},autoit:{title:"AutoIt",owner:"Golmote"},avisynth:{title:"AviSynth",alias:"avs",owner:"Zinfidel"},"avro-idl":{title:"Avro IDL",alias:"avdl",owner:"RunDevelopment"},awk:{title:"AWK",alias:"gawk",aliasTitles:{gawk:"GAWK"},owner:"RunDevelopment"},bash:{title:"Bash",alias:"shell",aliasTitles:{shell:"Shell"},owner:"zeitgeist87"},basic:{title:"BASIC",owner:"Golmote"},batch:{title:"Batch",owner:"Golmote"},bbcode:{title:"BBcode",alias:"shortcode",aliasTitles:{shortcode:"Shortcode"},owner:"RunDevelopment"},bicep:{title:"Bicep",owner:"johnnyreilly"},birb:{title:"Birb",require:"clike",owner:"Calamity210"},bison:{title:"Bison",require:"c",owner:"Golmote"},bnf:{title:"BNF",alias:"rbnf",aliasTitles:{rbnf:"RBNF"},owner:"RunDevelopment"},brainfuck:{title:"Brainfuck",owner:"Golmote"},brightscript:{title:"BrightScript",owner:"RunDevelopment"},bro:{title:"Bro",owner:"wayward710"},bsl:{title:"BSL (1C:Enterprise)",alias:"oscript",aliasTitles:{oscript:"OneScript"},owner:"Diversus23"},c:{title:"C",require:"clike",owner:"zeitgeist87"},csharp:{title:"C#",require:"clike",alias:["cs","dotnet"],owner:"mvalipour"},cpp:{title:"C++",require:"c",owner:"zeitgeist87"},cfscript:{title:"CFScript",require:"clike",alias:"cfc",owner:"mjclemente"},chaiscript:{title:"ChaiScript",require:["clike","cpp"],owner:"RunDevelopment"},cil:{title:"CIL",owner:"sbrl"},clojure:{title:"Clojure",owner:"troglotit"},cmake:{title:"CMake",owner:"mjrogozinski"},cobol:{title:"COBOL",owner:"RunDevelopment"},coffeescript:{title:"CoffeeScript",require:"javascript",alias:"coffee",owner:"R-osey"},concurnas:{title:"Concurnas",alias:"conc",owner:"jasontatton"},csp:{title:"Content-Security-Policy",owner:"ScottHelme"},cooklang:{title:"Cooklang",owner:"ahue"},coq:{title:"Coq",owner:"RunDevelopment"},crystal:{title:"Crystal",require:"ruby",owner:"MakeNowJust"},"css-extras":{title:"CSS Extras",require:"css",modify:"css",owner:"milesj"},csv:{title:"CSV",owner:"RunDevelopment"},cue:{title:"CUE",owner:"RunDevelopment"},cypher:{title:"Cypher",owner:"RunDevelopment"},d:{title:"D",require:"clike",owner:"Golmote"},dart:{title:"Dart",require:"clike",owner:"Golmote"},dataweave:{title:"DataWeave",owner:"machaval"},dax:{title:"DAX",owner:"peterbud"},dhall:{title:"Dhall",owner:"RunDevelopment"},diff:{title:"Diff",owner:"uranusjr"},django:{title:"Django/Jinja2",require:"markup-templating",alias:"jinja2",owner:"romanvm"},"dns-zone-file":{title:"DNS zone file",owner:"RunDevelopment",alias:"dns-zone"},docker:{title:"Docker",alias:"dockerfile",owner:"JustinBeckwith"},dot:{title:"DOT (Graphviz)",alias:"gv",optional:"markup",owner:"RunDevelopment"},ebnf:{title:"EBNF",owner:"RunDevelopment"},editorconfig:{title:"EditorConfig",owner:"osipxd"},eiffel:{title:"Eiffel",owner:"Conaclos"},ejs:{title:"EJS",require:["javascript","markup-templating"],owner:"RunDevelopment",alias:"eta",aliasTitles:{eta:"Eta"}},elixir:{title:"Elixir",owner:"Golmote"},elm:{title:"Elm",owner:"zwilias"},etlua:{title:"Embedded Lua templating",require:["lua","markup-templating"],owner:"RunDevelopment"},erb:{title:"ERB",require:["ruby","markup-templating"],owner:"Golmote"},erlang:{title:"Erlang",owner:"Golmote"},"excel-formula":{title:"Excel Formula",alias:["xlsx","xls"],owner:"RunDevelopment"},fsharp:{title:"F#",require:"clike",owner:"simonreynolds7"},factor:{title:"Factor",owner:"catb0t"},false:{title:"False",owner:"edukisto"},"firestore-security-rules":{title:"Firestore security rules",require:"clike",owner:"RunDevelopment"},flow:{title:"Flow",require:"javascript",owner:"Golmote"},fortran:{title:"Fortran",owner:"Golmote"},ftl:{title:"FreeMarker Template Language",require:"markup-templating",owner:"RunDevelopment"},gml:{title:"GameMaker Language",alias:"gamemakerlanguage",require:"clike",owner:"LiarOnce"},gap:{title:"GAP (CAS)",owner:"RunDevelopment"},gcode:{title:"G-code",owner:"RunDevelopment"},gdscript:{title:"GDScript",owner:"RunDevelopment"},gedcom:{title:"GEDCOM",owner:"Golmote"},gettext:{title:"gettext",alias:"po",owner:"RunDevelopment"},gherkin:{title:"Gherkin",owner:"hason"},git:{title:"Git",owner:"lgiraudel"},glsl:{title:"GLSL",require:"c",owner:"Golmote"},gn:{title:"GN",alias:"gni",owner:"RunDevelopment"},"linker-script":{title:"GNU Linker Script",alias:"ld",owner:"RunDevelopment"},go:{title:"Go",require:"clike",owner:"arnehormann"},"go-module":{title:"Go module",alias:"go-mod",owner:"RunDevelopment"},graphql:{title:"GraphQL",optional:"markdown",owner:"Golmote"},groovy:{title:"Groovy",require:"clike",owner:"robfletcher"},haml:{title:"Haml",require:"ruby",optional:["css","css-extras","coffeescript","erb","javascript","less","markdown","scss","textile"],owner:"Golmote"},handlebars:{title:"Handlebars",require:"markup-templating",alias:["hbs","mustache"],aliasTitles:{mustache:"Mustache"},owner:"Golmote"},haskell:{title:"Haskell",alias:"hs",owner:"bholst"},haxe:{title:"Haxe",require:"clike",optional:"regex",owner:"Golmote"},hcl:{title:"HCL",owner:"outsideris"},hlsl:{title:"HLSL",require:"c",owner:"RunDevelopment"},hoon:{title:"Hoon",owner:"matildepark"},http:{title:"HTTP",optional:["csp","css","hpkp","hsts","javascript","json","markup","uri"],owner:"danielgtaylor"},hpkp:{title:"HTTP Public-Key-Pins",owner:"ScottHelme"},hsts:{title:"HTTP Strict-Transport-Security",owner:"ScottHelme"},ichigojam:{title:"IchigoJam",owner:"BlueCocoa"},icon:{title:"Icon",owner:"Golmote"},"icu-message-format":{title:"ICU Message Format",owner:"RunDevelopment"},idris:{title:"Idris",alias:"idr",owner:"KeenS",require:"haskell"},ignore:{title:".ignore",owner:"osipxd",alias:["gitignore","hgignore","npmignore"],aliasTitles:{gitignore:".gitignore",hgignore:".hgignore",npmignore:".npmignore"}},inform7:{title:"Inform 7",owner:"Golmote"},ini:{title:"Ini",owner:"aviaryan"},io:{title:"Io",owner:"AlesTsurko"},j:{title:"J",owner:"Golmote"},java:{title:"Java",require:"clike",owner:"sherblot"},javadoc:{title:"JavaDoc",require:["markup","java","javadoclike"],modify:"java",optional:"scala",owner:"RunDevelopment"},javadoclike:{title:"JavaDoc-like",modify:["java","javascript","php"],owner:"RunDevelopment"},javastacktrace:{title:"Java stack trace",owner:"RunDevelopment"},jexl:{title:"Jexl",owner:"czosel"},jolie:{title:"Jolie",require:"clike",owner:"thesave"},jq:{title:"JQ",owner:"RunDevelopment"},jsdoc:{title:"JSDoc",require:["javascript","javadoclike","typescript"],modify:"javascript",optional:["actionscript","coffeescript"],owner:"RunDevelopment"},"js-extras":{title:"JS Extras",require:"javascript",modify:"javascript",optional:["actionscript","coffeescript","flow","n4js","typescript"],owner:"RunDevelopment"},json:{title:"JSON",alias:"webmanifest",aliasTitles:{webmanifest:"Web App Manifest"},owner:"CupOfTea696"},json5:{title:"JSON5",require:"json",owner:"RunDevelopment"},jsonp:{title:"JSONP",require:"json",owner:"RunDevelopment"},jsstacktrace:{title:"JS stack trace",owner:"sbrl"},"js-templates":{title:"JS Templates",require:"javascript",modify:"javascript",optional:["css","css-extras","graphql","markdown","markup","sql"],owner:"RunDevelopment"},julia:{title:"Julia",owner:"cdagnino"},keepalived:{title:"Keepalived Configure",owner:"dev-itsheng"},keyman:{title:"Keyman",owner:"mcdurdin"},kotlin:{title:"Kotlin",alias:["kt","kts"],aliasTitles:{kts:"Kotlin Script"},require:"clike",owner:"Golmote"},kumir:{title:"KuMir (\u041a\u0443\u041c\u0438\u0440)",alias:"kum",owner:"edukisto"},kusto:{title:"Kusto",owner:"RunDevelopment"},latex:{title:"LaTeX",alias:["tex","context"],aliasTitles:{tex:"TeX",context:"ConTeXt"},owner:"japborst"},latte:{title:"Latte",require:["clike","markup-templating","php"],owner:"nette"},less:{title:"Less",require:"css",optional:"css-extras",owner:"Golmote"},lilypond:{title:"LilyPond",require:"scheme",alias:"ly",owner:"RunDevelopment"},liquid:{title:"Liquid",require:"markup-templating",owner:"cinhtau"},lisp:{title:"Lisp",alias:["emacs","elisp","emacs-lisp"],owner:"JuanCaicedo"},livescript:{title:"LiveScript",owner:"Golmote"},llvm:{title:"LLVM IR",owner:"porglezomp"},log:{title:"Log file",optional:"javastacktrace",owner:"RunDevelopment"},lolcode:{title:"LOLCODE",owner:"Golmote"},lua:{title:"Lua",owner:"Golmote"},magma:{title:"Magma (CAS)",owner:"RunDevelopment"},makefile:{title:"Makefile",owner:"Golmote"},markdown:{title:"Markdown",require:"markup",optional:"yaml",alias:"md",owner:"Golmote"},"markup-templating":{title:"Markup templating",require:"markup",owner:"Golmote"},mata:{title:"Mata",owner:"RunDevelopment"},matlab:{title:"MATLAB",owner:"Golmote"},maxscript:{title:"MAXScript",owner:"RunDevelopment"},mel:{title:"MEL",owner:"Golmote"},mermaid:{title:"Mermaid",owner:"RunDevelopment"},mizar:{title:"Mizar",owner:"Golmote"},mongodb:{title:"MongoDB",owner:"airs0urce",require:"javascript"},monkey:{title:"Monkey",owner:"Golmote"},moonscript:{title:"MoonScript",alias:"moon",owner:"RunDevelopment"},n1ql:{title:"N1QL",owner:"TMWilds"},n4js:{title:"N4JS",require:"javascript",optional:"jsdoc",alias:"n4jsd",owner:"bsmith-n4"},"nand2tetris-hdl":{title:"Nand To Tetris HDL",owner:"stephanmax"},naniscript:{title:"Naninovel Script",owner:"Elringus",alias:"nani"},nasm:{title:"NASM",owner:"rbmj"},neon:{title:"NEON",owner:"nette"},nevod:{title:"Nevod",owner:"nezaboodka"},nginx:{title:"nginx",owner:"volado"},nim:{title:"Nim",owner:"Golmote"},nix:{title:"Nix",owner:"Golmote"},nsis:{title:"NSIS",owner:"idleberg"},objectivec:{title:"Objective-C",require:"c",alias:"objc",owner:"uranusjr"},ocaml:{title:"OCaml",owner:"Golmote"},odin:{title:"Odin",owner:"edukisto"},opencl:{title:"OpenCL",require:"c",modify:["c","cpp"],owner:"Milania1"},openqasm:{title:"OpenQasm",alias:"qasm",owner:"RunDevelopment"},oz:{title:"Oz",owner:"Golmote"},parigp:{title:"PARI/GP",owner:"Golmote"},parser:{title:"Parser",require:"markup",owner:"Golmote"},pascal:{title:"Pascal",alias:"objectpascal",aliasTitles:{objectpascal:"Object Pascal"},owner:"Golmote"},pascaligo:{title:"Pascaligo",owner:"DefinitelyNotAGoat"},psl:{title:"PATROL Scripting Language",owner:"bertysentry"},pcaxis:{title:"PC-Axis",alias:"px",owner:"RunDevelopment"},peoplecode:{title:"PeopleCode",alias:"pcode",owner:"RunDevelopment"},perl:{title:"Perl",owner:"Golmote"},php:{title:"PHP",require:"markup-templating",owner:"milesj"},phpdoc:{title:"PHPDoc",require:["php","javadoclike"],modify:"php",owner:"RunDevelopment"},"php-extras":{title:"PHP Extras",require:"php",modify:"php",owner:"milesj"},"plant-uml":{title:"PlantUML",alias:"plantuml",owner:"RunDevelopment"},plsql:{title:"PL/SQL",require:"sql",owner:"Golmote"},powerquery:{title:"PowerQuery",alias:["pq","mscript"],owner:"peterbud"},powershell:{title:"PowerShell",owner:"nauzilus"},processing:{title:"Processing",require:"clike",owner:"Golmote"},prolog:{title:"Prolog",owner:"Golmote"},promql:{title:"PromQL",owner:"arendjr"},properties:{title:".properties",owner:"Golmote"},protobuf:{title:"Protocol Buffers",require:"clike",owner:"just-boris"},pug:{title:"Pug",require:["markup","javascript"],optional:["coffeescript","ejs","handlebars","less","livescript","markdown","scss","stylus","twig"],owner:"Golmote"},puppet:{title:"Puppet",owner:"Golmote"},pure:{title:"Pure",optional:["c","cpp","fortran"],owner:"Golmote"},purebasic:{title:"PureBasic",require:"clike",alias:"pbfasm",owner:"HeX0R101"},purescript:{title:"PureScript",require:"haskell",alias:"purs",owner:"sriharshachilakapati"},python:{title:"Python",alias:"py",owner:"multipetros"},qsharp:{title:"Q#",require:"clike",alias:"qs",owner:"fedonman"},q:{title:"Q (kdb+ database)",owner:"Golmote"},qml:{title:"QML",require:"javascript",owner:"RunDevelopment"},qore:{title:"Qore",require:"clike",owner:"temnroegg"},r:{title:"R",owner:"Golmote"},racket:{title:"Racket",require:"scheme",alias:"rkt",owner:"RunDevelopment"},cshtml:{title:"Razor C#",alias:"razor",require:["markup","csharp"],optional:["css","css-extras","javascript","js-extras"],owner:"RunDevelopment"},jsx:{title:"React JSX",require:["markup","javascript"],optional:["jsdoc","js-extras","js-templates"],owner:"vkbansal"},tsx:{title:"React TSX",require:["jsx","typescript"]},reason:{title:"Reason",require:"clike",owner:"Golmote"},regex:{title:"Regex",owner:"RunDevelopment"},rego:{title:"Rego",owner:"JordanSh"},renpy:{title:"Ren'py",alias:"rpy",owner:"HyuchiaDiego"},rescript:{title:"ReScript",alias:"res",owner:"vmarcosp"},rest:{title:"reST (reStructuredText)",owner:"Golmote"},rip:{title:"Rip",owner:"ravinggenius"},roboconf:{title:"Roboconf",owner:"Golmote"},robotframework:{title:"Robot Framework",alias:"robot",owner:"RunDevelopment"},ruby:{title:"Ruby",require:"clike",alias:"rb",owner:"samflores"},rust:{title:"Rust",owner:"Golmote"},sas:{title:"SAS",optional:["groovy","lua","sql"],owner:"Golmote"},sass:{title:"Sass (Sass)",require:"css",optional:"css-extras",owner:"Golmote"},scss:{title:"Sass (Scss)",require:"css",optional:"css-extras",owner:"MoOx"},scala:{title:"Scala",require:"java",owner:"jozic"},scheme:{title:"Scheme",owner:"bacchus123"},"shell-session":{title:"Shell session",require:"bash",alias:["sh-session","shellsession"],owner:"RunDevelopment"},smali:{title:"Smali",owner:"RunDevelopment"},smalltalk:{title:"Smalltalk",owner:"Golmote"},smarty:{title:"Smarty",require:"markup-templating",optional:"php",owner:"Golmote"},sml:{title:"SML",alias:"smlnj",aliasTitles:{smlnj:"SML/NJ"},owner:"RunDevelopment"},solidity:{title:"Solidity (Ethereum)",alias:"sol",require:"clike",owner:"glachaud"},"solution-file":{title:"Solution file",alias:"sln",owner:"RunDevelopment"},soy:{title:"Soy (Closure Template)",require:"markup-templating",owner:"Golmote"},sparql:{title:"SPARQL",require:"turtle",owner:"Triply-Dev",alias:"rq"},"splunk-spl":{title:"Splunk SPL",owner:"RunDevelopment"},sqf:{title:"SQF: Status Quo Function (Arma 3)",require:"clike",owner:"RunDevelopment"},sql:{title:"SQL",owner:"multipetros"},squirrel:{title:"Squirrel",require:"clike",owner:"RunDevelopment"},stan:{title:"Stan",owner:"RunDevelopment"},stata:{title:"Stata Ado",require:["mata","java","python"],owner:"RunDevelopment"},iecst:{title:"Structured Text (IEC 61131-3)",owner:"serhioromano"},stylus:{title:"Stylus",owner:"vkbansal"},supercollider:{title:"SuperCollider",alias:"sclang",owner:"RunDevelopment"},swift:{title:"Swift",owner:"chrischares"},systemd:{title:"Systemd configuration file",owner:"RunDevelopment"},"t4-templating":{title:"T4 templating",owner:"RunDevelopment"},"t4-cs":{title:"T4 Text Templates (C#)",require:["t4-templating","csharp"],alias:"t4",owner:"RunDevelopment"},"t4-vb":{title:"T4 Text Templates (VB)",require:["t4-templating","vbnet"],owner:"RunDevelopment"},tap:{title:"TAP",owner:"isaacs",require:"yaml"},tcl:{title:"Tcl",owner:"PeterChaplin"},tt2:{title:"Template Toolkit 2",require:["clike","markup-templating"],owner:"gflohr"},textile:{title:"Textile",require:"markup",optional:"css",owner:"Golmote"},toml:{title:"TOML",owner:"RunDevelopment"},tremor:{title:"Tremor",alias:["trickle","troy"],owner:"darach",aliasTitles:{trickle:"trickle",troy:"troy"}},turtle:{title:"Turtle",alias:"trig",aliasTitles:{trig:"TriG"},owner:"jakubklimek"},twig:{title:"Twig",require:"markup-templating",owner:"brandonkelly"},typescript:{title:"TypeScript",require:"javascript",optional:"js-templates",alias:"ts",owner:"vkbansal"},typoscript:{title:"TypoScript",alias:"tsconfig",aliasTitles:{tsconfig:"TSConfig"},owner:"dkern"},unrealscript:{title:"UnrealScript",alias:["uscript","uc"],owner:"RunDevelopment"},uorazor:{title:"UO Razor Script",owner:"jaseowns"},uri:{title:"URI",alias:"url",aliasTitles:{url:"URL"},owner:"RunDevelopment"},v:{title:"V",require:"clike",owner:"taggon"},vala:{title:"Vala",require:"clike",optional:"regex",owner:"TemplarVolk"},vbnet:{title:"VB.Net",require:"basic",owner:"Bigsby"},velocity:{title:"Velocity",require:"markup",owner:"Golmote"},verilog:{title:"Verilog",owner:"a-rey"},vhdl:{title:"VHDL",owner:"a-rey"},vim:{title:"vim",owner:"westonganger"},"visual-basic":{title:"Visual Basic",alias:["vb","vba"],aliasTitles:{vba:"VBA"},owner:"Golmote"},warpscript:{title:"WarpScript",owner:"RunDevelopment"},wasm:{title:"WebAssembly",owner:"Golmote"},"web-idl":{title:"Web IDL",alias:"webidl",owner:"RunDevelopment"},wiki:{title:"Wiki markup",require:"markup",owner:"Golmote"},wolfram:{title:"Wolfram language",alias:["mathematica","nb","wl"],aliasTitles:{mathematica:"Mathematica",nb:"Mathematica Notebook"},owner:"msollami"},wren:{title:"Wren",owner:"clsource"},xeora:{title:"Xeora",require:"markup",alias:"xeoracube",aliasTitles:{xeoracube:"XeoraCube"},owner:"freakmaxi"},"xml-doc":{title:"XML doc (.net)",require:"markup",modify:["csharp","fsharp","vbnet"],owner:"RunDevelopment"},xojo:{title:"Xojo (REALbasic)",owner:"Golmote"},xquery:{title:"XQuery",require:"markup",owner:"Golmote"},yaml:{title:"YAML",alias:"yml",owner:"hason"},yang:{title:"YANG",owner:"RunDevelopment"},zig:{title:"Zig",owner:"RunDevelopment"}},plugins:{meta:{path:"plugins/{id}/prism-{id}",link:"plugins/{id}/"},"line-highlight":{title:"Line Highlight",description:"Highlights specific lines and/or line ranges."},"line-numbers":{title:"Line Numbers",description:"Line number at the beginning of code lines.",owner:"kuba-kubula"},"show-invisibles":{title:"Show Invisibles",description:"Show hidden characters such as tabs and line breaks.",optional:["autolinker","data-uri-highlight"]},autolinker:{title:"Autolinker",description:"Converts URLs and emails in code to clickable links. Parses Markdown links in comments."},wpd:{title:"WebPlatform Docs",description:'Makes tokens link to WebPlatform.org documentation. The links open in a new tab.'},"custom-class":{title:"Custom Class",description:"This plugin allows you to prefix Prism's default classes (.comment can become .namespace--comment) or replace them with your defined ones (like .editor__comment). You can even add new classes.",owner:"dvkndn",noCSS:!0},"file-highlight":{title:"File Highlight",description:"Fetch external files and highlight them with Prism. Used on the Prism website itself.",noCSS:!0},"show-language":{title:"Show Language",description:"Display the highlighted language in code blocks (inline code does not show the label).",owner:"nauzilus",noCSS:!0,require:"toolbar"},"jsonp-highlight":{title:"JSONP Highlight",description:"Fetch content with JSONP and highlight some interesting content (e.g. GitHub/Gists or Bitbucket API).",noCSS:!0,owner:"nauzilus"},"highlight-keywords":{title:"Highlight Keywords",description:"Adds special CSS classes for each keyword for fine-grained highlighting.",owner:"vkbansal",noCSS:!0},"remove-initial-line-feed":{title:"Remove initial line feed",description:"Removes the initial line feed in code blocks.",owner:"Golmote",noCSS:!0},"inline-color":{title:"Inline color",description:"Adds a small inline preview for colors in style sheets.",require:"css-extras",owner:"RunDevelopment"},previewers:{title:"Previewers",description:"Previewers for angles, colors, gradients, easing and time.",require:"css-extras",owner:"Golmote"},autoloader:{title:"Autoloader",description:"Automatically loads the needed languages to highlight the code blocks.",owner:"Golmote",noCSS:!0},"keep-markup":{title:"Keep Markup",description:"Prevents custom markup from being dropped out during highlighting.",owner:"Golmote",optional:"normalize-whitespace",noCSS:!0},"command-line":{title:"Command Line",description:"Display a command line with a prompt and, optionally, the output/response from the commands.",owner:"chriswells0"},"unescaped-markup":{title:"Unescaped Markup",description:"Write markup without having to escape anything."},"normalize-whitespace":{title:"Normalize Whitespace",description:"Supports multiple operations to normalize whitespace in code blocks.",owner:"zeitgeist87",optional:"unescaped-markup",noCSS:!0},"data-uri-highlight":{title:"Data-URI Highlight",description:"Highlights data-URI contents.",owner:"Golmote",noCSS:!0},toolbar:{title:"Toolbar",description:"Attach a toolbar for plugins to easily register buttons on the top of a code block.",owner:"mAAdhaTTah"},"copy-to-clipboard":{title:"Copy to Clipboard Button",description:"Add a button that copies the code block to the clipboard when clicked.",owner:"mAAdhaTTah",require:"toolbar",noCSS:!0},"download-button":{title:"Download Button",description:"A button in the toolbar of a code block adding a convenient way to download a code file.",owner:"Golmote",require:"toolbar",noCSS:!0},"match-braces":{title:"Match braces",description:"Highlights matching braces.",owner:"RunDevelopment"},"diff-highlight":{title:"Diff Highlight",description:"Highlights the code inside diff blocks.",owner:"RunDevelopment",require:"diff"},"filter-highlight-all":{title:"Filter highlightAll",description:"Filters the elements the highlightAll and highlightAllUnder methods actually highlight.",owner:"RunDevelopment",noCSS:!0},treeview:{title:"Treeview",description:"A language with special styles to highlight file system tree structures.",owner:"Golmote"}}})},2885:(e,t,n)=>{const r=n(9901),o=n(9642),a=new Set;function i(e){void 0===e?e=Object.keys(r.languages).filter((e=>"meta"!=e)):Array.isArray(e)||(e=[e]);const t=[...a,...Object.keys(Prism.languages)];o(r,e,t).load((e=>{if(!(e in r.languages))return void(i.silent||console.warn("Language does not exist: "+e));const t="./prism-"+e;delete n.c[n(6500).resolve(t)],delete Prism.languages[e],n(6500)(t),a.add(e)}))}i.silent=!1,e.exports=i},6726:(e,t,n)=>{var r={"./":2885};function o(e){var t=a(e);return n(t)}function a(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}o.keys=function(){return Object.keys(r)},o.resolve=a,e.exports=o,o.id=6726},6500:(e,t,n)=>{var r={"./":2885};function o(e){var t=a(e);return n(t)}function a(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}o.keys=function(){return Object.keys(r)},o.resolve=a,e.exports=o,o.id=6500},9642:e=>{"use strict";var t=function(){var e=function(){};function t(e,t){Array.isArray(e)?e.forEach(t):null!=e&&t(e,0)}function n(e){for(var t={},n=0,r=e.length;n "));var l={},s=e[r];if(s){function c(t){if(!(t in e))throw new Error(r+" depends on an unknown component "+t);if(!(t in l))for(var i in o(t,a),l[t]=!0,n[t])l[i]=!0}t(s.require,c),t(s.optional,c),t(s.modify,c)}n[r]=l,a.pop()}}return function(e){var t=n[e];return t||(o(e,r),t=n[e]),t}}function o(e){for(var t in e)return!0;return!1}return function(a,i,l){var s=function(e){var t={};for(var n in e){var r=e[n];for(var o in r)if("meta"!=o){var a=r[o];t[o]="string"==typeof a?{title:a}:a}}return t}(a),c=function(e){var n;return function(r){if(r in e)return r;if(!n)for(var o in n={},e){var a=e[o];t(a&&a.alias,(function(t){if(t in n)throw new Error(t+" cannot be alias for both "+o+" and "+n[t]);if(t in e)throw new Error(t+" cannot be alias of "+o+" because it is a component.");n[t]=o}))}return n[r]||r}}(s);i=i.map(c),l=(l||[]).map(c);var u=n(i),d=n(l);i.forEach((function e(n){var r=s[n];t(r&&r.require,(function(t){t in d||(u[t]=!0,e(t))}))}));for(var p,f=r(s),m=u;o(m);){for(var h in p={},m){var g=s[h];t(g&&g.modify,(function(e){e in d&&(p[e]=!0)}))}for(var v in d)if(!(v in u))for(var b in f(v))if(b in u){p[v]=!0;break}for(var y in m=p)u[y]=!0}var w={getIds:function(){var e=[];return w.load((function(t){e.push(t)})),e},load:function(t,n){return function(t,n,r,o){var a=o?o.series:void 0,i=o?o.parallel:e,l={},s={};function c(e){if(e in l)return l[e];s[e]=!0;var o,u=[];for(var d in t(e))d in n&&u.push(d);if(0===u.length)o=r(e);else{var p=i(u.map((function(e){var t=c(e);return delete s[e],t})));a?o=a(p,(function(){return r(e)})):r(e)}return l[e]=o}for(var u in n)c(u);var d=[];for(var p in s)d.push(l[p]);return i(d)}(f,u,t,n)}};return w}}();e.exports=t},2703:(e,t,n)=>{"use strict";var r=n(414);function o(){}function a(){}a.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,a,i){if(i!==r){var l=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 l.name="Invariant Violation",l}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:a,resetWarningCache:o};return n.PropTypes=n,n}},5697:(e,t,n)=>{e.exports=n(2703)()},414:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},4448:(e,t,n)=>{"use strict";var r=n(7294),o=n(7418),a=n(3840);function i(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n