[
  {
    "path": ".circleci/config.yml",
    "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: cimg/node:20.19.4\n  working_directory: /mnt/ramdisk/repo\n\nversion: 2.1\n\nparameters:\n  \"force-release-docs\":\n    type: boolean\n    default: false\n\norbs:\n  publish-docs: infinitered/publish-docs@0.5.0\njobs:\n  tests:\n    <<: *defaults\n    resource_class: large\n    steps:\n      - checkout\n      - run:\n          name: Install the latest version of bun\n          command: curl -fsSL https://bun.sh/install | bash\n      - run:\n          name: Link bun\n          command: sudo ln -s ~/.bun/bin/bun /usr/local/bin/\n      - restore_cache:\n          name: Restore node modules\n          keys:\n            - v2-dependencies-{{ checksum \"package.json\" }}-{{ arch }}\n            # fallback to using the latest cache if no exact match is found\n            - v2-dependencies-\n      - run:\n          name: Install dependencies\n          command: pnpm install\n      - save_cache:\n          name: Save node modules\n          paths:\n            - node_modules\n          key: v1-dependencies-{{ checksum \"package.json\" }}-{{ arch }}\n      - run:\n          # We don't want to install CocoaPods on ubuntu where we run the tests.\n          name: Set up dummy `pod` command\n          command: |\n            sudo ln /bin/true /usr/local/bin/pod\n      - run:\n          name: Ensure git user is configured\n          command: |\n            git config --global user.email \"ci@infinite.red\"\n            git config --global user.name \"Infinite Red\"\n      - restore_cache:\n          name: Restore ignite dependency cache\n          key: ignite-deps-cache-{{ .Environment.IGNITE_DEPS_PACKAGER }}-{{ checksum \"boilerplate/package.json\" }}-{{ arch }}-{{ .Environment.IGNITE_DEPS_KEY_SUFFIX }}\n      - run:\n          name: Run static tests\n          command: pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run depcruise\n      - run:\n          name: Run jest tests\n          command: pnpm run test\n          no_output_timeout: 10m\n      - save_cache:\n          name: Save ignite dependency cache\n          paths:\n            - ~/.cache/ignite\n          key: ignite-deps-cache-{{ .Environment.IGNITE_DEPS_PACKAGER }}-{{ checksum \"boilerplate/package.json\" }}-{{ arch }}-{{ .Environment.IGNITE_DEPS_KEY_SUFFIX }}\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\" }}-{{ arch }}\n            # fallback to using the latest cache if no exact match is found\n            - v1-dependencies-\n      - run:\n          name: Build package\n          command: pnpm run build\n      # Run semantic-release after all the above is set.\n      - run:\n          name: Publish to npm\n          command: |\n            export NPM_ID_TOKEN=$(circleci run oidc get --claims '{\"aud\": \"npm:registry.npmjs.org\"}')\n            pnpm run ci:publish\n\n# Publishing docs details\npublish-details: &publish-details\n  description: \"Infinite Red's hottest boilerplate for React Native.\"\n  git_email: \"ci@infinite.red\"\n  git_username: \"Infinite Red CI\"\n  label: \"Ignite\"\n  project_name: \"ignite-cli\"\n  source_docs_dir: docs\n  source_repo_directory: \"source\"\n  target_docs_dir: \"docs\"\n  target_repo: \"git@github.com:infinitered/ir-docs.git\"\n  target_repo_directory: \"target\"\n  ssh_key_fingerprint: \"SHA256:aovjemSAAaR4+VcbAyUjlRrHpVoKistvCQcc0adPkHU\"\nworkflows:\n  version: 2\n  test_and_release:\n    jobs:\n      - tests\n      - publish:\n          context: ignite-npm-context\n          requires:\n            - tests\n          filters:\n            branches:\n              only: master\n  release-docs:\n    when:\n      and:\n        - not: << pipeline.parameters.force-release-docs >>\n        - true # Placeholder for correct YAML structure\n    jobs:\n      - publish-docs/publish_docs:\n          <<: *publish-details\n          filters:\n            branches:\n              only:\n                - master\n            tags:\n              only:\n                - '*v[0-9]+\\.[0-9]+\\.[0-9]+'\n  force-release-docs:\n    when: << pipeline.parameters.force-release-docs >>\n    jobs:\n      - publish-docs/publish_docs:\n          <<: *publish-details\n"
  },
  {
    "path": ".dependency-cruiser.js",
    "content": "/** @type {import('dependency-cruiser').IConfiguration} */\nmodule.exports = {\n  forbidden: [\n    {\n      name: \"no-circular\",\n      severity: \"warn\",\n      comment:\n        \"This dependency is part of a circular relationship. You might want to revise \" +\n        \"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) \",\n      from: {},\n      to: {\n        circular: true,\n      },\n    },\n    {\n      name: \"no-orphans\",\n      comment:\n        \"This is an orphan module - it's likely not used (anymore?). Either use it or \" +\n        \"remove it. If it's logical this module is an orphan (i.e. it's a config file), \" +\n        \"add an exception for it in your dependency-cruiser configuration. By default \" +\n        \"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration \" +\n        \"files (.d.ts), tsconfig.json and some of the babel and webpack configs.\",\n      severity: \"warn\",\n      from: {\n        orphan: true,\n        pathNot: [\n          \"(^|/)\\\\.[^/]+\\\\.(js|cjs|mjs|ts|json)$\",\n          \"\\\\.d\\\\.ts$\",\n          \"(^|/)tsconfig\\\\.json$\",\n          \"(^|/)(babel|webpack)\\\\.config\\\\.(js|cjs|mjs|ts|json)$\",\n          \"template\\\\.config\\\\.js$\", // template config for Ignite CLI\n          \"bin/ignite$\", // CLI entry point\n        ],\n      },\n      to: {},\n    },\n    {\n      name: \"no-deprecated-core\",\n      comment:\n        \"A module depends on a node core module that has been deprecated. Find an alternative - these are \" +\n        \"bound to exist - node doesn't deprecate lightly.\",\n      severity: \"warn\",\n      from: {},\n      to: {\n        dependencyTypes: [\"core\"],\n        path: [\"^(punycode)$\", \"^(domain)$\", \"^(constants)$\", \"^(sys)$\"],\n      },\n    },\n    {\n      name: \"not-to-deprecated\",\n      comment:\n        \"This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later \" +\n        \"version of that module, or find an alternative. Deprecated modules are a security risk.\",\n      severity: \"warn\",\n      from: {},\n      to: {\n        dependencyTypes: [\"deprecated\"],\n      },\n    },\n    {\n      name: \"no-non-package-json\",\n      severity: \"error\",\n      comment:\n        \"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. \" +\n        \"That's problematic as the package either (1) won't be available on live (2) will be \" +\n        \"available on live with an non-guaranteed version. Fix it by adding the package to the dependencies \" +\n        \"in your package.json.\",\n      from: {},\n      to: {\n        dependencyTypes: [\"npm-no-pkg\", \"npm-unknown\"],\n      },\n    },\n    {\n      name: \"not-to-unresolvable\",\n      comment:\n        \"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm \" +\n        \"module: add it to your package.json. In all other cases you likely already know what to do.\",\n      severity: \"error\",\n      from: {},\n      to: {\n        couldNotResolve: true,\n      },\n    },\n    {\n      name: \"no-duplicate-dep-types\",\n      comment:\n        \"Likely this module depends on an external ('npm') package that occurs more than once \" +\n        \"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause \" +\n        \"maintenance problems later on.\",\n      severity: \"warn\",\n      from: {},\n      to: {\n        moreThanOneDependencyType: true,\n        dependencyTypesNot: [\"type-only\"],\n      },\n    },\n    {\n      name: \"not-to-spec\",\n      comment:\n        \"This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. \" +\n        \"If there's something in a spec that's of use to other modules, it doesn't have that single \" +\n        \"responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.\",\n      severity: \"error\",\n      from: {\n        pathNot: \"\\\\.(spec|test)\\\\.(js|mjs|cjs|ts|tsx)$\",\n      },\n      to: {\n        path: \"\\\\.(spec|test)\\\\.(js|mjs|cjs|ts|tsx)$\",\n      },\n    },\n    {\n      name: \"not-to-dev-dep\",\n      severity: \"error\",\n      comment:\n        \"This module depends on an npm package from the 'devDependencies' section of your \" +\n        \"package.json. It looks like something that ships to production, though. To prevent problems \" +\n        \"with npm packages that aren't there on production declare it (only!) in the 'dependencies'\" +\n        \"section of your package.json. If this module is development only - add it to the \" +\n        \"from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration\",\n      from: {\n        path: \"^(src)\",\n        pathNot: \"\\\\.(spec|test)\\\\.(js|mjs|cjs|ts|tsx)$\",\n      },\n      to: {\n        dependencyTypes: [\"npm-dev\"],\n        pathNot: [\"node_modules/@types/\"],\n      },\n    },\n    {\n      name: \"optional-deps-used\",\n      severity: \"info\",\n      comment:\n        \"This module depends on an npm package that is declared as an optional dependency \" +\n        \"in your package.json. As this makes sense in limited situations only, it's flagged here. \" +\n        \"If you're using an optional dependency here by design - add an exception to your\" +\n        \"dependency-cruiser configuration.\",\n      from: {},\n      to: {\n        dependencyTypes: [\"npm-optional\"],\n      },\n    },\n    {\n      name: \"peer-deps-used\",\n      comment:\n        \"This module depends on an npm package that is declared as a peer dependency \" +\n        \"in your package.json. This makes sense if your package is e.g. a plugin, but in \" +\n        \"other cases - maybe not so much. If the use of a peer dependency is intentional \" +\n        \"add an exception to your dependency-cruiser configuration.\",\n      severity: \"warn\",\n      from: {},\n      to: {\n        dependencyTypes: [\"npm-peer\"],\n      },\n    },\n  ],\n  options: {\n    doNotFollow: {\n      path: \"node_modules\",\n    },\n    tsPreCompilationDeps: true,\n    tsConfig: {\n      fileName: \"tsconfig.json\",\n    },\n    enhancedResolveOptions: {\n      exportsFields: [\"exports\"],\n      conditionNames: [\"import\", \"require\", \"node\", \"default\"],\n      extensions: [\".ts\", \".js\", \".json\"],\n    },\n    reporterOptions: {\n      dot: {\n        collapsePattern: \"node_modules/(@[^/]+/[^/]+|[^/]+)\",\n      },\n      archi: {\n        collapsePattern: \"^(src|test)/[^/]+\",\n      },\n      text: {\n        highlightFocused: true,\n      },\n    },\n  },\n}\n"
  },
  {
    "path": ".eslintignore",
    "content": "node_modules\n.vscode\n\n**/*.snap\n**/*.txt\nbin/ignite\n"
  },
  {
    "path": ".eslintrc.js",
    "content": "const boilerplateLintConfig = require(\"./boilerplate/.eslintrc.js\")\n\nmodule.exports = {\n  ...boilerplateLintConfig,\n}\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing to Ignite CLI\n\nWe welcome all contributors to Ignite CLI! This contributing guide will help you get up and running to submit your first pull request.\n\nBefore submitting a pull request, you will want to make sure that your branch meets the following requirements:\n\n\\_Working on Ignite CLI requires pnpm 10.9.0\n\n- Everything works on iOS/Android\n- Jest tests pass in the root folder (`pnpm run test`)\n- New tests are included for any new functionality\n- Code is compliant with our linter and prettier (`pnpm run format:write && pnpm run lint`)\n- Branch has already been [synced with the upstream repo](https://help.github.com/articles/syncing-a-fork/) and any merge-conflicts have been resolved.\n\n## Requirements\n\n- Node (reasonably recent version)\n- pnpm (while you can use Ignite CLI without pnpm, we require it for contributors)\n\n## Getting Started\n\n1. Fork and then clone the repo (`git clone git@github.com:<YOURGITHUBUSER>/ignite.git`)\n2. CD into the directory (`cd ignite`)\n3. Uninstall npm version (`pnpm remove ignite-cli -g`)\n4. Pull all package dependencies (`pnpm install`)\n5. Link the local binary (`pnpm link`)\n\nTest it out:\n\n```sh\n$ ignite --version\n<current version here>\n$ which ignite\n/usr/local/bin/ignite\n$ ignite new UberForHeadLice\n...\n```\n\nNow you're ready to check out a new branch and get hacking on Ignite CLI!\n\n## Source Code\n\nTo get familiarized with Ignite CLI's source code, read the [Tour of Ignite CLI's source code](../docs/contributing/Tour-of-Ignite.md) (TODO! currently out of date).\n\n## How to Build and Run App\n\n```sh\n$ cd ~/your/projects\n$ ignite new HackingOnIgnite\n```\n\n## Testing the App\n\nWe use Jest for testing.\n\nTo run tests from the ignite folder:\n\n```sh\n$ pnpm run test\n```\n\n**To Run Lint** from ignite:\n\n```sh\n$ pnpm run lint\n```\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug report\ndescription: For issues with running ignite on your computer\nlabels: [\"bug\"]\nbody:\n\n  - type: markdown\n    attributes:\n      value: >\n        Before creating a **bug report**, please check the following:\n          * Ensure you're running the latest version release.\n          * Ensure there isn't an existing issue for your bug. If there is, leave a comment on the existing issue.\n\n        If you're unsure whether you've hit a bug, check out the #react-native channel in our [Slack Community](https://community.infinite.red).\n\n  - type: textarea\n    attributes:\n      label: Describe the bug\n      description: Also include a description of how to reproduce the bug\n    validations:\n      required: true\n\n  - type: input\n    id: version\n    attributes:\n      label: Ignite version\n      description: If you're not on release, provide the commit hash\n      placeholder: 7.15.0\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Additional info\n      description: Run `npx ignite-cli doctor` and paste the results\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Discussions\n    url: https://github.com/infinitered/ignite/discussions\n    about: For questions and discussion about ignite\n  - name: Slack Community\n    url: https://community.infinite.red/\n    about: Check out our community for more real-time discussion\n  - name: Twitter Community\n    url: https://twitter.com/i/communities/1509407040095068166\n    about: We post news, tips, requests for help, and other discussions.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/enhancement.md",
    "content": "---\nname: Enhancement\nabout: For ignite enhancement suggestions or feature request\ntitle: \"\"\nlabels: \"enhancement\"\nassignees: \"\"\n---\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "## Description\n\n<!-- Add your PR description here -->\n\n- Issues: <!-- e.g. \"fixes #9999\", \"related: #9999\", etc -->\n\n## Screenshots\n\n<!-- If not applicable, delete this whole section -->\n\n<!-- If you’re submitting code changes related to a visible feature, please include before-and-after screenshots or videos. -->\n\n<!-- If GH's auto attachment previews too large, trying resizing it with: <img src=\"filename-from-github-upload\" width=\"300\" /> -->\n\n| Before                           | After                           |\n| -------------------------------- | ------------------------------- |\n| [Insert before screenshot/video] | [Insert after screenshot/video] |\n\n## Checklist\n\n<!-- if an item doesn't apply, just delete it -->\n\n- [ ] `README.md` and other relevant documentation has been updated with my changes\n- [ ] I have manually tested this, including by generating a new app locally ([see docs](https://docs.infinite.red/ignite-cli/contributing/Contributing-To-Ignite/#testing-changes-from-your-local-copy-of-ignite)).\n"
  },
  {
    "path": ".gitignore",
    "content": "# OSX\n#\n.idea\n.DS_Store\nnpm-debug.log\nnpm-debug.log*\nyarn-error.log\nlerna-debug.log\nnode_modules\n.vscode/*\n\n# TS build\n/build\ncoverage\n\n# This is at root because we don't want to ignore it in\n# newly spun-up apps, but we do want to ignore it in\n# Ignite's source repo.\nboilerplate/yarn.lock\nboilerplate/bun.lockb\nboilerplate/bun.lock\nboilerplate/pnpm-lock.yaml\nboilerplate/.gitignore.template\n\n# Test artifacts\ntest/artifacts/*\n\n# flame CLI\n.config\n\n# Ignore everything inside .yarn except the necessary subdirectories\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/sdks\n!.yarn/versions\n\n# Ignore Yarn's global cache (not needed for Zero-Installs)\n.yarn/cache\n\n# Ignore build artifacts\n.yarn/build-state.yml\n.yarn/install-state.gz\n\n\n# Dependency graph visualizations\nignite-cli-dependency-graph.svg\nignite-cli-dependency-graph.png\n"
  },
  {
    "path": ".nvmrc",
    "content": "v20\n"
  },
  {
    "path": ".prettierignore",
    "content": "/build\n/boilerplate/*\n!/boilerplate/app"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"printWidth\": 100,\n  \"semi\": false,\n  \"singleQuote\": false,\n  \"trailingComma\": \"all\",\n  \"quoteProps\": \"consistent\"\n}\n"
  },
  {
    "path": ".yarnrc.yml",
    "content": "enableGlobalCache: false\n\nnodeLinker: node-modules\n\nyarnPath: .yarn/releases/yarn-4.9.1.cjs\n\nenableScripts: false\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n- Using welcoming and inclusive language\n- Being respectful of differing viewpoints and experiences\n- Gracefully accepting constructive criticism\n- Focusing on what is best for the community\n- Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n- The use of sexualized language or imagery and unwelcome sexual attention or advances\n- Trolling, insulting/derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or electronic address, without explicit permission\n- Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@infinite.red. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016-present Infinite Red, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n---\n\nNote: this license only applies to the source code of the Ignite CLI, including\nthe source code of the boilerplate project. It does not apply to any source code\ngenerated by the CLI, which are property of the person or entity that used the\nCLI to generate them.\n\nHowever, some files may be added or installed automatically as part of the\ngeneration process (e.g. through npm packages). These files are subject to their\nown licenses, which may include more restrictive terms. It is your responsibility\nto review and comply with the licenses of any third-party dependencies included\nin the generated project.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\"><img src=\"https://user-images.githubusercontent.com/1479215/206780298-2b98221d-9c57-4cd3-866a-cf85ec1ddd9e.jpg\" alt=\"Ignite README Splash Image\" /></p>\n\n# Ignite - the battle-tested React Native boilerplate\n\n<a href=\"https://badge.fury.io/js/ignite-cli\" target=\"_blank\"><img src=\"https://badge.fury.io/js/ignite-cli.svg\" alt=\"npm version\" height=\"20\" /></a>\n![GitHub Repo stars](https://img.shields.io/github/stars/infinitered/ignite)\n![Twitter Follow](https://img.shields.io/twitter/follow/ir_ignite)\n[![CircleCI](https://dl.circleci.com/status-badge/img/gh/infinitered/ignite/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/infinitered/ignite/tree/master)\n\n## Proven React Native boilerplate\n\nDeveloped and maintained consistently since 2016, Ignite is the oldest active and most popular third-party React Native / Expo app boilerplate.\n\nThis is the React Native starting point that the [Infinite Red](https://infinite.red/react-native-app-development-company) team uses on a day-to-day basis to build client apps. Developers who use Ignite report that it saves them two to four weeks of time on average off the beginning of their React Native project!\n\n## Intro Videos\n\nHere are a few videos / talks that introduce Ignite and show off some of its features. Check them out!\n\n<table>\n  <tr>\n    <td width=\"50%\">\n      <figure>\n        <a href=\"https://www.youtube.com/watch?v=KOSvDlFyg20\">\n          <img src=\"https://img.youtube.com/vi/KOSvDlFyg20/sddefault.jpg\" alt=\"Getting Started with Ignite\"  width=\"100%\" /><br />\n        <figcaption><strong>Getting Started with Ignite</strong></figcaption>\n        </a>\n      </figure>\n    </td>\n    <td>\n      <figure>\n        <a href=\"https://www.youtube.com/watch?v=dNWkJOpD6YE&list=PLFHvL21g9bk0XOO9XK6d6S9w1jBU6Dz_U&index=16\">\n          <img src=\"https://img.youtube.com/vi/dNWkJOpD6YE/sddefault.jpg\" alt=\"Sweetening React Native Development With Ignite\" width=\"100%\" /><br />\n        <figcaption><strong>Ignite talk at Chain React 2024 - Robin Heinze</strong></figcaption>\n        </a>\n      </figure>\n    </td>\n  </tr>\n</table>\n\n## [Full Documentation](https://github.com/infinitered/ignite/blob/master/docs/README.md)\n\nWe've put great effort into the documentation as a team, please [read through it here](https://github.com/infinitered/ignite/blob/master/docs). If you're unsure why a certain decision was made related to this boilerplate or how to proceed with a particular feature, it's likely documented. If it still isn't clear, go through the proper [help channels](#reporting-bugs--getting-help) and we always welcome PRs to improve the docs!\n\n## Tech Stack\n\nNothing makes it into Ignite unless it's been proven on projects that Infinite Red works on. Ignite apps include the following rock-solid technical decisions out of the box:\n\n| Library                          | Category             | Version | Description                                    |\n| -------------------------------- | -------------------- | ------- | ---------------------------------------------- |\n| React Native                     | Mobile Framework     | v0.81   | The best cross-platform mobile framework       |\n| React                            | UI Framework         | v19     | The most popular UI framework in the world     |\n| TypeScript                       | Language             | v5      | Static typechecking                            |\n| React Navigation                 | Navigation           | v7      | Performant and consistent navigation framework |\n| Expo                             | SDK                  | v55     | Allows (optional) Expo modules                 |\n| Expo Font                        | Custom Fonts         | v14     | Import custom fonts                            |\n| Expo Localization                | Internationalization | v17     | i18n support (including RTL!)                  |\n| RN Reanimated                    | Animations           | v4      | Beautiful and performant animations            |\n| MMKV                             | Persistence          | v3      | State persistence                              |\n| apisauce                         | REST client          | v3      | Communicate with back-end                      |\n| Jest                             | Test Runner          | v29     | Standard test runner for JS apps               |\n| date-fns                         | Date library         | v4      | Excellent date library                         |\n| react-native-keyboard-controller | Keyboard library     | v1      | Great keyboard manager library                 |\n| react-native-edge-to-edge        | UI library           | v1      | Enables edge-to-edge in Android                |\n| Reactotron RN                    | Inspector/Debugger   | v5      | JS debugging                                   |\n| Maestro                          | Testing Framework    |         | Automate end-to-end UI testing                 |\n| Hermes                           | JS engine            |         | Fine-tuned JS engine for RN                    |\n\nIgnite also comes with a [component library](./docs/boilerplate/app/components/Components.md) that is tuned for custom designs, theming support, testing, custom fonts, generators, and much, much more.\n\n## Quick Start\n\nPrerequisites:\n\n- You'll need at least a recent version of [Node](https://nodejs.org/en) to run the CLI\n- For compiling/running in a simulator, make sure you're set up for React Native by following [the official documentation](https://reactnative.dev/docs/environment-setup).\n\nThe Ignite CLI will walk you through the steps to ignite a new React Native app:\n\n```bash\n# Get walked through the prompts for the different options to start your new app\nnpx ignite-cli@latest new PizzaApp\n\n# Accept all the recommended defaults and get straight to coding!\nnpx ignite-cli@latest new PizzaApp --yes\n```\n\nOnce you're up and running, check out our [Getting Started Guide](https://docs.infinite.red/ignite-cli/Guide/).\n\nIf you'd like to follow a tutorial, check out [this one from Robin Heinze](https://shift.infinite.red/creating-a-trivia-app-with-ignite-bowser-part-1-1987cc6e93a1).\n\n### Troubleshooting\n\nThe above commands may fail with various errors, depending on your operating system and dependency versions. Some troubleshooting steps to follow:\n\n- Uninstall global versions of the Ignite CLI via `npm uninstall -g ignite-cli` and use the CLI via `npx ignite-cli`\n- Make sure you are using a reasonably recent version of Node. This can be checked via the `node --version` command. If you require multiple Node versions on your system, install `nvm`, and then run `nvm install --lts`. At the time of writing, Node LTS is v20.x.x.\n- If the installation fails because of an Xcode error (missing Xcode command line tools), the easiest way to install them is to run `sudo xcode-select --install` in your terminal.\n- If Xcode and command line tools are already installed, but the installation complains about missing patch dependencies, you may need to switch the Xcode location to something else: `sudo xcode-select -s /Applications/Xcode.app/Contents/Developer`\n- Opening the project in Xcode can give you other insights into what's happening: `open ./ios/<yourapp>.xcworkspace`\n- Add the `--debug` switch to the Ignite CLI new command to provide additional output during project initialization\n\n## Reporting Bugs / Getting Help\n\nIf you run into problems, first search the issues and discussions in this repository. If you don't find anything, you can come talk to our friendly and active developers in the Infinite Red Community Slack ([community.infinite.red](https://community.infinite.red)).\n\nIf you still need help after reaching out to the proper channels, feel free to open a new GitHub issue via `npx ignite-cli issue \"Unable to Ignite new app\"` (as an example). This will help start writing your issue with the correct diagnostic information included.\n\n## Contributing to Ignite\n\nWant to contribute to Ignite? Check out [the contributing guide](./docs/contributing/Contributing-To-Ignite.md) for more info on how to work with the codebase.\n\n## Need Inspiration?\n\nIf you need battle-tested solutions from Infinite Red experts on everything from Accessibility, to CI/CD configuration, head to [Ignite Cookbook](https://ignitecookbook.com) for code snippets from our team and the community!\n\n## No time to learn React Native? Hire Infinite Red for your next project\n\nWe get it – sometimes there just isn’t enough time on a project to learn the ins and outs of a new framework. Infinite Red’s here to help! Our experienced team of React Native engineers have worked with companies like Microsoft, GasBuddy, Zoom, and Mercari to bring some of the most complex React Native projects to life.\n\nWhether it’s running a full project or training a team on React Native, we can help you solve your company’s toughest engineering challenges – and make it a great experience at the same time.\n\nReady to see how we can work together? [Send us a message](https://infinite.red/contact)\n\n## Further Reading\n\n- Watch Jamon Holmgren's talk at React Live Amsterdam 2019 where he uses Ignite to build an app in less than 30 minutes: [https://www.youtube.com/watch?v=OgiFKMd_TeY](https://www.youtube.com/watch?v=OgiFKMd_TeY)\n- Prior art includes [Ignite Andross](https://github.com/infinitered/ignite-andross) and [Ignite Bowser](https://github.com/infinitered/ignite-bowser) (which is very similar to the current version of Ignite).\n- [Who are We?](https://infinite.red/react-native-app-development-company) - Learn More About Infinite Red, the top React Native app development company\n\n## License and Trademark Notice\n\nThis project's source code is licensed under the [MIT License](LICENSE). The Ignite name, its logo, and any other brand assets associated with Ignite and Infinite Red are the exclusive property of Infinite Red, Inc. These marks are not covered by the MIT License provided herein and may not be used without explicit written permission from Infinite Red, Inc.\n\n### Note on Generated Code\n\nThe MIT License applies solely to the source code of the Ignite CLI and the source code of the included boilerplate project. Any source code generated by using the Ignite CLI, not including trademark assets described above, is owned entirely by the individual or entity that generated it.\n\nHowever, some files may be added or installed automatically as part of the generation process (e.g. through npm packages). These files are subject to their own licenses, which may include more restrictive terms. It is your responsibility to review and comply with the licenses of any third-party dependencies included in the generated project.\n"
  },
  {
    "path": "bin/ignite",
    "content": "#!/usr/bin/env node\n\n/* tslint:disable */\n\n// speed up `ignite-cli --version` et al\nif ([\"v\", \"version\", \"-v\", \"--v\", \"-version\", \"--version\"].includes(process.argv[2])) {\n  var contents = require(\"fs\").readFileSync(__dirname + \"/../package.json\")\n  var package = JSON.parse(contents)\n\n  // now output the version and exit\n  console.log(package.version)\n  process.exit(0)\n}\n\n// normal source directory\nvar sourceDir = __dirname + \"/../build\"\n\n// check if we're running in dev mode\nvar devMode = require(\"fs\").existsSync(`${__dirname}/../src`)\nvar wantsCompiled = process.argv.indexOf(\"--compiled-build\") >= 0\nif (devMode && !wantsCompiled) {\n  // hook into ts-node so we can run typescript on the fly\n  require(\"ts-node\").register({ project: `${__dirname}/../tsconfig.json` })\n  sourceDir = __dirname + \"/../src\"\n}\n\n// kick off ignite!\nrequire(sourceDir + \"/cli\").run(process.argv)\n"
  },
  {
    "path": "boilerplate/.dependency-cruiser.js",
    "content": "const metroConfig = require(\"./metro.config.js\")\n\nconst platforms = [\"ios\", \"android\", \"web\", \"native\"]\nconst extensions = metroConfig?.resolver?.sourceExts.flatMap((pExt) =>\n  platforms.map((platform) => `.${platform}.${pExt}`).concat(`.${pExt}`),\n)\n\n/** @type {import('dependency-cruiser').IConfiguration} */\nmodule.exports = {\n  forbidden: [\n    {\n      name: \"no-circular\",\n      severity: \"warn\",\n      comment:\n        \"This dependency is part of a circular relationship. You might want to revise \" +\n        \"your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) \",\n      from: {},\n      to: {\n        circular: true,\n      },\n    },\n    {\n      name: \"no-orphans\",\n      comment:\n        \"This is an orphan module - it's likely not used (anymore?). Either use it or \" +\n        \"remove it. If it's logical this module is an orphan (i.e. it's a config file), \" +\n        \"add an exception for it in your dependency-cruiser configuration. By default \" +\n        \"this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration \" +\n        \"files (.d.ts), tsconfig.json and some of the babel and webpack configs.\",\n      severity: \"warn\",\n      from: {\n        orphan: true,\n        pathNot: [\n          \"(^|/)\\\\.[^/]+\\\\.(js|cjs|mjs|ts|json)$\",\n          \"\\\\.d\\\\.ts$\",\n          \"(^|/)tsconfig\\\\.json$\",\n          \"(^|/)(babel|webpack)\\\\.config\\\\.(js|cjs|mjs|ts|json)$\",\n          \"crashReporting\\\\.ts$\", // Boilerplate file for future crash reporting setup\n          \"utils/delay\\\\.ts$\", // Utility function for delaying execution\n        ],\n      },\n      to: {},\n    },\n    {\n      name: \"no-deprecated-core\",\n      comment:\n        \"A module depends on a node core module that has been deprecated. Find an alternative - these are \" +\n        \"bound to exist - node doesn't deprecate lightly.\",\n      severity: \"warn\",\n      from: {},\n      to: {\n        dependencyTypes: [\"core\"],\n        path: [\"^(punycode)$\", \"^(domain)$\", \"^(constants)$\", \"^(sys)$\"],\n      },\n    },\n    {\n      name: \"not-to-deprecated\",\n      comment:\n        \"This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later \" +\n        \"version of that module, or find an alternative. Deprecated modules are a security risk.\",\n      severity: \"warn\",\n      from: {},\n      to: {\n        dependencyTypes: [\"deprecated\"],\n      },\n    },\n    {\n      name: \"no-non-package-json\",\n      severity: \"error\",\n      comment:\n        \"This module depends on an npm package that isn't in the 'dependencies' section of your package.json. \" +\n        \"That's problematic as the package either (1) won't be available on live (2) will be \" +\n        \"available on live with an non-guaranteed version. Fix it by adding the package to the dependencies \" +\n        \"in your package.json.\",\n      from: {},\n      to: {\n        dependencyTypes: [\"npm-no-pkg\", \"npm-unknown\"],\n      },\n    },\n    {\n      name: \"not-to-unresolvable\",\n      comment:\n        \"This module depends on a module that cannot be found ('resolved to disk'). If it's an npm \" +\n        \"module: add it to your package.json. In all other cases you likely already know what to do.\",\n      severity: \"error\",\n      from: {},\n      to: {\n        couldNotResolve: true,\n      },\n    },\n    {\n      name: \"no-duplicate-dep-types\",\n      comment:\n        \"Likely this module depends on an external ('npm') package that occurs more than once \" +\n        \"in your package.json i.e. bot as a devDependencies and in dependencies. This will cause \" +\n        \"maintenance problems later on.\",\n      severity: \"warn\",\n      from: {},\n      to: {\n        moreThanOneDependencyType: true,\n        dependencyTypesNot: [\"type-only\"],\n      },\n    },\n    {\n      name: \"not-to-spec\",\n      comment:\n        \"This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. \" +\n        \"If there's something in a spec that's of use to other modules, it doesn't have that single \" +\n        \"responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.\",\n      severity: \"error\",\n      from: {\n        pathNot: \"\\\\.(spec|test)\\\\.(js|mjs|cjs|ts|tsx)$\",\n      },\n      to: {\n        path: \"\\\\.(spec|test)\\\\.(js|mjs|cjs|ts|tsx)$\",\n      },\n    },\n    {\n      name: \"not-to-dev-dep\",\n      severity: \"error\",\n      comment:\n        \"This module depends on an npm package from the 'devDependencies' section of your \" +\n        \"package.json. It looks like something that ships to production, though. To prevent problems \" +\n        \"with npm packages that aren't there on production declare it (only!) in the 'dependencies'\" +\n        \"section of your package.json. If this module is development only - add it to the \" +\n        \"from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration\",\n      from: {\n        path: \"^(app|src)\",\n        pathNot: \"\\\\.(spec|test)\\\\.(js|mjs|cjs|ts|tsx)$\",\n      },\n      to: {\n        dependencyTypes: [\"npm-dev\"],\n        pathNot: [\"node_modules/@types/\"],\n        exoticRequireNot: [\n          \"react-native/Libraries/Utilities/codegenNativeComponent\",\n          \"react-native/Libraries/Utilities/codegenNativeCommands\",\n        ],\n      },\n    },\n    {\n      name: \"optional-deps-used\",\n      severity: \"info\",\n      comment:\n        \"This module depends on an npm package that is declared as an optional dependency \" +\n        \"in your package.json. As this makes sense in limited situations only, it's flagged here. \" +\n        \"If you're using an optional dependency here by design - add an exception to your\" +\n        \"dependency-cruiser configuration.\",\n      from: {},\n      to: {\n        dependencyTypes: [\"npm-optional\"],\n      },\n    },\n    {\n      name: \"peer-deps-used\",\n      comment:\n        \"This module depends on an npm package that is declared as a peer dependency \" +\n        \"in your package.json. This makes sense if your package is e.g. a plugin, but in \" +\n        \"other cases - maybe not so much. If the use of a peer dependency is intentional \" +\n        \"add an exception to your dependency-cruiser configuration.\",\n      severity: \"warn\",\n      from: {},\n      to: {\n        dependencyTypes: [\"npm-peer\"],\n      },\n    },\n  ],\n  options: {\n    doNotFollow: {\n      path: \"node_modules\",\n    },\n    tsPreCompilationDeps: true,\n    tsConfig: {\n      fileName: \"tsconfig.json\",\n    },\n    enhancedResolveOptions: {\n      exportsFields: [\"exports\"],\n      conditionNames: [\"import\", \"require\", \"node\", \"default\"],\n      // React Native / Metro bundler support for platform-specific extensions\n      // See: https://reactnative.dev/docs/platform-specific-code\n      // See: https://github.com/sverweij/dependency-cruiser/issues/511\n      extensions,\n    },\n    reporterOptions: {\n      dot: {\n        collapsePattern: \"node_modules/(@[^/]+/[^/]+|[^/]+)\",\n      },\n      archi: {\n        collapsePattern: \"^(app|src|test)/[^/]+\",\n      },\n      text: {\n        highlightFocused: true,\n      },\n    },\n  },\n}\n"
  },
  {
    "path": "boilerplate/.eslintignore",
    "content": "node_modules\nios\nandroid\n.expo\n.vscode\nignite/ignite.json\npackage.json\n.eslintignore\n"
  },
  {
    "path": "boilerplate/.eslintrc.js",
    "content": "// https://docs.expo.dev/guides/using-eslint/\nmodule.exports = {\n  root: true,\n  extends: [\n    \"plugin:@typescript-eslint/recommended\",\n    \"plugin:react/recommended\",\n    \"plugin:react-native/all\",\n    // `expo` must come after `standard` or its globals configuration will be overridden\n    \"expo\",\n    // `jsx-runtime` must come after `expo` or it will be overridden\n    \"plugin:react/jsx-runtime\",\n    \"prettier\",\n  ],\n  plugins: [\"reactotron\", \"prettier\"],\n  rules: {\n    \"prettier/prettier\": \"error\",\n    // typescript-eslint\n    \"@typescript-eslint/array-type\": 0,\n    \"@typescript-eslint/ban-ts-comment\": 0,\n    \"@typescript-eslint/no-explicit-any\": 0,\n    \"@typescript-eslint/no-unused-vars\": [\n      \"error\",\n      {\n        argsIgnorePattern: \"^_\",\n        varsIgnorePattern: \"^_\",\n      },\n    ],\n    \"@typescript-eslint/no-var-requires\": 0,\n    \"@typescript-eslint/no-require-imports\": 0,\n    \"@typescript-eslint/no-empty-object-type\": 0,\n    // eslint\n    \"no-use-before-define\": 0,\n    \"no-restricted-imports\": [\n      \"error\",\n      {\n        paths: [\n          // Prefer named exports from 'react' instead of importing `React`\n          {\n            name: \"react\",\n            importNames: [\"default\"],\n            message: \"Import named exports from 'react' instead.\",\n          },\n          {\n            name: \"react-native\",\n            importNames: [\"SafeAreaView\"],\n            message: \"Use the SafeAreaView from 'react-native-safe-area-context' instead.\",\n          },\n          {\n            name: \"react-native\",\n            importNames: [\"Text\", \"Button\", \"TextInput\"],\n            message: \"Use the custom wrapper component from '@/components'.\",\n          },\n        ],\n      },\n    ],\n    // react\n    \"react/prop-types\": 0,\n    // react-native\n    \"react-native/no-raw-text\": 0,\n    // reactotron\n    \"reactotron/no-tron-in-production\": \"error\",\n    // eslint-config-standard overrides\n    \"comma-dangle\": 0,\n    \"no-global-assign\": 0,\n    \"quotes\": 0,\n    \"space-before-function-paren\": 0,\n    // eslint-import\n    \"import/order\": [\n      \"error\",\n      {\n        \"alphabetize\": {\n          order: \"asc\",\n          caseInsensitive: true,\n        },\n        \"newlines-between\": \"always\",\n        \"groups\": [[\"builtin\", \"external\"], \"internal\", \"unknown\", [\"parent\", \"sibling\"], \"index\"],\n        \"distinctGroup\": false,\n        \"pathGroups\": [\n          {\n            pattern: \"react\",\n            group: \"external\",\n            position: \"before\",\n          },\n          {\n            pattern: \"react-native\",\n            group: \"external\",\n            position: \"before\",\n          },\n          {\n            pattern: \"expo{,-*}\",\n            group: \"external\",\n            position: \"before\",\n          },\n          {\n            pattern: \"@/**\",\n            group: \"unknown\",\n            position: \"after\",\n          },\n        ],\n        \"pathGroupsExcludedImportTypes\": [\"react\", \"react-native\", \"expo\", \"expo-*\"],\n      },\n    ],\n    \"import/newline-after-import\": 1,\n  },\n}\n"
  },
  {
    "path": "boilerplate/.gitignore",
    "content": "# OSX\n#\n.DS_Store\n\n# Xcode\n#\nbuild/\n*.pbxuser\n!default.pbxuser\n*.mode1v3\n!default.mode1v3\n*.mode2v3\n!default.mode2v3\n*.perspectivev3\n!default.perspectivev3\nxcuserdata\n*.xccheckout\n*.moved-aside\nDerivedData\n*.hmap\n*.ipa\n*.xcuserstate\nios/.xcode.env.local\n\n# Android/IntelliJ\n#\nbuild/\n.idea\n.gradle\nlocal.properties\n*.iml\n*.hprof\n.cxx/\n\n# node.js\n#\nnode_modules/\nnpm-debug.log\n\n# BUCK\nbuck-out/\n\\.buckd/\n*.keystore\n!debug.keystore\n\n# fastlane\n#\n# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the\n# screenshots whenever they are needed.\n# For more information about the recommended setup visit:\n# https://docs.fastlane.tools/best-practices/source-control/\n\n**/fastlane/report.xml\n**/fastlane/Preview.html\n**/fastlane/screenshots\n**/fastlane/test_output\n\n# Bundle artifact\n*.jsbundle\n\n# Ignite-specific items below\n# You can safely replace everything above this comment with whatever is\n# in the default .gitignore generated by React-Native CLI\n\n# VS Code\n.vscode\n\n# Expo\n.expo/*\nbin/Exponent.app\n/android\n/ios\nexpo-env.d.ts\n\n## Secrets\nnpm-debug.*\n*.jks\n*.p8\n*.p12\n*.key\n*.mobileprovision\n*.orig.*\nweb-build/\n\n# Configurations\n!env.js\n\n/coverage\n\n# Dependency Graph Visualizations\napp-dependency-graph.svg\napp-dependency-graph.png"
  },
  {
    "path": "boilerplate/.maestro/flows/FavoritePodcast.yaml",
    "content": "# 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: ${MAESTRO_APP_ID}\nenv:\n  FAVORITES_TEXT: \"Switch on to only show favorites\" # en.demoPodcastListScreen.accessibility.switch\nonFlowStart:\n  - runFlow: ../shared/_OnFlowStart.yaml\n---\n- runFlow: ../shared/_Login.yaml\n- tapOn: \"Podcast\"\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    # https://maestro.mobile.dev/troubleshooting/known-issues#android-accidental-double-tap\n    retryTapIfNoChange: false\n- repeat:\n    times: 2\n    commands:\n      - scroll\n- copyTextFrom:\n    text: \"RNR .*\" # assumes all podcast titles start with RNR\n    index: 2 # grab the third one, others might not be fully visible\n- longPressOn: ${maestro.copiedText}\n- scrollUntilVisible:\n    element:\n      text: ${FAVORITES_TEXT}\n    direction: UP\n    timeout: 50000\n    speed: 90\n    visibilityPercentage: 100\n- tapOn:\n    text: ${FAVORITES_TEXT}\n- assertVisible: ${maestro.copiedText}\n# @demo remove-file\n"
  },
  {
    "path": "boilerplate/.maestro/flows/Login.yaml",
    "content": "#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: ${MAESTRO_APP_ID} # 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\nonFlowStart:\n  - runFlow: ../shared/_OnFlowStart.yaml\n---\n- runFlow: ../shared/_Login.yaml\n# @demo remove-file\n"
  },
  {
    "path": "boilerplate/.maestro/shared/_Login.yaml",
    "content": "#flow: Shared _Login\n#intent: shared login flow for any flow that needs to start with a log in.\nappId: ${MAESTRO_APP_ID} \n---\n- assertVisible: \"Log In\"\n- tapOn:\n    text: \"Tap to Log 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# @demo remove-file\n"
  },
  {
    "path": "boilerplate/.maestro/shared/_OnFlowStart.yaml",
    "content": "# flow: Shared _OnFlowStart\n#intent:\n# launch the app with a completely clear state, wait for animations to settle,\n# and click through the expo dev screens if needed.\n# These conditionals slow the app launch down a little but are necessary because the expo\n# dev server and launch screen are only shown when the new architecture is turned off in expo 53.\n# So we check to see if we need to connect to the metro server... that loads the app and then we\n# check if the dev menu is showing and dismiss it if necessary.\n# Then the app is then launched and ready for the maestro tests to run.\n#\n# This flow should be included in every maestro test header as `onFlowStart` to ensure expo screens\n# are bypassed if necessary. Example:\n#\n# appId: ${MAESTRO_APP_ID}\n# onFlowStart:\n#   - runFlow: ../shared/_OnFlowStart.yaml\n# ---\n# [your maestro flow]\n#\nappId: ${MAESTRO_APP_ID}\n---\n# launch the app with a clean slate\n- launchApp:\n    clearState: true\n    clearKeychain: true\n    stopApp: true\n\n- waitForAnimationToEnd\n\n# conditionally run the dev client flow if the words \"Development servers\" is present.\n# this uses the default maestro timeout and moves on if it doesn't see the text.\n- runFlow:\n    when:\n      visible: 'Development servers'\n    commands:\n      # this regex allows for different hosts and ports\n      - tapOn: \"http://.*:.*\" \n      - waitForAnimationToEnd\n      - tapOn: \"Close\" # dismiss the bottom sheet\n"
  },
  {
    "path": "boilerplate/.npmrc",
    "content": "# I wish we could put this in package.json instead of here\nstrict-peer-dependencies=false"
  },
  {
    "path": "boilerplate/.prettierignore",
    "content": "node_modules\nios\nandroid\n.expo\n.vscode\nignite/ignite.json\npackage.json\n.eslintignore\n*.ejs\n"
  },
  {
    "path": "boilerplate/.prettierrc",
    "content": "{\n  \"printWidth\": 100,\n  \"semi\": false,\n  \"singleQuote\": false,\n  \"trailingComma\": \"all\",\n  \"quoteProps\": \"consistent\"\n}"
  },
  {
    "path": "boilerplate/README.md",
    "content": "# Welcome to your new ignited app!\n\n> The latest and greatest boilerplate for Infinite Red opinions\n\nThis is the boilerplate that [Infinite Red](https://infinite.red) uses as a way to test bleeding-edge changes to our React Native stack.\n\n- [Quick start documentation](https://github.com/infinitered/ignite/blob/master/docs/boilerplate/Boilerplate.md)\n- [Full documentation](https://github.com/infinitered/ignite/blob/master/docs/README.md)\n\n## Getting Started\n\n```bash\npnpm run\npnpm run start\n```\n\nTo make things work on your local simulator, or on your phone, you need first to [run `eas build`](https://github.com/infinitered/ignite/blob/master/docs/expo/EAS.md). We have many shortcuts on `package.json` to make it easier:\n\n```bash\npnpm run build:ios:sim # build for ios simulator\npnpm run build:ios:device # build for ios device\npnpm run build:ios:prod # build for ios device\n```\n\n### `./assets`\n\nThis directory is designed to organize and store various assets, making it easy for you to manage and use them in your application. The assets are further categorized into subdirectories, including `icons` and `images`:\n\n```tree\nassets\n├── icons\n└── images\n```\n\n**icons**\nThis is where your icon assets will live. These icons can be used for buttons, navigation elements, or any other UI components. The recommended format for icons is PNG, but other formats can be used as well.\n\nIgnite comes with a built-in `Icon` component. You can find detailed usage instructions in the [docs](https://github.com/infinitered/ignite/blob/master/docs/boilerplate/app/components/Icon.md).\n\n**images**\nThis is where your images will live, such as background images, logos, or any other graphics. You can use various formats such as PNG, JPEG, or GIF for your images.\n\nAnother valuable built-in component within Ignite is the `AutoImage` component. You can find detailed usage instructions in the [docs](https://github.com/infinitered/ignite/blob/master/docs/Components-AutoImage.md).\n\nHow to use your `icon` or `image` assets:\n\n```typescript\nimport { Image } from 'react-native';\n\nconst MyComponent = () => {\n  return (\n    <Image source={require('assets/images/my_image.png')} />\n  );\n};\n```\n\n## Running Maestro end-to-end tests\n\nFollow our [Maestro Setup](https://ignitecookbook.com/docs/recipes/MaestroSetup) recipe.\n\n## Next Steps\n\n### Ignite Cookbook\n\n[Ignite Cookbook](https://ignitecookbook.com/) is an easy way for developers to browse and share code snippets (or “recipes”) that actually work.\n\n### Upgrade Ignite boilerplate\n\nRead our [Upgrade Guide](https://ignitecookbook.com/docs/recipes/UpdatingIgnite) to learn how to upgrade your Ignite project.\n\n## Community\n\n⭐️ Help us out by [starring on GitHub](https://github.com/infinitered/ignite), filing bug reports in [issues](https://github.com/infinitered/ignite/issues) or [ask questions](https://github.com/infinitered/ignite/discussions).\n\n💬 Join us on [Slack](https://join.slack.com/t/infiniteredcommunity/shared_invite/zt-1f137np4h-zPTq_CbaRFUOR_glUFs2UA) to discuss.\n\n📰 Make our Editor-in-chief happy by [reading the React Native Newsletter](https://reactnativenewsletter.com/).\n"
  },
  {
    "path": "boilerplate/app/app.tsx",
    "content": "/* eslint-disable import/first */\n/**\n * Welcome to the main entry point of the app. In this file, we'll\n * be kicking off our app.\n *\n * Most of this file is boilerplate and you shouldn't need to modify\n * it very often. But take some time to look through and understand\n * what is going on here.\n *\n * The app navigation resides in ./app/navigators, so head over there\n * if you're interested in adding screens and navigators.\n */\nif (__DEV__) {\n  // Load Reactotron in development only.\n  // Note that you must be using metro's `inlineRequires` for this to work.\n  // If you turn it off in metro.config.js, you'll have to manually import it.\n  require(\"./devtools/ReactotronConfig.ts\")\n}\nimport \"./utils/gestureHandler\"\n\nimport { useEffect, useState } from \"react\"\nimport { useFonts } from \"expo-font\"\nimport * as Linking from \"expo-linking\"\nimport { KeyboardProvider } from \"react-native-keyboard-controller\"\nimport { initialWindowMetrics, SafeAreaProvider } from \"react-native-safe-area-context\"\n\nimport { AuthProvider } from \"./context/AuthContext\" // @demo remove-current-line\nimport { initI18n } from \"./i18n\"\nimport { AppNavigator } from \"./navigators/AppNavigator\"\nimport { useNavigationPersistence } from \"./navigators/navigationUtilities\"\nimport { ThemeProvider } from \"./theme/context\"\nimport { customFontsToLoad } from \"./theme/typography\"\nimport { loadDateFnsLocale } from \"./utils/formatDate\"\nimport * as storage from \"./utils/storage\"\n\nexport const NAVIGATION_PERSISTENCE_KEY = \"NAVIGATION_STATE\"\n\n// Web linking configuration\nconst prefix = Linking.createURL(\"/\")\nconst config = {\n  screens: {\n    Login: {\n      path: \"\",\n    },\n    Welcome: \"welcome\",\n    Demo: {\n      screens: {\n        DemoShowroom: {\n          path: \"showroom/:queryIndex?/:itemIndex?\",\n        },\n        DemoDebug: \"debug\",\n        DemoPodcastList: \"podcast\",\n        DemoCommunity: \"community\",\n      },\n    },\n  },\n}\n\n/**\n * This is the root component of our app.\n * @param {AppProps} props - The props for the `App` component.\n * @returns {JSX.Element} The rendered `App` component.\n */\nexport function App() {\n  const {\n    initialNavigationState,\n    onNavigationStateChange,\n    isRestored: isNavigationStateRestored,\n  } = useNavigationPersistence(storage, NAVIGATION_PERSISTENCE_KEY)\n\n  const [areFontsLoaded, fontLoadError] = useFonts(customFontsToLoad)\n  const [isI18nInitialized, setIsI18nInitialized] = useState(false)\n\n  useEffect(() => {\n    initI18n()\n      .then(() => setIsI18nInitialized(true))\n      .then(() => loadDateFnsLocale())\n  }, [])\n\n  // Before we show the app, we have to wait for our state to be ready.\n  // In the meantime, don't render anything. This will be the background\n  // color set in native by rootView's background color.\n  // In iOS: application:didFinishLaunchingWithOptions:\n  // In Android: https://stackoverflow.com/a/45838109/204044\n  // You can replace with your own loading component if you wish.\n  if (!isNavigationStateRestored || !isI18nInitialized || (!areFontsLoaded && !fontLoadError)) {\n    return null\n  }\n\n  const linking = {\n    prefixes: [prefix],\n    config,\n  }\n\n  // otherwise, we're ready to render the app\n  return (\n    <SafeAreaProvider initialMetrics={initialWindowMetrics}>\n      <KeyboardProvider>\n        {/* @demo remove-block-start */}\n        <AuthProvider>\n          {/* @demo remove-block-end */}\n          <ThemeProvider>\n            <AppNavigator\n              linking={linking}\n              initialState={initialNavigationState}\n              onStateChange={onNavigationStateChange}\n            />\n          </ThemeProvider>\n          {/* @demo remove-block-start */}\n        </AuthProvider>\n        {/* @demo remove-block-end */}\n      </KeyboardProvider>\n    </SafeAreaProvider>\n  )\n}\n"
  },
  {
    "path": "boilerplate/app/components/AutoImage.tsx",
    "content": "import { useLayoutEffect, useState } from \"react\"\nimport { Image, ImageProps, ImageURISource, Platform, PixelRatio } from \"react-native\"\n\nexport interface AutoImageProps extends ImageProps {\n  /**\n   * How wide should the image be?\n   */\n  maxWidth?: number\n  /**\n   * How tall should the image be?\n   */\n  maxHeight?: number\n  headers?: {\n    [key: string]: string\n  }\n}\n\n/**\n * A hook that will return the scaled dimensions of an image based on the\n * provided dimensions' aspect ratio. If no desired dimensions are provided,\n * it will return the original dimensions of the remote image.\n *\n * How is this different from `resizeMode: 'contain'`? Firstly, you can\n * specify only one side's size (not both). Secondly, the image will scale to fit\n * the desired dimensions instead of just being contained within its image-container.\n * @param {number} remoteUri - The URI of the remote image.\n * @param {number} dimensions - The desired dimensions of the image. If not provided, the original dimensions will be returned.\n * @returns {[number, number]} - The scaled dimensions of the image.\n */\nexport function useAutoImage(\n  remoteUri: string,\n  headers?: {\n    [key: string]: string\n  },\n  dimensions?: [maxWidth?: number, maxHeight?: number],\n): [width: number, height: number] {\n  const [[remoteWidth, remoteHeight], setRemoteImageDimensions] = useState([0, 0])\n  const remoteAspectRatio = remoteWidth / remoteHeight\n  const [maxWidth, maxHeight] = dimensions ?? []\n\n  useLayoutEffect(() => {\n    if (!remoteUri) return\n\n    if (!headers) {\n      Image.getSize(remoteUri, (w, h) => setRemoteImageDimensions([w, h]))\n    } else {\n      Image.getSizeWithHeaders(remoteUri, headers, (w, h) => setRemoteImageDimensions([w, h]))\n    }\n  }, [remoteUri, headers])\n\n  if (Number.isNaN(remoteAspectRatio)) return [0, 0]\n\n  if (maxWidth && maxHeight) {\n    const aspectRatio = Math.min(maxWidth / remoteWidth, maxHeight / remoteHeight)\n    return [\n      PixelRatio.roundToNearestPixel(remoteWidth * aspectRatio),\n      PixelRatio.roundToNearestPixel(remoteHeight * aspectRatio),\n    ]\n  } else if (maxWidth) {\n    return [maxWidth, PixelRatio.roundToNearestPixel(maxWidth / remoteAspectRatio)]\n  } else if (maxHeight) {\n    return [PixelRatio.roundToNearestPixel(maxHeight * remoteAspectRatio), maxHeight]\n  } else {\n    return [remoteWidth, remoteHeight]\n  }\n}\n\n/**\n * An Image component that automatically sizes a remote or data-uri image.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/AutoImage/}\n * @param {AutoImageProps} props - The props for the `AutoImage` component.\n * @returns {JSX.Element} The rendered `AutoImage` component.\n */\nexport function AutoImage(props: AutoImageProps) {\n  const { maxWidth, maxHeight, ...ImageProps } = props\n  const source = props.source as ImageURISource\n  const headers = source?.headers\n\n  const [width, height] = useAutoImage(\n    Platform.select({\n      web: (source?.uri as string) ?? (source as string),\n      default: source?.uri as string,\n    }),\n    headers,\n    [maxWidth, maxHeight],\n  )\n\n  return <Image {...ImageProps} style={[{ width, height }, props.style]} />\n}\n"
  },
  {
    "path": "boilerplate/app/components/Button.tsx",
    "content": "import { ComponentType } from \"react\"\nimport {\n  Pressable,\n  PressableProps,\n  PressableStateCallbackType,\n  StyleProp,\n  TextStyle,\n  ViewStyle,\n} from \"react-native\"\n\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\nimport type { ThemedStyle, ThemedStyleArray } from \"@/theme/types\"\n\nimport { Text, TextProps } from \"./Text\"\n\ntype Presets = \"default\" | \"filled\" | \"reversed\"\n\nexport interface ButtonAccessoryProps {\n  style: StyleProp<any>\n  pressableState: PressableStateCallbackType\n  disabled?: boolean\n}\n\nexport interface ButtonProps extends PressableProps {\n  /**\n   * Text which is looked up via i18n.\n   */\n  tx?: TextProps[\"tx\"]\n  /**\n   * The text to display if not using `tx` or nested components.\n   */\n  text?: TextProps[\"text\"]\n  /**\n   * Optional options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  txOptions?: TextProps[\"txOptions\"]\n  /**\n   * An optional style override useful for padding & margin.\n   */\n  style?: StyleProp<ViewStyle>\n  /**\n   * An optional style override for the \"pressed\" state.\n   */\n  pressedStyle?: StyleProp<ViewStyle>\n  /**\n   * An optional style override for the button text.\n   */\n  textStyle?: StyleProp<TextStyle>\n  /**\n   * An optional style override for the button text when in the \"pressed\" state.\n   */\n  pressedTextStyle?: StyleProp<TextStyle>\n  /**\n   * An optional style override for the button text when in the \"disabled\" state.\n   */\n  disabledTextStyle?: StyleProp<TextStyle>\n  /**\n   * One of the different types of button presets.\n   */\n  preset?: Presets\n  /**\n   * An optional component to render on the right side of the text.\n   * Example: `RightAccessory={(props) => <View {...props} />}`\n   */\n  RightAccessory?: ComponentType<ButtonAccessoryProps>\n  /**\n   * An optional component to render on the left side of the text.\n   * Example: `LeftAccessory={(props) => <View {...props} />}`\n   */\n  LeftAccessory?: ComponentType<ButtonAccessoryProps>\n  /**\n   * Children components.\n   */\n  children?: React.ReactNode\n  /**\n   * disabled prop, accessed directly for declarative styling reasons.\n   * https://reactnative.dev/docs/pressable#disabled\n   */\n  disabled?: boolean\n  /**\n   * An optional style override for the disabled state\n   */\n  disabledStyle?: StyleProp<ViewStyle>\n}\n\n/**\n * A component that allows users to take actions and make choices.\n * Wraps the Text component with a Pressable component.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Button/}\n * @param {ButtonProps} props - The props for the `Button` component.\n * @returns {JSX.Element} The rendered `Button` component.\n * @example\n * <Button\n *   tx=\"common:ok\"\n *   style={styles.button}\n *   textStyle={styles.buttonText}\n *   onPress={handleButtonPress}\n * />\n */\nexport function Button(props: ButtonProps) {\n  const {\n    tx,\n    text,\n    txOptions,\n    style: $viewStyleOverride,\n    pressedStyle: $pressedViewStyleOverride,\n    textStyle: $textStyleOverride,\n    pressedTextStyle: $pressedTextStyleOverride,\n    disabledTextStyle: $disabledTextStyleOverride,\n    children,\n    RightAccessory,\n    LeftAccessory,\n    disabled,\n    disabledStyle: $disabledViewStyleOverride,\n    ...rest\n  } = props\n\n  const { themed } = useAppTheme()\n\n  const preset: Presets = props.preset ?? \"default\"\n  /**\n   * @param {PressableStateCallbackType} root0 - The root object containing the pressed state.\n   * @param {boolean} root0.pressed - The pressed state.\n   * @returns {StyleProp<ViewStyle>} The view style based on the pressed state.\n   */\n  function $viewStyle({ pressed }: PressableStateCallbackType): StyleProp<ViewStyle> {\n    return [\n      themed($viewPresets[preset]),\n      $viewStyleOverride,\n      !!pressed && themed([$pressedViewPresets[preset], $pressedViewStyleOverride]),\n      !!disabled && $disabledViewStyleOverride,\n    ]\n  }\n  /**\n   * @param {PressableStateCallbackType} root0 - The root object containing the pressed state.\n   * @param {boolean} root0.pressed - The pressed state.\n   * @returns {StyleProp<TextStyle>} The text style based on the pressed state.\n   */\n  function $textStyle({ pressed }: PressableStateCallbackType): StyleProp<TextStyle> {\n    return [\n      themed($textPresets[preset]),\n      $textStyleOverride,\n      !!pressed && themed([$pressedTextPresets[preset], $pressedTextStyleOverride]),\n      !!disabled && $disabledTextStyleOverride,\n    ]\n  }\n\n  return (\n    <Pressable\n      style={$viewStyle}\n      accessibilityRole=\"button\"\n      accessibilityState={{ disabled: !!disabled }}\n      {...rest}\n      disabled={disabled}\n    >\n      {(state) => (\n        <>\n          {!!LeftAccessory && (\n            <LeftAccessory style={$leftAccessoryStyle} pressableState={state} disabled={disabled} />\n          )}\n\n          <Text tx={tx} text={text} txOptions={txOptions} style={$textStyle(state)}>\n            {children}\n          </Text>\n\n          {!!RightAccessory && (\n            <RightAccessory\n              style={$rightAccessoryStyle}\n              pressableState={state}\n              disabled={disabled}\n            />\n          )}\n        </>\n      )}\n    </Pressable>\n  )\n}\n\nconst $baseViewStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  minHeight: 56,\n  borderRadius: 4,\n  justifyContent: \"center\",\n  alignItems: \"center\",\n  paddingVertical: spacing.sm,\n  paddingHorizontal: spacing.sm,\n  overflow: \"hidden\",\n})\n\nconst $baseTextStyle: ThemedStyle<TextStyle> = ({ typography }) => ({\n  fontSize: 16,\n  lineHeight: 20,\n  fontFamily: typography.primary.medium,\n  textAlign: \"center\",\n  flexShrink: 1,\n  flexGrow: 0,\n  zIndex: 2,\n})\n\nconst $rightAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginStart: spacing.xs,\n  zIndex: 1,\n})\nconst $leftAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginEnd: spacing.xs,\n  zIndex: 1,\n})\n\nconst $viewPresets: Record<Presets, ThemedStyleArray<ViewStyle>> = {\n  default: [\n    $styles.row,\n    $baseViewStyle,\n    ({ colors }) => ({\n      borderWidth: 1,\n      borderColor: colors.palette.neutral400,\n      backgroundColor: colors.palette.neutral100,\n    }),\n  ],\n  filled: [\n    $styles.row,\n    $baseViewStyle,\n    ({ colors }) => ({ backgroundColor: colors.palette.neutral300 }),\n  ],\n  reversed: [\n    $styles.row,\n    $baseViewStyle,\n    ({ colors }) => ({ backgroundColor: colors.palette.neutral800 }),\n  ],\n}\n\nconst $textPresets: Record<Presets, ThemedStyleArray<TextStyle>> = {\n  default: [$baseTextStyle],\n  filled: [$baseTextStyle],\n  reversed: [$baseTextStyle, ({ colors }) => ({ color: colors.palette.neutral100 })],\n}\n\nconst $pressedViewPresets: Record<Presets, ThemedStyle<ViewStyle>> = {\n  default: ({ colors }) => ({ backgroundColor: colors.palette.neutral200 }),\n  filled: ({ colors }) => ({ backgroundColor: colors.palette.neutral400 }),\n  reversed: ({ colors }) => ({ backgroundColor: colors.palette.neutral700 }),\n}\n\nconst $pressedTextPresets: Record<Presets, ThemedStyle<TextStyle>> = {\n  default: () => ({ opacity: 0.9 }),\n  filled: () => ({ opacity: 0.9 }),\n  reversed: () => ({ opacity: 0.9 }),\n}\n"
  },
  {
    "path": "boilerplate/app/components/Card.tsx",
    "content": "import { ComponentType, Fragment, ReactElement } from \"react\"\nimport {\n  StyleProp,\n  TextStyle,\n  TouchableOpacity,\n  TouchableOpacityProps,\n  View,\n  ViewProps,\n  ViewStyle,\n} from \"react-native\"\n\nimport type { ThemedStyle, ThemedStyleArray } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { Text, TextProps } from \"./Text\"\n\ntype Presets = \"default\" | \"reversed\"\n\ninterface CardProps extends TouchableOpacityProps {\n  /**\n   * One of the different types of text presets.\n   */\n  preset?: Presets\n  /**\n   * How the content should be aligned vertically. This is especially (but not exclusively) useful\n   * when the card is a fixed height but the content is dynamic.\n   *\n   * `top` (default) - aligns all content to the top.\n   * `center` - aligns all content to the center.\n   * `space-between` - spreads out the content evenly.\n   * `force-footer-bottom` - aligns all content to the top, but forces the footer to the bottom.\n   */\n  verticalAlignment?: \"top\" | \"center\" | \"space-between\" | \"force-footer-bottom\"\n  /**\n   * Custom component added to the left of the card body.\n   */\n  LeftComponent?: ReactElement\n  /**\n   * Custom component added to the right of the card body.\n   */\n  RightComponent?: ReactElement\n  /**\n   * The heading text to display if not using `headingTx`.\n   */\n  heading?: TextProps[\"text\"]\n  /**\n   * Heading text which is looked up via i18n.\n   */\n  headingTx?: TextProps[\"tx\"]\n  /**\n   * Optional heading options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  headingTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Style overrides for heading text.\n   */\n  headingStyle?: StyleProp<TextStyle>\n  /**\n   * Pass any additional props directly to the heading Text component.\n   */\n  HeadingTextProps?: TextProps\n  /**\n   * Custom heading component.\n   * Overrides all other `heading*` props.\n   */\n  HeadingComponent?: ReactElement\n  /**\n   * The content text to display if not using `contentTx`.\n   */\n  content?: TextProps[\"text\"]\n  /**\n   * Content text which is looked up via i18n.\n   */\n  contentTx?: TextProps[\"tx\"]\n  /**\n   * Optional content options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  contentTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Style overrides for content text.\n   */\n  contentStyle?: StyleProp<TextStyle>\n  /**\n   * Pass any additional props directly to the content Text component.\n   */\n  ContentTextProps?: TextProps\n  /**\n   * Custom content component.\n   * Overrides all other `content*` props.\n   */\n  ContentComponent?: ReactElement\n  /**\n   * The footer text to display if not using `footerTx`.\n   */\n  footer?: TextProps[\"text\"]\n  /**\n   * Footer text which is looked up via i18n.\n   */\n  footerTx?: TextProps[\"tx\"]\n  /**\n   * Optional footer options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  footerTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Style overrides for footer text.\n   */\n  footerStyle?: StyleProp<TextStyle>\n  /**\n   * Pass any additional props directly to the footer Text component.\n   */\n  FooterTextProps?: TextProps\n  /**\n   * Custom footer component.\n   * Overrides all other `footer*` props.\n   */\n  FooterComponent?: ReactElement\n}\n\n/**\n * Cards are useful for displaying related information in a contained way.\n * If a ListItem displays content horizontally, a Card can be used to display content vertically.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Card/}\n * @param {CardProps} props - The props for the `Card` component.\n * @returns {JSX.Element} The rendered `Card` component.\n */\nexport function Card(props: CardProps) {\n  const {\n    content,\n    contentTx,\n    contentTxOptions,\n    footer,\n    footerTx,\n    footerTxOptions,\n    heading,\n    headingTx,\n    headingTxOptions,\n    ContentComponent,\n    HeadingComponent,\n    FooterComponent,\n    LeftComponent,\n    RightComponent,\n    verticalAlignment = \"top\",\n    style: $containerStyleOverride,\n    contentStyle: $contentStyleOverride,\n    headingStyle: $headingStyleOverride,\n    footerStyle: $footerStyleOverride,\n    ContentTextProps,\n    HeadingTextProps,\n    FooterTextProps,\n    ...WrapperProps\n  } = props\n\n  const {\n    themed,\n    theme: { spacing },\n  } = useAppTheme()\n\n  const preset: Presets = props.preset ?? \"default\"\n  const isPressable = !!WrapperProps.onPress\n  const isHeadingPresent = !!(HeadingComponent || heading || headingTx)\n  const isContentPresent = !!(ContentComponent || content || contentTx)\n  const isFooterPresent = !!(FooterComponent || footer || footerTx)\n\n  const Wrapper = (isPressable ? TouchableOpacity : View) as ComponentType<\n    TouchableOpacityProps | ViewProps\n  >\n  const HeaderContentWrapper = verticalAlignment === \"force-footer-bottom\" ? View : Fragment\n\n  const $containerStyle: StyleProp<ViewStyle> = [\n    themed($containerPresets[preset]),\n    $containerStyleOverride,\n  ]\n  const $headingStyle = [\n    themed($headingPresets[preset]),\n    (isFooterPresent || isContentPresent) && { marginBottom: spacing.xxxs },\n    $headingStyleOverride,\n    HeadingTextProps?.style,\n  ]\n  const $contentStyle = [\n    themed($contentPresets[preset]),\n    isHeadingPresent && { marginTop: spacing.xxxs },\n    isFooterPresent && { marginBottom: spacing.xxxs },\n    $contentStyleOverride,\n    ContentTextProps?.style,\n  ]\n  const $footerStyle = [\n    themed($footerPresets[preset]),\n    (isHeadingPresent || isContentPresent) && { marginTop: spacing.xxxs },\n    $footerStyleOverride,\n    FooterTextProps?.style,\n  ]\n  const $alignmentWrapperStyle = [\n    $alignmentWrapper,\n    { justifyContent: $alignmentWrapperFlexOptions[verticalAlignment] },\n    LeftComponent && { marginStart: spacing.md },\n    RightComponent && { marginEnd: spacing.md },\n  ]\n\n  return (\n    <Wrapper\n      style={$containerStyle}\n      activeOpacity={0.8}\n      accessibilityRole={isPressable ? \"button\" : undefined}\n      {...WrapperProps}\n    >\n      {LeftComponent}\n\n      <View style={$alignmentWrapperStyle}>\n        <HeaderContentWrapper>\n          {HeadingComponent ||\n            (isHeadingPresent && (\n              <Text\n                weight=\"bold\"\n                text={heading}\n                tx={headingTx}\n                txOptions={headingTxOptions}\n                {...HeadingTextProps}\n                style={$headingStyle}\n              />\n            ))}\n\n          {ContentComponent ||\n            (isContentPresent && (\n              <Text\n                weight=\"normal\"\n                text={content}\n                tx={contentTx}\n                txOptions={contentTxOptions}\n                {...ContentTextProps}\n                style={$contentStyle}\n              />\n            ))}\n        </HeaderContentWrapper>\n\n        {FooterComponent ||\n          (isFooterPresent && (\n            <Text\n              weight=\"normal\"\n              size=\"xs\"\n              text={footer}\n              tx={footerTx}\n              txOptions={footerTxOptions}\n              {...FooterTextProps}\n              style={$footerStyle}\n            />\n          ))}\n      </View>\n\n      {RightComponent}\n    </Wrapper>\n  )\n}\n\nconst $containerBase: ThemedStyle<ViewStyle> = (theme) => ({\n  borderRadius: theme.spacing.md,\n  padding: theme.spacing.xs,\n  borderWidth: 1,\n  shadowColor: theme.colors.palette.neutral800,\n  shadowOffset: { width: 0, height: 12 },\n  shadowOpacity: 0.08,\n  shadowRadius: 12.81,\n  elevation: 16,\n  minHeight: 96,\n})\n\nconst $alignmentWrapper: ViewStyle = {\n  flex: 1,\n  alignSelf: \"stretch\",\n}\n\nconst $alignmentWrapperFlexOptions = {\n  \"top\": \"flex-start\",\n  \"center\": \"center\",\n  \"space-between\": \"space-between\",\n  \"force-footer-bottom\": \"space-between\",\n} as const\n\nconst $containerPresets: Record<Presets, ThemedStyleArray<ViewStyle>> = {\n  default: [\n    $styles.row,\n    $containerBase,\n    (theme) => ({\n      backgroundColor: theme.colors.palette.neutral100,\n      borderColor: theme.colors.palette.neutral300,\n    }),\n  ],\n  reversed: [\n    $styles.row,\n    $containerBase,\n    (theme) => ({\n      backgroundColor: theme.colors.palette.neutral800,\n      borderColor: theme.colors.palette.neutral500,\n    }),\n  ],\n}\n\nconst $headingPresets: Record<Presets, ThemedStyleArray<TextStyle>> = {\n  default: [],\n  reversed: [(theme) => ({ color: theme.colors.palette.neutral100 })],\n}\n\nconst $contentPresets: Record<Presets, ThemedStyleArray<TextStyle>> = {\n  default: [],\n  reversed: [(theme) => ({ color: theme.colors.palette.neutral100 })],\n}\n\nconst $footerPresets: Record<Presets, ThemedStyleArray<TextStyle>> = {\n  default: [],\n  reversed: [(theme) => ({ color: theme.colors.palette.neutral100 })],\n}\n"
  },
  {
    "path": "boilerplate/app/components/EmptyState.tsx",
    "content": "import { Image, ImageProps, ImageStyle, StyleProp, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { translate } from \"@/i18n/translate\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\n\nimport { Button, ButtonProps } from \"./Button\"\nimport { Text, TextProps } from \"./Text\"\n\nconst sadFace = require(\"@assets/images/sad-face.png\")\n\ninterface EmptyStateProps {\n  /**\n   * An optional prop that specifies the text/image set to use for the empty state.\n   */\n  preset?: \"generic\"\n  /**\n   * Style override for the container.\n   */\n  style?: StyleProp<ViewStyle>\n  /**\n   * An Image source to be displayed above the heading.\n   */\n  imageSource?: ImageProps[\"source\"]\n  /**\n   * Style overrides for image.\n   */\n  imageStyle?: StyleProp<ImageStyle>\n  /**\n   * Pass any additional props directly to the Image component.\n   */\n  ImageProps?: Omit<ImageProps, \"source\">\n  /**\n   * The heading text to display if not using `headingTx`.\n   */\n  heading?: TextProps[\"text\"]\n  /**\n   * Heading text which is looked up via i18n.\n   */\n  headingTx?: TextProps[\"tx\"]\n  /**\n   * Optional heading options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  headingTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Style overrides for heading text.\n   */\n  headingStyle?: StyleProp<TextStyle>\n  /**\n   * Pass any additional props directly to the heading Text component.\n   */\n  HeadingTextProps?: TextProps\n  /**\n   * The content text to display if not using `contentTx`.\n   */\n  content?: TextProps[\"text\"]\n  /**\n   * Content text which is looked up via i18n.\n   */\n  contentTx?: TextProps[\"tx\"]\n  /**\n   * Optional content options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  contentTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Style overrides for content text.\n   */\n  contentStyle?: StyleProp<TextStyle>\n  /**\n   * Pass any additional props directly to the content Text component.\n   */\n  ContentTextProps?: TextProps\n  /**\n   * The button text to display if not using `buttonTx`.\n   */\n  button?: TextProps[\"text\"]\n  /**\n   * Button text which is looked up via i18n.\n   */\n  buttonTx?: TextProps[\"tx\"]\n  /**\n   * Optional button options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  buttonTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Style overrides for button.\n   */\n  buttonStyle?: ButtonProps[\"style\"]\n  /**\n   * Style overrides for button text.\n   */\n  buttonTextStyle?: ButtonProps[\"textStyle\"]\n  /**\n   * Called when the button is pressed.\n   */\n  buttonOnPress?: ButtonProps[\"onPress\"]\n  /**\n   * Pass any additional props directly to the Button component.\n   */\n  ButtonProps?: ButtonProps\n}\n\ninterface EmptyStatePresetItem {\n  imageSource: ImageProps[\"source\"]\n  heading: TextProps[\"text\"]\n  content: TextProps[\"text\"]\n  button: TextProps[\"text\"]\n}\n\n/**\n * A component to use when there is no data to display. It can be utilized to direct the user what to do next.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/EmptyState/}\n * @param {EmptyStateProps} props - The props for the `EmptyState` component.\n * @returns {JSX.Element} The rendered `EmptyState` component.\n */\nexport function EmptyState(props: EmptyStateProps) {\n  const {\n    theme,\n    themed,\n    theme: { spacing },\n  } = useAppTheme()\n\n  const EmptyStatePresets = {\n    generic: {\n      imageSource: sadFace,\n      heading: translate(\"emptyStateComponent:generic.heading\"),\n      content: translate(\"emptyStateComponent:generic.content\"),\n      button: translate(\"emptyStateComponent:generic.button\"),\n    } as EmptyStatePresetItem,\n  } as const\n\n  const preset = EmptyStatePresets[props.preset ?? \"generic\"]\n\n  const {\n    button = preset.button,\n    buttonTx,\n    buttonOnPress,\n    buttonTxOptions,\n    content = preset.content,\n    contentTx,\n    contentTxOptions,\n    heading = preset.heading,\n    headingTx,\n    headingTxOptions,\n    imageSource = preset.imageSource,\n    style: $containerStyleOverride,\n    buttonStyle: $buttonStyleOverride,\n    buttonTextStyle: $buttonTextStyleOverride,\n    contentStyle: $contentStyleOverride,\n    headingStyle: $headingStyleOverride,\n    imageStyle: $imageStyleOverride,\n    ButtonProps,\n    ContentTextProps,\n    HeadingTextProps,\n    ImageProps,\n  } = props\n\n  const isImagePresent = !!imageSource\n  const isHeadingPresent = !!(heading || headingTx)\n  const isContentPresent = !!(content || contentTx)\n  const isButtonPresent = !!(button || buttonTx)\n\n  const $containerStyles = [$containerStyleOverride]\n  const $imageStyles = [\n    $image,\n    (isHeadingPresent || isContentPresent || isButtonPresent) && { marginBottom: spacing.xxxs },\n    $imageStyleOverride,\n    ImageProps?.style,\n  ]\n  const $headingStyles = [\n    themed($heading),\n    isImagePresent && { marginTop: spacing.xxxs },\n    (isContentPresent || isButtonPresent) && { marginBottom: spacing.xxxs },\n    $headingStyleOverride,\n    HeadingTextProps?.style,\n  ]\n  const $contentStyles = [\n    themed($content),\n    (isImagePresent || isHeadingPresent) && { marginTop: spacing.xxxs },\n    isButtonPresent && { marginBottom: spacing.xxxs },\n    $contentStyleOverride,\n    ContentTextProps?.style,\n  ]\n  const $buttonStyles = [\n    (isImagePresent || isHeadingPresent || isContentPresent) && { marginTop: spacing.xl },\n    $buttonStyleOverride,\n    ButtonProps?.style,\n  ]\n\n  return (\n    <View style={$containerStyles}>\n      {isImagePresent && (\n        <Image\n          source={imageSource}\n          {...ImageProps}\n          style={$imageStyles}\n          tintColor={theme.colors.palette.neutral900}\n        />\n      )}\n\n      {isHeadingPresent && (\n        <Text\n          preset=\"subheading\"\n          text={heading}\n          tx={headingTx}\n          txOptions={headingTxOptions}\n          {...HeadingTextProps}\n          style={$headingStyles}\n        />\n      )}\n\n      {isContentPresent && (\n        <Text\n          text={content}\n          tx={contentTx}\n          txOptions={contentTxOptions}\n          {...ContentTextProps}\n          style={$contentStyles}\n        />\n      )}\n\n      {isButtonPresent && (\n        <Button\n          onPress={buttonOnPress}\n          text={button}\n          tx={buttonTx}\n          txOptions={buttonTxOptions}\n          textStyle={$buttonTextStyleOverride}\n          {...ButtonProps}\n          style={$buttonStyles}\n        />\n      )}\n    </View>\n  )\n}\n\nconst $image: ImageStyle = { alignSelf: \"center\" }\nconst $heading: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  textAlign: \"center\",\n  paddingHorizontal: spacing.lg,\n})\nconst $content: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  textAlign: \"center\",\n  paddingHorizontal: spacing.lg,\n})\n"
  },
  {
    "path": "boilerplate/app/components/Header.tsx",
    "content": "import { ReactElement } from \"react\"\nimport {\n  StyleProp,\n  TextStyle,\n  TouchableOpacity,\n  TouchableOpacityProps,\n  View,\n  ViewStyle,\n} from \"react-native\"\n\nimport { isRTL } from \"@/i18n\"\nimport { translate } from \"@/i18n/translate\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\nimport { ExtendedEdge, useSafeAreaInsetsStyle } from \"@/utils/useSafeAreaInsetsStyle\"\n\nimport { IconTypes, PressableIcon } from \"./Icon\"\nimport { Text, TextProps } from \"./Text\"\n\nexport interface HeaderProps {\n  /**\n   * The layout of the title relative to the action components.\n   * - `center` will force the title to always be centered relative to the header. If the title or the action buttons are too long, the title will be cut off.\n   * - `flex` will attempt to center the title relative to the action buttons. If the action buttons are different widths, the title will be off-center relative to the header.\n   */\n  titleMode?: \"center\" | \"flex\"\n  /**\n   * Optional title style override.\n   */\n  titleStyle?: StyleProp<TextStyle>\n  /**\n   * Optional outer title container style override.\n   */\n  titleContainerStyle?: StyleProp<ViewStyle>\n  /**\n   * Optional inner header wrapper style override.\n   */\n  style?: StyleProp<ViewStyle>\n  /**\n   * Optional outer header container style override.\n   */\n  containerStyle?: StyleProp<ViewStyle>\n  /**\n   * Background color\n   */\n  backgroundColor?: string\n  /**\n   * Title text to display if not using `tx` or nested components.\n   */\n  title?: TextProps[\"text\"]\n  /**\n   * Title text which is looked up via i18n.\n   */\n  titleTx?: TextProps[\"tx\"]\n  /**\n   * Optional options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  titleTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Icon that should appear on the left.\n   * Can be used with `onLeftPress`.\n   */\n  leftIcon?: IconTypes\n  /**\n   * An optional tint color for the left icon\n   */\n  leftIconColor?: string\n  /**\n   * Left action text to display if not using `leftTx`.\n   * Can be used with `onLeftPress`. Overrides `leftIcon`.\n   */\n  leftText?: TextProps[\"text\"]\n  /**\n   * Left action text text which is looked up via i18n.\n   * Can be used with `onLeftPress`. Overrides `leftIcon`.\n   */\n  leftTx?: TextProps[\"tx\"]\n  /**\n   * Left action custom ReactElement if the built in action props don't suffice.\n   * Overrides `leftIcon`, `leftTx` and `leftText`.\n   */\n  LeftActionComponent?: ReactElement\n  /**\n   * Optional options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  leftTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * What happens when you press the left icon or text action.\n   */\n  onLeftPress?: TouchableOpacityProps[\"onPress\"]\n  /**\n   * Icon that should appear on the right.\n   * Can be used with `onRightPress`.\n   */\n  rightIcon?: IconTypes\n  /**\n   * An optional tint color for the right icon\n   */\n  rightIconColor?: string\n  /**\n   * Right action text to display if not using `rightTx`.\n   * Can be used with `onRightPress`. Overrides `rightIcon`.\n   */\n  rightText?: TextProps[\"text\"]\n  /**\n   * Right action text text which is looked up via i18n.\n   * Can be used with `onRightPress`. Overrides `rightIcon`.\n   */\n  rightTx?: TextProps[\"tx\"]\n  /**\n   * Right action custom ReactElement if the built in action props don't suffice.\n   * Overrides `rightIcon`, `rightTx` and `rightText`.\n   */\n  RightActionComponent?: ReactElement\n  /**\n   * Optional options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  rightTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * What happens when you press the right icon or text action.\n   */\n  onRightPress?: TouchableOpacityProps[\"onPress\"]\n  /**\n   * Override the default edges for the safe area.\n   */\n  safeAreaEdges?: ExtendedEdge[]\n}\n\ninterface HeaderActionProps {\n  backgroundColor?: string\n  icon?: IconTypes\n  iconColor?: string\n  text?: TextProps[\"text\"]\n  tx?: TextProps[\"tx\"]\n  txOptions?: TextProps[\"txOptions\"]\n  onPress?: TouchableOpacityProps[\"onPress\"]\n  ActionComponent?: ReactElement\n}\n\n/**\n * Header that appears on many screens. Will hold navigation buttons and screen title.\n * The Header is meant to be used with the `screenOptions.header` option on navigators, routes, or screen components via `navigation.setOptions({ header })`.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Header/}\n * @param {HeaderProps} props - The props for the `Header` component.\n * @returns {JSX.Element} The rendered `Header` component.\n */\nexport function Header(props: HeaderProps) {\n  const {\n    theme: { colors },\n    themed,\n  } = useAppTheme()\n  const {\n    backgroundColor = colors.background,\n    LeftActionComponent,\n    leftIcon,\n    leftIconColor,\n    leftText,\n    leftTx,\n    leftTxOptions,\n    onLeftPress,\n    onRightPress,\n    RightActionComponent,\n    rightIcon,\n    rightIconColor,\n    rightText,\n    rightTx,\n    rightTxOptions,\n    safeAreaEdges = [\"top\"],\n    title,\n    titleMode = \"center\",\n    titleTx,\n    titleTxOptions,\n    titleContainerStyle: $titleContainerStyleOverride,\n    style: $styleOverride,\n    titleStyle: $titleStyleOverride,\n    containerStyle: $containerStyleOverride,\n  } = props\n\n  const $containerInsets = useSafeAreaInsetsStyle(safeAreaEdges)\n\n  const titleContent = titleTx ? translate(titleTx, titleTxOptions) : title\n\n  return (\n    <View style={[$container, $containerInsets, { backgroundColor }, $containerStyleOverride]}>\n      <View style={[$styles.row, $wrapper, $styleOverride]}>\n        <HeaderAction\n          tx={leftTx}\n          text={leftText}\n          icon={leftIcon}\n          iconColor={leftIconColor}\n          onPress={onLeftPress}\n          txOptions={leftTxOptions}\n          backgroundColor={backgroundColor}\n          ActionComponent={LeftActionComponent}\n        />\n\n        {!!titleContent && (\n          <View\n            style={[\n              $titleWrapperPointerEvents,\n              titleMode === \"center\" && themed($titleWrapperCenter),\n              titleMode === \"flex\" && $titleWrapperFlex,\n              $titleContainerStyleOverride,\n            ]}\n          >\n            <Text\n              weight=\"medium\"\n              size=\"md\"\n              text={titleContent}\n              style={[$title, $titleStyleOverride]}\n            />\n          </View>\n        )}\n\n        <HeaderAction\n          tx={rightTx}\n          text={rightText}\n          icon={rightIcon}\n          iconColor={rightIconColor}\n          onPress={onRightPress}\n          txOptions={rightTxOptions}\n          backgroundColor={backgroundColor}\n          ActionComponent={RightActionComponent}\n        />\n      </View>\n    </View>\n  )\n}\n\n/**\n * @param {HeaderActionProps} props - The props for the `HeaderAction` component.\n * @returns {JSX.Element} The rendered `HeaderAction` component.\n */\nfunction HeaderAction(props: HeaderActionProps) {\n  const { backgroundColor, icon, text, tx, txOptions, onPress, ActionComponent, iconColor } = props\n  const { themed } = useAppTheme()\n\n  const content = tx ? translate(tx, txOptions) : text\n\n  if (ActionComponent) return ActionComponent\n\n  if (content) {\n    return (\n      <TouchableOpacity\n        style={themed([$actionTextContainer, { backgroundColor }])}\n        onPress={onPress}\n        disabled={!onPress}\n        activeOpacity={0.8}\n      >\n        <Text weight=\"medium\" size=\"md\" text={content} style={themed($actionText)} />\n      </TouchableOpacity>\n    )\n  }\n\n  if (icon) {\n    return (\n      <PressableIcon\n        size={24}\n        icon={icon}\n        color={iconColor}\n        onPress={onPress}\n        containerStyle={themed([$actionIconContainer, { backgroundColor }])}\n        style={isRTL ? { transform: [{ rotate: \"180deg\" }] } : {}}\n      />\n    )\n  }\n\n  return <View style={[$actionFillerContainer, { backgroundColor }]} />\n}\n\nconst $wrapper: ViewStyle = {\n  height: 56,\n  alignItems: \"center\",\n  justifyContent: \"space-between\",\n}\n\nconst $container: ViewStyle = {\n  width: \"100%\",\n}\n\nconst $title: TextStyle = {\n  textAlign: \"center\",\n}\n\nconst $actionTextContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  flexGrow: 0,\n  alignItems: \"center\",\n  justifyContent: \"center\",\n  height: \"100%\",\n  paddingHorizontal: spacing.md,\n  zIndex: 2,\n})\n\nconst $actionText: ThemedStyle<TextStyle> = ({ colors }) => ({\n  color: colors.tint,\n})\n\nconst $actionIconContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  flexGrow: 0,\n  alignItems: \"center\",\n  justifyContent: \"center\",\n  height: \"100%\",\n  paddingHorizontal: spacing.md,\n  zIndex: 2,\n})\n\nconst $actionFillerContainer: ViewStyle = {\n  width: 16,\n}\n\nconst $titleWrapperPointerEvents: ViewStyle = {\n  pointerEvents: \"none\",\n}\n\nconst $titleWrapperCenter: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  alignItems: \"center\",\n  justifyContent: \"center\",\n  height: \"100%\",\n  width: \"100%\",\n  position: \"absolute\",\n  paddingHorizontal: spacing.xxl,\n  zIndex: 1,\n})\n\nconst $titleWrapperFlex: ViewStyle = {\n  justifyContent: \"center\",\n  flexGrow: 1,\n}\n"
  },
  {
    "path": "boilerplate/app/components/Icon.tsx",
    "content": "import {\n  Image,\n  ImageStyle,\n  StyleProp,\n  TouchableOpacity,\n  TouchableOpacityProps,\n  View,\n  ViewProps,\n  ViewStyle,\n} from \"react-native\"\n\nimport { useAppTheme } from \"@/theme/context\"\n\nexport type IconTypes = keyof typeof iconRegistry\n\ntype BaseIconProps = {\n  /**\n   * The name of the icon\n   */\n  icon: IconTypes\n\n  /**\n   * An optional tint color for the icon\n   */\n  color?: string\n\n  /**\n   * An optional size for the icon. If not provided, the icon will be sized to the icon's resolution.\n   */\n  size?: number\n\n  /**\n   * Style overrides for the icon image\n   */\n  style?: StyleProp<ImageStyle>\n\n  /**\n   * Style overrides for the icon container\n   */\n  containerStyle?: StyleProp<ViewStyle>\n}\n\ntype PressableIconProps = Omit<TouchableOpacityProps, \"style\"> & BaseIconProps\ntype IconProps = Omit<ViewProps, \"style\"> & BaseIconProps\n\n/**\n * A component to render a registered icon.\n * It is wrapped in a <TouchableOpacity />\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Icon/}\n * @param {PressableIconProps} props - The props for the `PressableIcon` component.\n * @returns {JSX.Element} The rendered `PressableIcon` component.\n */\nexport function PressableIcon(props: PressableIconProps) {\n  const {\n    icon,\n    color,\n    size,\n    style: $imageStyleOverride,\n    containerStyle: $containerStyleOverride,\n    ...pressableProps\n  } = props\n\n  const { theme } = useAppTheme()\n\n  const $imageStyle: StyleProp<ImageStyle> = [\n    $imageStyleBase,\n    { tintColor: color ?? theme.colors.text },\n    size !== undefined && { width: size, height: size },\n    $imageStyleOverride,\n  ]\n\n  return (\n    <TouchableOpacity {...pressableProps} style={$containerStyleOverride}>\n      <Image style={$imageStyle} source={iconRegistry[icon]} />\n    </TouchableOpacity>\n  )\n}\n\n/**\n * A component to render a registered icon.\n * It is wrapped in a <View />, use `PressableIcon` if you want to react to input\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Icon/}\n * @param {IconProps} props - The props for the `Icon` component.\n * @returns {JSX.Element} The rendered `Icon` component.\n */\nexport function Icon(props: IconProps) {\n  const {\n    icon,\n    color,\n    size,\n    style: $imageStyleOverride,\n    containerStyle: $containerStyleOverride,\n    ...viewProps\n  } = props\n\n  const { theme } = useAppTheme()\n\n  const $imageStyle: StyleProp<ImageStyle> = [\n    $imageStyleBase,\n    { tintColor: color ?? theme.colors.text },\n    size !== undefined && { width: size, height: size },\n    $imageStyleOverride,\n  ]\n\n  return (\n    <View {...viewProps} style={$containerStyleOverride}>\n      <Image style={$imageStyle} source={iconRegistry[icon]} />\n    </View>\n  )\n}\n\nexport const iconRegistry = {\n  back: require(\"@assets/icons/back.png\"),\n  bell: require(\"@assets/icons/bell.png\"),\n  caretLeft: require(\"@assets/icons/caretLeft.png\"),\n  caretRight: require(\"@assets/icons/caretRight.png\"),\n  check: require(\"@assets/icons/check.png\"),\n  clap: require(\"@assets/icons/demo/clap.png\"), // @demo remove-current-line\n  community: require(\"@assets/icons/demo/community.png\"), // @demo remove-current-line\n  components: require(\"@assets/icons/demo/components.png\"), // @demo remove-current-line\n  debug: require(\"@assets/icons/demo/debug.png\"), // @demo remove-current-line\n  github: require(\"@assets/icons/demo/github.png\"), // @demo remove-current-line\n  heart: require(\"@assets/icons/demo/heart.png\"), // @demo remove-current-line\n  hidden: require(\"@assets/icons/hidden.png\"),\n  ladybug: require(\"@assets/icons/ladybug.png\"),\n  lock: require(\"@assets/icons/lock.png\"),\n  menu: require(\"@assets/icons/menu.png\"),\n  more: require(\"@assets/icons/more.png\"),\n  pin: require(\"@assets/icons/demo/pin.png\"), // @demo remove-current-line\n  podcast: require(\"@assets/icons/demo/podcast.png\"), // @demo remove-current-line\n  settings: require(\"@assets/icons/settings.png\"),\n  slack: require(\"@assets/icons/demo/slack.png\"), // @demo remove-current-line\n  view: require(\"@assets/icons/view.png\"),\n  x: require(\"@assets/icons/x.png\"),\n}\n\nconst $imageStyleBase: ImageStyle = {\n  resizeMode: \"contain\",\n}\n"
  },
  {
    "path": "boilerplate/app/components/ListItem.tsx",
    "content": "import { forwardRef, ReactElement } from \"react\"\nimport {\n  StyleProp,\n  TextStyle,\n  TouchableOpacity,\n  TouchableOpacityProps,\n  View,\n  ViewStyle,\n} from \"react-native\"\n\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { Icon, IconTypes } from \"./Icon\"\nimport { Text, TextProps } from \"./Text\"\n\nexport interface ListItemProps extends TouchableOpacityProps {\n  /**\n   * How tall the list item should be.\n   * Default: 56\n   */\n  height?: number\n  /**\n   * Whether to show the top separator.\n   * Default: false\n   */\n  topSeparator?: boolean\n  /**\n   * Whether to show the bottom separator.\n   * Default: false\n   */\n  bottomSeparator?: boolean\n  /**\n   * Text to display if not using `tx` or nested components.\n   */\n  text?: TextProps[\"text\"]\n  /**\n   * Text which is looked up via i18n.\n   */\n  tx?: TextProps[\"tx\"]\n  /**\n   * Children components.\n   */\n  children?: TextProps[\"children\"]\n  /**\n   * Optional options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  txOptions?: TextProps[\"txOptions\"]\n  /**\n   * Optional text style override.\n   */\n  textStyle?: StyleProp<TextStyle>\n  /**\n   * Pass any additional props directly to the Text component.\n   */\n  TextProps?: TextProps\n  /**\n   * Optional View container style override.\n   */\n  containerStyle?: StyleProp<ViewStyle>\n  /**\n   * Optional TouchableOpacity style override.\n   */\n  style?: StyleProp<ViewStyle>\n  /**\n   * Icon that should appear on the left.\n   */\n  leftIcon?: IconTypes\n  /**\n   * An optional tint color for the left icon\n   */\n  leftIconColor?: string\n  /**\n   * Icon that should appear on the right.\n   */\n  rightIcon?: IconTypes\n  /**\n   * An optional tint color for the right icon\n   */\n  rightIconColor?: string\n  /**\n   * Right action custom ReactElement.\n   * Overrides `rightIcon`.\n   */\n  RightComponent?: ReactElement\n  /**\n   * Left action custom ReactElement.\n   * Overrides `leftIcon`.\n   */\n  LeftComponent?: ReactElement\n}\n\ninterface ListItemActionProps {\n  icon?: IconTypes\n  iconColor?: string\n  Component?: ReactElement\n  size: number\n  side: \"left\" | \"right\"\n}\n\n/**\n * A styled row component that can be used in FlatList, SectionList, or by itself.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/ListItem/}\n * @param {ListItemProps} props - The props for the `ListItem` component.\n * @returns {JSX.Element} The rendered `ListItem` component.\n */\nexport const ListItem = forwardRef<View, ListItemProps>(function ListItem(\n  props: ListItemProps,\n  ref,\n) {\n  const {\n    bottomSeparator,\n    children,\n    height = 56,\n    LeftComponent,\n    leftIcon,\n    leftIconColor,\n    RightComponent,\n    rightIcon,\n    rightIconColor,\n    style,\n    text,\n    TextProps,\n    topSeparator,\n    tx,\n    txOptions,\n    textStyle: $textStyleOverride,\n    containerStyle: $containerStyleOverride,\n    ...TouchableOpacityProps\n  } = props\n  const { themed } = useAppTheme()\n\n  const isTouchable =\n    TouchableOpacityProps.onPress !== undefined ||\n    TouchableOpacityProps.onPressIn !== undefined ||\n    TouchableOpacityProps.onPressOut !== undefined ||\n    TouchableOpacityProps.onLongPress !== undefined\n\n  const $textStyles = [$textStyle, $textStyleOverride, TextProps?.style]\n\n  const $containerStyles = [\n    topSeparator && $separatorTop,\n    bottomSeparator && $separatorBottom,\n    $containerStyleOverride,\n  ]\n\n  const $touchableStyles = [$styles.row, $touchableStyle, { minHeight: height }, style]\n\n  const Wrapper = isTouchable ? TouchableOpacity : View\n\n  return (\n    <View ref={ref} style={themed($containerStyles)}>\n      <Wrapper {...TouchableOpacityProps} style={$touchableStyles}>\n        <ListItemAction\n          side=\"left\"\n          size={height}\n          icon={leftIcon}\n          iconColor={leftIconColor}\n          Component={LeftComponent}\n        />\n\n        <Text {...TextProps} tx={tx} text={text} txOptions={txOptions} style={themed($textStyles)}>\n          {children}\n        </Text>\n\n        <ListItemAction\n          side=\"right\"\n          size={height}\n          icon={rightIcon}\n          iconColor={rightIconColor}\n          Component={RightComponent}\n        />\n      </Wrapper>\n    </View>\n  )\n})\n\n/**\n * @param {ListItemActionProps} props - The props for the `ListItemAction` component.\n * @returns {JSX.Element | null} The rendered `ListItemAction` component.\n */\nfunction ListItemAction(props: ListItemActionProps) {\n  const { icon, Component, iconColor, size, side } = props\n  const { themed } = useAppTheme()\n\n  const $iconContainerStyles = [$iconContainer]\n\n  if (Component) return Component\n\n  if (icon !== undefined) {\n    return (\n      <Icon\n        size={24}\n        icon={icon}\n        color={iconColor}\n        containerStyle={themed([\n          $iconContainerStyles,\n          side === \"left\" && $iconContainerLeft,\n          side === \"right\" && $iconContainerRight,\n          { height: size },\n        ])}\n      />\n    )\n  }\n\n  return null\n}\n\nconst $separatorTop: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  borderTopWidth: 1,\n  borderTopColor: colors.separator,\n})\n\nconst $separatorBottom: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  borderBottomWidth: 1,\n  borderBottomColor: colors.separator,\n})\n\nconst $textStyle: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  paddingVertical: spacing.xs,\n  alignSelf: \"center\",\n  flexGrow: 1,\n  flexShrink: 1,\n})\n\nconst $touchableStyle: ViewStyle = {\n  alignItems: \"flex-start\",\n}\n\nconst $iconContainer: ViewStyle = {\n  justifyContent: \"center\",\n  alignItems: \"center\",\n  flexGrow: 0,\n}\nconst $iconContainerLeft: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginEnd: spacing.md,\n})\n\nconst $iconContainerRight: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginStart: spacing.md,\n})\n"
  },
  {
    "path": "boilerplate/app/components/Screen.tsx",
    "content": "import { ReactNode, useRef, useState } from \"react\"\nimport {\n  KeyboardAvoidingView,\n  KeyboardAvoidingViewProps,\n  LayoutChangeEvent,\n  Platform,\n  ScrollViewProps,\n  StyleProp,\n  View,\n  ViewStyle,\n} from \"react-native\"\nimport { useScrollToTop } from \"@react-navigation/native\"\nimport { SystemBars, SystemBarsProps, SystemBarStyle } from \"react-native-edge-to-edge\"\nimport {\n  KeyboardAwareScrollView,\n  type KeyboardAwareScrollViewRef,\n} from \"react-native-keyboard-controller\"\n\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\nimport { ExtendedEdge, useSafeAreaInsetsStyle } from \"@/utils/useSafeAreaInsetsStyle\"\n\nexport const DEFAULT_BOTTOM_OFFSET = 50\n\ninterface BaseScreenProps {\n  /**\n   * Children components.\n   */\n  children?: ReactNode\n  /**\n   * Style for the outer content container useful for padding & margin.\n   */\n  style?: StyleProp<ViewStyle>\n  /**\n   * Style for the inner content container useful for padding & margin.\n   */\n  contentContainerStyle?: StyleProp<ViewStyle>\n  /**\n   * Override the default edges for the safe area.\n   */\n  safeAreaEdges?: ExtendedEdge[]\n  /**\n   * Background color\n   */\n  backgroundColor?: string\n  /**\n   * System bar setting. Defaults to dark.\n   */\n  systemBarStyle?: SystemBarStyle\n  /**\n   * By how much should we offset the keyboard? Defaults to 0.\n   */\n  keyboardOffset?: number\n  /**\n   * By how much we scroll up when the keyboard is shown. Defaults to 50.\n   */\n  keyboardBottomOffset?: number\n  /**\n   * Pass any additional props directly to the SystemBars component.\n   */\n  SystemBarsProps?: SystemBarsProps\n  /**\n   * Pass any additional props directly to the KeyboardAvoidingView component.\n   */\n  KeyboardAvoidingViewProps?: KeyboardAvoidingViewProps\n}\n\ninterface FixedScreenProps extends BaseScreenProps {\n  preset?: \"fixed\"\n}\ninterface ScrollScreenProps extends BaseScreenProps {\n  preset?: \"scroll\"\n  /**\n   * Should keyboard persist on screen tap. Defaults to handled.\n   * Only applies to scroll preset.\n   */\n  keyboardShouldPersistTaps?: \"handled\" | \"always\" | \"never\"\n  /**\n   * Pass any additional props directly to the ScrollView component.\n   */\n  ScrollViewProps?: ScrollViewProps\n}\n\ninterface AutoScreenProps extends Omit<ScrollScreenProps, \"preset\"> {\n  preset?: \"auto\"\n  /**\n   * Threshold to trigger the automatic disabling/enabling of scroll ability.\n   * Defaults to `{ percent: 0.92 }`.\n   */\n  scrollEnabledToggleThreshold?: { percent?: number; point?: number }\n}\n\nexport type ScreenProps = ScrollScreenProps | FixedScreenProps | AutoScreenProps\n\nconst isIos = Platform.OS === \"ios\"\n\ntype ScreenPreset = \"fixed\" | \"scroll\" | \"auto\"\n\n/**\n * @param {ScreenPreset?} preset - The preset to check.\n * @returns {boolean} - Whether the preset is non-scrolling.\n */\nfunction isNonScrolling(preset?: ScreenPreset) {\n  return !preset || preset === \"fixed\"\n}\n\n/**\n * Custom hook that handles the automatic enabling/disabling of scroll ability based on the content size and screen size.\n * @param {UseAutoPresetProps} props - The props for the `useAutoPreset` hook.\n * @returns {{boolean, Function, Function}} - The scroll state, and the `onContentSizeChange` and `onLayout` functions.\n */\nfunction useAutoPreset(props: AutoScreenProps): {\n  scrollEnabled: boolean\n  onContentSizeChange: (w: number, h: number) => void\n  onLayout: (e: LayoutChangeEvent) => void\n} {\n  const { preset, scrollEnabledToggleThreshold } = props\n  const { percent = 0.92, point = 0 } = scrollEnabledToggleThreshold || {}\n\n  const scrollViewHeight = useRef<null | number>(null)\n  const scrollViewContentHeight = useRef<null | number>(null)\n  const [scrollEnabled, setScrollEnabled] = useState(true)\n\n  function updateScrollState() {\n    if (scrollViewHeight.current === null || scrollViewContentHeight.current === null) return\n\n    // check whether content fits the screen then toggle scroll state according to it\n    const contentFitsScreen = (function () {\n      if (point) {\n        return scrollViewContentHeight.current < scrollViewHeight.current - point\n      } else {\n        return scrollViewContentHeight.current < scrollViewHeight.current * percent\n      }\n    })()\n\n    // content is less than the size of the screen, so we can disable scrolling\n    if (scrollEnabled && contentFitsScreen) setScrollEnabled(false)\n\n    // content is greater than the size of the screen, so let's enable scrolling\n    if (!scrollEnabled && !contentFitsScreen) setScrollEnabled(true)\n  }\n\n  /**\n   * @param {number} w - The width of the content.\n   * @param {number} h - The height of the content.\n   */\n  function onContentSizeChange(w: number, h: number) {\n    // update scroll-view content height\n    scrollViewContentHeight.current = h\n    updateScrollState()\n  }\n\n  /**\n   * @param {LayoutChangeEvent} e = The layout change event.\n   */\n  function onLayout(e: LayoutChangeEvent) {\n    const { height } = e.nativeEvent.layout\n    // update scroll-view  height\n    scrollViewHeight.current = height\n    updateScrollState()\n  }\n\n  // update scroll state on every render\n  if (preset === \"auto\") updateScrollState()\n\n  return {\n    scrollEnabled: preset === \"auto\" ? scrollEnabled : true,\n    onContentSizeChange,\n    onLayout,\n  }\n}\n\n/**\n * @param {ScreenProps} props - The props for the `ScreenWithoutScrolling` component.\n * @returns {JSX.Element} - The rendered `ScreenWithoutScrolling` component.\n */\nfunction ScreenWithoutScrolling(props: ScreenProps) {\n  const { style, contentContainerStyle, children, preset } = props\n  return (\n    <View style={[$outerStyle, style]}>\n      <View style={[$innerStyle, preset === \"fixed\" && $justifyFlexEnd, contentContainerStyle]}>\n        {children}\n      </View>\n    </View>\n  )\n}\n\n/**\n * @param {ScreenProps} props - The props for the `ScreenWithScrolling` component.\n * @returns {JSX.Element} - The rendered `ScreenWithScrolling` component.\n */\nfunction ScreenWithScrolling(props: ScreenProps) {\n  const {\n    children,\n    keyboardShouldPersistTaps = \"handled\",\n    keyboardBottomOffset = DEFAULT_BOTTOM_OFFSET,\n    contentContainerStyle,\n    ScrollViewProps,\n    style,\n  } = props as ScrollScreenProps\n\n  const ref = useRef<KeyboardAwareScrollViewRef>(null)\n\n  const { scrollEnabled, onContentSizeChange, onLayout } = useAutoPreset(props as AutoScreenProps)\n\n  // Add native behavior of pressing the active tab to scroll to the top of the content\n  // More info at: https://reactnavigation.org/docs/use-scroll-to-top/\n  useScrollToTop(ref)\n\n  return (\n    <KeyboardAwareScrollView\n      bottomOffset={keyboardBottomOffset}\n      {...{ keyboardShouldPersistTaps, scrollEnabled, ref }}\n      {...ScrollViewProps}\n      onLayout={(e) => {\n        onLayout(e)\n        ScrollViewProps?.onLayout?.(e)\n      }}\n      onContentSizeChange={(w: number, h: number) => {\n        onContentSizeChange(w, h)\n        ScrollViewProps?.onContentSizeChange?.(w, h)\n      }}\n      style={[$outerStyle, ScrollViewProps?.style, style]}\n      contentContainerStyle={[\n        $innerStyle,\n        ScrollViewProps?.contentContainerStyle,\n        contentContainerStyle,\n      ]}\n    >\n      {children}\n    </KeyboardAwareScrollView>\n  )\n}\n\n/**\n * Represents a screen component that provides a consistent layout and behavior for different screen presets.\n * The `Screen` component can be used with different presets such as \"fixed\", \"scroll\", or \"auto\".\n * It handles safe area insets, status bar settings, keyboard avoiding behavior, and scrollability based on the preset.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Screen/}\n * @param {ScreenProps} props - The props for the `Screen` component.\n * @returns {JSX.Element} The rendered `Screen` component.\n */\nexport function Screen(props: ScreenProps) {\n  const {\n    theme: { colors },\n    themeContext,\n  } = useAppTheme()\n  const {\n    backgroundColor,\n    KeyboardAvoidingViewProps,\n    keyboardOffset = 0,\n    safeAreaEdges,\n    SystemBarsProps,\n    systemBarStyle,\n  } = props\n\n  const $containerInsets = useSafeAreaInsetsStyle(safeAreaEdges)\n\n  return (\n    <View\n      style={[\n        $containerStyle,\n        { backgroundColor: backgroundColor || colors.background },\n        $containerInsets,\n      ]}\n    >\n      <SystemBars\n        style={systemBarStyle || (themeContext === \"dark\" ? \"light\" : \"dark\")}\n        {...SystemBarsProps}\n      />\n\n      <KeyboardAvoidingView\n        behavior={isIos ? \"padding\" : \"height\"}\n        keyboardVerticalOffset={keyboardOffset}\n        {...KeyboardAvoidingViewProps}\n        style={[$styles.flex1, KeyboardAvoidingViewProps?.style]}\n      >\n        {isNonScrolling(props.preset) ? (\n          <ScreenWithoutScrolling {...props} />\n        ) : (\n          <ScreenWithScrolling {...props} />\n        )}\n      </KeyboardAvoidingView>\n    </View>\n  )\n}\n\nconst $containerStyle: ViewStyle = {\n  flex: 1,\n  height: \"100%\",\n  width: \"100%\",\n}\n\nconst $outerStyle: ViewStyle = {\n  flex: 1,\n  height: \"100%\",\n  width: \"100%\",\n}\n\nconst $justifyFlexEnd: ViewStyle = {\n  justifyContent: \"flex-end\",\n}\n\nconst $innerStyle: ViewStyle = {\n  justifyContent: \"flex-start\",\n  alignItems: \"stretch\",\n}\n"
  },
  {
    "path": "boilerplate/app/components/Text.test.tsx",
    "content": "import { NavigationContainer } from \"@react-navigation/native\"\nimport { render } from \"@testing-library/react-native\"\n\nimport { Text } from \"./Text\"\nimport { ThemeProvider } from \"../theme/context\"\n\n/* This is an example component test using react-native-testing-library. For more\n * information on how to write your own, see the documentation here:\n * https://callstack.github.io/react-native-testing-library/ */\nconst testText = \"Test string\"\n\ndescribe(\"Text\", () => {\n  it(\"should render the component\", () => {\n    const { getByText } = render(\n      <ThemeProvider>\n        <NavigationContainer>\n          <Text text={testText} />\n        </NavigationContainer>\n      </ThemeProvider>,\n    )\n    expect(getByText(testText)).toBeDefined()\n  })\n})\n"
  },
  {
    "path": "boilerplate/app/components/Text.tsx",
    "content": "import { ReactNode, forwardRef, ForwardedRef } from \"react\"\n// eslint-disable-next-line no-restricted-imports\nimport { StyleProp, Text as RNText, TextProps as RNTextProps, TextStyle } from \"react-native\"\nimport { TOptions } from \"i18next\"\n\nimport { isRTL, TxKeyPath } from \"@/i18n\"\nimport { translate } from \"@/i18n/translate\"\nimport type { ThemedStyle, ThemedStyleArray } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { typography } from \"@/theme/typography\"\n\ntype Sizes = keyof typeof $sizeStyles\ntype Weights = keyof typeof typography.primary\ntype Presets = \"default\" | \"bold\" | \"heading\" | \"subheading\" | \"formLabel\" | \"formHelper\"\n\nexport interface TextProps extends RNTextProps {\n  /**\n   * Text which is looked up via i18n.\n   */\n  tx?: TxKeyPath\n  /**\n   * The text to display if not using `tx` or nested components.\n   */\n  text?: string\n  /**\n   * Optional options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  txOptions?: TOptions\n  /**\n   * An optional style override useful for padding & margin.\n   */\n  style?: StyleProp<TextStyle>\n  /**\n   * One of the different types of text presets.\n   */\n  preset?: Presets\n  /**\n   * Text weight modifier.\n   */\n  weight?: Weights\n  /**\n   * Text size modifier.\n   */\n  size?: Sizes\n  /**\n   * Children components.\n   */\n  children?: ReactNode\n}\n\n/**\n * For your text displaying needs.\n * This component is a HOC over the built-in React Native one.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Text/}\n * @param {TextProps} props - The props for the `Text` component.\n * @returns {JSX.Element} The rendered `Text` component.\n */\nexport const Text = forwardRef(function Text(props: TextProps, ref: ForwardedRef<RNText>) {\n  const { weight, size, tx, txOptions, text, children, style: $styleOverride, ...rest } = props\n  const { themed } = useAppTheme()\n\n  const i18nText = tx && translate(tx, txOptions)\n  const content = i18nText || text || children\n\n  const preset: Presets = props.preset ?? \"default\"\n  const $styles: StyleProp<TextStyle> = [\n    $rtlStyle,\n    themed($presets[preset]),\n    weight && $fontWeightStyles[weight],\n    size && $sizeStyles[size],\n    $styleOverride,\n  ]\n\n  return (\n    <RNText {...rest} style={$styles} ref={ref}>\n      {content}\n    </RNText>\n  )\n})\n\nconst $sizeStyles = {\n  xxl: { fontSize: 36, lineHeight: 44 } satisfies TextStyle,\n  xl: { fontSize: 24, lineHeight: 34 } satisfies TextStyle,\n  lg: { fontSize: 20, lineHeight: 32 } satisfies TextStyle,\n  md: { fontSize: 18, lineHeight: 26 } satisfies TextStyle,\n  sm: { fontSize: 16, lineHeight: 24 } satisfies TextStyle,\n  xs: { fontSize: 14, lineHeight: 21 } satisfies TextStyle,\n  xxs: { fontSize: 12, lineHeight: 18 } satisfies TextStyle,\n}\n\nconst $fontWeightStyles = Object.entries(typography.primary).reduce((acc, [weight, fontFamily]) => {\n  return { ...acc, [weight]: { fontFamily } }\n}, {}) as Record<Weights, TextStyle>\n\nconst $baseStyle: ThemedStyle<TextStyle> = (theme) => ({\n  ...$sizeStyles.sm,\n  ...$fontWeightStyles.normal,\n  color: theme.colors.text,\n})\n\nconst $presets: Record<Presets, ThemedStyleArray<TextStyle>> = {\n  default: [$baseStyle],\n  bold: [$baseStyle, { ...$fontWeightStyles.bold }],\n  heading: [\n    $baseStyle,\n    {\n      ...$sizeStyles.xxl,\n      ...$fontWeightStyles.bold,\n    },\n  ],\n  subheading: [$baseStyle, { ...$sizeStyles.lg, ...$fontWeightStyles.medium }],\n  formLabel: [$baseStyle, { ...$fontWeightStyles.medium }],\n  formHelper: [$baseStyle, { ...$sizeStyles.sm, ...$fontWeightStyles.normal }],\n}\nconst $rtlStyle: TextStyle = isRTL ? { writingDirection: \"rtl\" } : {}\n"
  },
  {
    "path": "boilerplate/app/components/TextField.tsx",
    "content": "import { ComponentType, forwardRef, Ref, useImperativeHandle, useRef } from \"react\"\nimport {\n  ImageStyle,\n  StyleProp,\n  // eslint-disable-next-line no-restricted-imports\n  TextInput,\n  TextInputProps,\n  TextStyle,\n  TouchableOpacity,\n  View,\n  ViewStyle,\n} from \"react-native\"\n\nimport { isRTL } from \"@/i18n\"\nimport { translate } from \"@/i18n/translate\"\nimport type { ThemedStyle, ThemedStyleArray } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { Text, TextProps } from \"./Text\"\n\nexport interface TextFieldAccessoryProps {\n  style: StyleProp<ViewStyle | TextStyle | ImageStyle>\n  status: TextFieldProps[\"status\"]\n  multiline: boolean\n  editable: boolean\n}\n\nexport interface TextFieldProps extends Omit<TextInputProps, \"ref\"> {\n  /**\n   * A style modifier for different input states.\n   */\n  status?: \"error\" | \"disabled\"\n  /**\n   * The label text to display if not using `labelTx`.\n   */\n  label?: TextProps[\"text\"]\n  /**\n   * Label text which is looked up via i18n.\n   */\n  labelTx?: TextProps[\"tx\"]\n  /**\n   * Optional label options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  labelTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Pass any additional props directly to the label Text component.\n   */\n  LabelTextProps?: TextProps\n  /**\n   * The helper text to display if not using `helperTx`.\n   */\n  helper?: TextProps[\"text\"]\n  /**\n   * Helper text which is looked up via i18n.\n   */\n  helperTx?: TextProps[\"tx\"]\n  /**\n   * Optional helper options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  helperTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Pass any additional props directly to the helper Text component.\n   */\n  HelperTextProps?: TextProps\n  /**\n   * The placeholder text to display if not using `placeholderTx`.\n   */\n  placeholder?: TextProps[\"text\"]\n  /**\n   * Placeholder text which is looked up via i18n.\n   */\n  placeholderTx?: TextProps[\"tx\"]\n  /**\n   * Optional placeholder options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  placeholderTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Optional input style override.\n   */\n  style?: StyleProp<TextStyle>\n  /**\n   * Style overrides for the container\n   */\n  containerStyle?: StyleProp<ViewStyle>\n  /**\n   * Style overrides for the input wrapper\n   */\n  inputWrapperStyle?: StyleProp<ViewStyle>\n  /**\n   * An optional component to render on the right side of the input.\n   * Example: `RightAccessory={(props) => <Icon icon=\"ladybug\" containerStyle={props.style} color={props.editable ? colors.textDim : colors.text} />}`\n   * Note: It is a good idea to memoize this.\n   */\n  RightAccessory?: ComponentType<TextFieldAccessoryProps>\n  /**\n   * An optional component to render on the left side of the input.\n   * Example: `LeftAccessory={(props) => <Icon icon=\"ladybug\" containerStyle={props.style} color={props.editable ? colors.textDim : colors.text} />}`\n   * Note: It is a good idea to memoize this.\n   */\n  LeftAccessory?: ComponentType<TextFieldAccessoryProps>\n}\n\n/**\n * A component that allows for the entering and editing of text.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/TextField/}\n * @param {TextFieldProps} props - The props for the `TextField` component.\n * @returns {JSX.Element} The rendered `TextField` component.\n */\nexport const TextField = forwardRef(function TextField(props: TextFieldProps, ref: Ref<TextInput>) {\n  const {\n    labelTx,\n    label,\n    labelTxOptions,\n    placeholderTx,\n    placeholder,\n    placeholderTxOptions,\n    helper,\n    helperTx,\n    helperTxOptions,\n    status,\n    RightAccessory,\n    LeftAccessory,\n    HelperTextProps,\n    LabelTextProps,\n    style: $inputStyleOverride,\n    containerStyle: $containerStyleOverride,\n    inputWrapperStyle: $inputWrapperStyleOverride,\n    ...TextInputProps\n  } = props\n  const input = useRef<TextInput>(null)\n\n  const {\n    themed,\n    theme: { colors },\n  } = useAppTheme()\n\n  const disabled = TextInputProps.editable === false || status === \"disabled\"\n\n  const placeholderContent = placeholderTx\n    ? translate(placeholderTx, placeholderTxOptions)\n    : placeholder\n\n  const $containerStyles = [$containerStyleOverride]\n\n  const $labelStyles = [$labelStyle, LabelTextProps?.style]\n\n  const $inputWrapperStyles = [\n    $styles.row,\n    $inputWrapperStyle,\n    status === \"error\" && { borderColor: colors.error },\n    TextInputProps.multiline && { minHeight: 112 },\n    LeftAccessory && { paddingStart: 0 },\n    RightAccessory && { paddingEnd: 0 },\n    $inputWrapperStyleOverride,\n  ]\n\n  const $inputStyles: ThemedStyleArray<TextStyle> = [\n    $inputStyle,\n    disabled && { color: colors.textDim },\n    isRTL && { textAlign: \"right\" as TextStyle[\"textAlign\"] },\n    TextInputProps.multiline && { height: \"auto\" },\n    $inputStyleOverride,\n  ]\n\n  const $helperStyles = [\n    $helperStyle,\n    status === \"error\" && { color: colors.error },\n    HelperTextProps?.style,\n  ]\n\n  /**\n   *\n   */\n  function focusInput() {\n    if (disabled) return\n\n    input.current?.focus()\n  }\n\n  useImperativeHandle(ref, () => input.current as TextInput)\n\n  return (\n    <TouchableOpacity\n      activeOpacity={1}\n      style={$containerStyles}\n      onPress={focusInput}\n      accessibilityState={{ disabled }}\n    >\n      {!!(label || labelTx) && (\n        <Text\n          preset=\"formLabel\"\n          text={label}\n          tx={labelTx}\n          txOptions={labelTxOptions}\n          {...LabelTextProps}\n          style={themed($labelStyles)}\n        />\n      )}\n\n      <View style={themed($inputWrapperStyles)}>\n        {!!LeftAccessory && (\n          <LeftAccessory\n            style={themed($leftAccessoryStyle)}\n            status={status}\n            editable={!disabled}\n            multiline={TextInputProps.multiline ?? false}\n          />\n        )}\n\n        <TextInput\n          ref={input}\n          underlineColorAndroid={colors.transparent}\n          textAlignVertical=\"top\"\n          placeholder={placeholderContent}\n          placeholderTextColor={colors.textDim}\n          {...TextInputProps}\n          editable={!disabled}\n          style={themed($inputStyles)}\n        />\n\n        {!!RightAccessory && (\n          <RightAccessory\n            style={themed($rightAccessoryStyle)}\n            status={status}\n            editable={!disabled}\n            multiline={TextInputProps.multiline ?? false}\n          />\n        )}\n      </View>\n\n      {!!(helper || helperTx) && (\n        <Text\n          preset=\"formHelper\"\n          text={helper}\n          tx={helperTx}\n          txOptions={helperTxOptions}\n          {...HelperTextProps}\n          style={themed($helperStyles)}\n        />\n      )}\n    </TouchableOpacity>\n  )\n})\n\nconst $labelStyle: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginBottom: spacing.xs,\n})\n\nconst $inputWrapperStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  alignItems: \"flex-start\",\n  borderWidth: 1,\n  borderRadius: 4,\n  backgroundColor: colors.palette.neutral200,\n  borderColor: colors.palette.neutral400,\n  overflow: \"hidden\",\n})\n\nconst $inputStyle: ThemedStyle<TextStyle> = ({ colors, typography, spacing }) => ({\n  flex: 1,\n  alignSelf: \"stretch\",\n  fontFamily: typography.primary.normal,\n  color: colors.text,\n  fontSize: 16,\n  height: 24,\n  // https://github.com/facebook/react-native/issues/21720#issuecomment-532642093\n  paddingVertical: 0,\n  paddingHorizontal: 0,\n  marginVertical: spacing.xs,\n  marginHorizontal: spacing.sm,\n})\n\nconst $helperStyle: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginTop: spacing.xs,\n})\n\nconst $rightAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginEnd: spacing.xs,\n  height: 40,\n  justifyContent: \"center\",\n  alignItems: \"center\",\n})\n\nconst $leftAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginStart: spacing.xs,\n  height: 40,\n  justifyContent: \"center\",\n  alignItems: \"center\",\n})\n"
  },
  {
    "path": "boilerplate/app/components/Toggle/Checkbox.tsx",
    "content": "import { useEffect, useRef, useCallback } from \"react\"\nimport { Image, ImageStyle, Animated, StyleProp, View, ViewStyle } from \"react-native\"\n\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { iconRegistry, IconTypes } from \"../Icon\"\nimport { $inputOuterBase, BaseToggleInputProps, ToggleProps, Toggle } from \"./Toggle\"\n\nexport interface CheckboxToggleProps extends Omit<ToggleProps<CheckboxInputProps>, \"ToggleInput\"> {\n  /**\n   * Optional style prop that affects the Image component.\n   */\n  inputDetailStyle?: ImageStyle\n  /**\n   * Checkbox-only prop that changes the icon used for the \"on\" state.\n   */\n  icon?: IconTypes\n}\n\ninterface CheckboxInputProps extends BaseToggleInputProps<CheckboxToggleProps> {\n  icon?: CheckboxToggleProps[\"icon\"]\n}\n/**\n * @param {CheckboxToggleProps} props - The props for the `Checkbox` component.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Checkbox}\n * @returns {JSX.Element} The rendered `Checkbox` component.\n */\nexport function Checkbox(props: CheckboxToggleProps) {\n  const { icon, ...rest } = props\n  const checkboxInput = useCallback(\n    (toggleProps: CheckboxInputProps) => <CheckboxInput {...toggleProps} icon={icon} />,\n    [icon],\n  )\n  return <Toggle accessibilityRole=\"checkbox\" {...rest} ToggleInput={checkboxInput} />\n}\n\nfunction CheckboxInput(props: CheckboxInputProps) {\n  const {\n    on,\n    status,\n    disabled,\n    icon = \"check\",\n    outerStyle: $outerStyleOverride,\n    innerStyle: $innerStyleOverride,\n    detailStyle: $detailStyleOverride,\n  } = props\n\n  const {\n    theme: { colors },\n  } = useAppTheme()\n\n  const opacity = useRef(new Animated.Value(0))\n\n  useEffect(() => {\n    Animated.timing(opacity.current, {\n      toValue: on ? 1 : 0,\n      duration: 300,\n      useNativeDriver: true,\n    }).start()\n  }, [on])\n\n  const offBackgroundColor = [\n    disabled && colors.palette.neutral400,\n    status === \"error\" && colors.errorBackground,\n    colors.palette.neutral200,\n  ].filter(Boolean)[0]\n\n  const outerBorderColor = [\n    disabled && colors.palette.neutral400,\n    status === \"error\" && colors.error,\n    !on && colors.palette.neutral800,\n    colors.palette.secondary500,\n  ].filter(Boolean)[0]\n\n  const onBackgroundColor = [\n    disabled && colors.transparent,\n    status === \"error\" && colors.errorBackground,\n    colors.palette.secondary500,\n  ].filter(Boolean)[0]\n\n  const iconTintColor = [\n    disabled && colors.palette.neutral600,\n    status === \"error\" && colors.error,\n    colors.palette.accent100,\n  ].filter(Boolean)[0]\n\n  return (\n    <View\n      style={[\n        $inputOuter,\n        { backgroundColor: offBackgroundColor, borderColor: outerBorderColor },\n        $outerStyleOverride,\n      ]}\n    >\n      <Animated.View\n        style={[\n          $styles.toggleInner,\n          { backgroundColor: onBackgroundColor },\n          $innerStyleOverride,\n          { opacity: opacity.current },\n        ]}\n      >\n        <Image\n          source={icon ? iconRegistry[icon] : iconRegistry.check}\n          style={[\n            $checkboxDetail,\n            !!iconTintColor && { tintColor: iconTintColor },\n            $detailStyleOverride as ImageStyle,\n          ]}\n        />\n      </Animated.View>\n    </View>\n  )\n}\n\nconst $checkboxDetail: ImageStyle = {\n  width: 20,\n  height: 20,\n  resizeMode: \"contain\",\n}\n\nconst $inputOuter: StyleProp<ViewStyle> = [$inputOuterBase, { borderRadius: 4 }]\n"
  },
  {
    "path": "boilerplate/app/components/Toggle/Radio.tsx",
    "content": "import { useEffect, useRef } from \"react\"\nimport { StyleProp, View, ViewStyle, Animated } from \"react-native\"\n\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { $inputOuterBase, BaseToggleInputProps, ToggleProps, Toggle } from \"./Toggle\"\n\nexport interface RadioToggleProps extends Omit<ToggleProps<RadioInputProps>, \"ToggleInput\"> {\n  /**\n   * Optional style prop that affects the dot View.\n   */\n  inputDetailStyle?: ViewStyle\n}\n\ninterface RadioInputProps extends BaseToggleInputProps<RadioToggleProps> {}\n\n/**\n * @param {RadioToggleProps} props - The props for the `Radio` component.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Radio}\n * @returns {JSX.Element} The rendered `Radio` component.\n */\nexport function Radio(props: RadioToggleProps) {\n  return <Toggle accessibilityRole=\"radio\" {...props} ToggleInput={RadioInput} />\n}\n\nfunction RadioInput(props: RadioInputProps) {\n  const {\n    on,\n    status,\n    disabled,\n    outerStyle: $outerStyleOverride,\n    innerStyle: $innerStyleOverride,\n    detailStyle: $detailStyleOverride,\n  } = props\n\n  const {\n    theme: { colors },\n  } = useAppTheme()\n\n  const opacity = useRef(new Animated.Value(0))\n\n  useEffect(() => {\n    Animated.timing(opacity.current, {\n      toValue: on ? 1 : 0,\n      duration: 300,\n      useNativeDriver: true,\n    }).start()\n  }, [on])\n\n  const offBackgroundColor = [\n    disabled && colors.palette.neutral400,\n    status === \"error\" && colors.errorBackground,\n    colors.palette.neutral200,\n  ].filter(Boolean)[0]\n\n  const outerBorderColor = [\n    disabled && colors.palette.neutral400,\n    status === \"error\" && colors.error,\n    !on && colors.palette.neutral800,\n    colors.palette.secondary500,\n  ].filter(Boolean)[0]\n\n  const onBackgroundColor = [\n    disabled && colors.transparent,\n    status === \"error\" && colors.errorBackground,\n    colors.palette.neutral100,\n  ].filter(Boolean)[0]\n\n  const dotBackgroundColor = [\n    disabled && colors.palette.neutral600,\n    status === \"error\" && colors.error,\n    colors.palette.secondary500,\n  ].filter(Boolean)[0]\n\n  return (\n    <View\n      style={[\n        $inputOuter,\n        { backgroundColor: offBackgroundColor, borderColor: outerBorderColor },\n        $outerStyleOverride,\n      ]}\n    >\n      <Animated.View\n        style={[\n          $styles.toggleInner,\n          { backgroundColor: onBackgroundColor },\n          $innerStyleOverride,\n          { opacity: opacity.current },\n        ]}\n      >\n        <View\n          style={[$radioDetail, { backgroundColor: dotBackgroundColor }, $detailStyleOverride]}\n        />\n      </Animated.View>\n    </View>\n  )\n}\n\nconst $radioDetail: ViewStyle = {\n  width: 12,\n  height: 12,\n  borderRadius: 6,\n}\n\nconst $inputOuter: StyleProp<ViewStyle> = [$inputOuterBase, { borderRadius: 12 }]\n"
  },
  {
    "path": "boilerplate/app/components/Toggle/Switch.tsx",
    "content": "import { useEffect, useMemo, useRef, useCallback } from \"react\"\nimport { Animated, Image, ImageStyle, Platform, StyleProp, View, ViewStyle } from \"react-native\"\n\nimport { iconRegistry } from \"@/components/Icon\"\nimport { isRTL } from \"@/i18n\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { $inputOuterBase, BaseToggleInputProps, Toggle, ToggleProps } from \"./Toggle\"\n\nexport interface SwitchToggleProps extends Omit<ToggleProps<SwitchInputProps>, \"ToggleInput\"> {\n  /**\n   * Switch-only prop that adds a text/icon label for on/off states.\n   */\n  accessibilityMode?: \"text\" | \"icon\"\n  /**\n   * Optional style prop that affects the knob View.\n   * Note: `width` and `height` rules should be points (numbers), not percentages.\n   */\n  inputDetailStyle?: Omit<ViewStyle, \"width\" | \"height\"> & { width?: number; height?: number }\n}\n\ninterface SwitchInputProps extends BaseToggleInputProps<SwitchToggleProps> {\n  accessibilityMode?: SwitchToggleProps[\"accessibilityMode\"]\n}\n\n/**\n * @param {SwitchToggleProps} props - The props for the `Switch` component.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Switch}\n * @returns {JSX.Element} The rendered `Switch` component.\n */\nexport function Switch(props: SwitchToggleProps) {\n  const { accessibilityMode, ...rest } = props\n  const switchInput = useCallback(\n    (toggleProps: SwitchInputProps) => (\n      <SwitchInput {...toggleProps} accessibilityMode={accessibilityMode} />\n    ),\n    [accessibilityMode],\n  )\n  return <Toggle accessibilityRole=\"switch\" {...rest} ToggleInput={switchInput} />\n}\n\nfunction SwitchInput(props: SwitchInputProps) {\n  const {\n    on,\n    status,\n    disabled,\n    outerStyle: $outerStyleOverride,\n    innerStyle: $innerStyleOverride,\n    detailStyle: $detailStyleOverride,\n  } = props\n\n  const {\n    theme: { colors },\n    themed,\n  } = useAppTheme()\n\n  const animate = useRef(new Animated.Value(on ? 1 : 0)) // Initial value is set based on isActive\n  const opacity = useRef(new Animated.Value(0))\n\n  useEffect(() => {\n    Animated.timing(animate.current, {\n      toValue: on ? 1 : 0,\n      duration: 300,\n      useNativeDriver: true, // Enable native driver for smoother animations\n    }).start()\n  }, [on])\n\n  useEffect(() => {\n    Animated.timing(opacity.current, {\n      toValue: on ? 1 : 0,\n      duration: 300,\n      useNativeDriver: true,\n    }).start()\n  }, [on])\n\n  const knobSizeFallback = 2\n\n  const knobWidth = [$detailStyleOverride?.width, $switchDetail?.width, knobSizeFallback].find(\n    (v) => typeof v === \"number\",\n  )\n\n  const knobHeight = [$detailStyleOverride?.height, $switchDetail?.height, knobSizeFallback].find(\n    (v) => typeof v === \"number\",\n  )\n\n  const offBackgroundColor = [\n    disabled && colors.palette.neutral400,\n    status === \"error\" && colors.errorBackground,\n    colors.palette.neutral300,\n  ].filter(Boolean)[0]\n\n  const onBackgroundColor = [\n    disabled && colors.transparent,\n    status === \"error\" && colors.errorBackground,\n    colors.palette.secondary500,\n  ].filter(Boolean)[0]\n\n  const knobBackgroundColor = (function () {\n    if (on) {\n      return [\n        $detailStyleOverride?.backgroundColor,\n        status === \"error\" && colors.error,\n        disabled && colors.palette.neutral600,\n        colors.palette.neutral100,\n      ].filter(Boolean)[0]\n    } else {\n      return [\n        $innerStyleOverride?.backgroundColor,\n        disabled && colors.palette.neutral600,\n        status === \"error\" && colors.error,\n        colors.palette.neutral200,\n      ].filter(Boolean)[0]\n    }\n  })()\n\n  const rtlAdjustment = isRTL ? -1 : 1\n  const $themedSwitchInner = useMemo(() => themed([$styles.toggleInner, $switchInner]), [themed])\n\n  const offsetLeft = ($innerStyleOverride?.paddingStart ||\n    $innerStyleOverride?.paddingLeft ||\n    $themedSwitchInner?.paddingStart ||\n    $themedSwitchInner?.paddingLeft ||\n    0) as number\n\n  const offsetRight = ($innerStyleOverride?.paddingEnd ||\n    $innerStyleOverride?.paddingRight ||\n    $themedSwitchInner?.paddingEnd ||\n    $themedSwitchInner?.paddingRight ||\n    0) as number\n\n  const outputRange =\n    Platform.OS === \"web\"\n      ? isRTL\n        ? [+(knobWidth || 0) + offsetRight, offsetLeft]\n        : [offsetLeft, +(knobWidth || 0) + offsetRight]\n      : [rtlAdjustment * offsetLeft, rtlAdjustment * (+(knobWidth || 0) + offsetRight)]\n\n  const $animatedSwitchKnob = animate.current.interpolate({\n    inputRange: [0, 1],\n    outputRange,\n  })\n\n  return (\n    <View style={[$inputOuter, { backgroundColor: offBackgroundColor }, $outerStyleOverride]}>\n      <Animated.View\n        style={[\n          $themedSwitchInner,\n          { backgroundColor: onBackgroundColor },\n          $innerStyleOverride,\n          { opacity: opacity.current },\n        ]}\n      />\n\n      <SwitchAccessibilityLabel {...props} role=\"on\" />\n      <SwitchAccessibilityLabel {...props} role=\"off\" />\n\n      <Animated.View\n        style={[\n          $switchDetail,\n          $detailStyleOverride,\n          { transform: [{ translateX: $animatedSwitchKnob }] },\n          { width: knobWidth, height: knobHeight },\n          { backgroundColor: knobBackgroundColor },\n        ]}\n      />\n    </View>\n  )\n}\n\n/**\n * @param {ToggleInputProps & { role: \"on\" | \"off\" }} props - The props for the `SwitchAccessibilityLabel` component.\n * @returns {JSX.Element} The rendered `SwitchAccessibilityLabel` component.\n */\nfunction SwitchAccessibilityLabel(props: SwitchInputProps & { role: \"on\" | \"off\" }) {\n  const { on, disabled, status, accessibilityMode, role, innerStyle, detailStyle } = props\n\n  const {\n    theme: { colors },\n  } = useAppTheme()\n\n  if (!accessibilityMode) return null\n\n  const shouldLabelBeVisible = (on && role === \"on\") || (!on && role === \"off\")\n\n  const $switchAccessibilityStyle: StyleProp<ViewStyle> = [\n    $switchAccessibility,\n    role === \"off\" && { end: \"5%\" },\n    role === \"on\" && { left: \"5%\" },\n  ]\n\n  const color = (function () {\n    if (disabled) return colors.palette.neutral600\n    if (status === \"error\") return colors.error\n    if (!on) return innerStyle?.backgroundColor || colors.palette.secondary500\n    return detailStyle?.backgroundColor || colors.palette.neutral100\n  })()\n\n  return (\n    <View style={$switchAccessibilityStyle}>\n      {accessibilityMode === \"text\" && shouldLabelBeVisible && (\n        <View\n          style={[\n            role === \"on\" && $switchAccessibilityLine,\n            role === \"on\" && { backgroundColor: color },\n            role === \"off\" && $switchAccessibilityCircle,\n            role === \"off\" && { borderColor: color },\n          ]}\n        />\n      )}\n\n      {accessibilityMode === \"icon\" && shouldLabelBeVisible && (\n        <Image\n          style={[$switchAccessibilityIcon, { tintColor: color }]}\n          source={role === \"off\" ? iconRegistry.hidden : iconRegistry.view}\n        />\n      )}\n    </View>\n  )\n}\n\nconst $inputOuter: StyleProp<ViewStyle> = [\n  $inputOuterBase,\n  { height: 32, width: 56, borderRadius: 16, borderWidth: 0 },\n]\n\nconst $switchInner: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  borderColor: colors.transparent,\n  position: \"absolute\",\n  paddingStart: 4,\n  paddingEnd: 4,\n})\n\nconst $switchDetail: SwitchToggleProps[\"inputDetailStyle\"] = {\n  borderRadius: 12,\n  position: \"absolute\",\n  width: 24,\n  height: 24,\n}\n\nconst $switchAccessibility: ViewStyle = {\n  width: \"40%\",\n  justifyContent: \"center\",\n  alignItems: \"center\",\n}\n\nconst $switchAccessibilityIcon: ImageStyle = {\n  width: 14,\n  height: 14,\n  resizeMode: \"contain\",\n}\n\nconst $switchAccessibilityLine: ViewStyle = {\n  width: 2,\n  height: 12,\n}\n\nconst $switchAccessibilityCircle: ViewStyle = {\n  borderWidth: 2,\n  width: 12,\n  height: 12,\n  borderRadius: 6,\n}\n"
  },
  {
    "path": "boilerplate/app/components/Toggle/Toggle.tsx",
    "content": "import { ComponentType, FC, useMemo } from \"react\"\nimport {\n  GestureResponderEvent,\n  ImageStyle,\n  StyleProp,\n  SwitchProps,\n  TextInputProps,\n  TextStyle,\n  TouchableOpacity,\n  TouchableOpacityProps,\n  View,\n  ViewProps,\n  ViewStyle,\n} from \"react-native\"\n\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { Text, TextProps } from \"../Text\"\n\nexport interface ToggleProps<T> extends Omit<TouchableOpacityProps, \"style\"> {\n  /**\n   * A style modifier for different input states.\n   */\n  status?: \"error\" | \"disabled\"\n  /**\n   * If false, input is not editable. The default value is true.\n   */\n  editable?: TextInputProps[\"editable\"]\n  /**\n   * The value of the field. If true the component will be turned on.\n   */\n  value?: boolean\n  /**\n   * Invoked with the new value when the value changes.\n   */\n  onValueChange?: SwitchProps[\"onValueChange\"]\n  /**\n   * Style overrides for the container\n   */\n  containerStyle?: StyleProp<ViewStyle>\n  /**\n   * Style overrides for the input wrapper\n   */\n  inputWrapperStyle?: StyleProp<ViewStyle>\n  /**\n   * Optional input wrapper style override.\n   * This gives the inputs their size, shape, \"off\" background-color, and outer border.\n   */\n  inputOuterStyle?: ViewStyle\n  /**\n   * Optional input style override.\n   * This gives the inputs their inner characteristics and \"on\" background-color.\n   */\n  inputInnerStyle?: ViewStyle\n  /**\n   * Optional detail style override.\n   * See Checkbox, Radio, and Switch for more details\n   */\n  inputDetailStyle?: ViewStyle\n  /**\n   * The position of the label relative to the action component.\n   * Default: right\n   */\n  labelPosition?: \"left\" | \"right\"\n  /**\n   * The label text to display if not using `labelTx`.\n   */\n  label?: TextProps[\"text\"]\n  /**\n   * Label text which is looked up via i18n.\n   */\n  labelTx?: TextProps[\"tx\"]\n  /**\n   * Optional label options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  labelTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Style overrides for label text.\n   */\n  labelStyle?: StyleProp<TextStyle>\n  /**\n   * Pass any additional props directly to the label Text component.\n   */\n  LabelTextProps?: TextProps\n  /**\n   * The helper text to display if not using `helperTx`.\n   */\n  helper?: TextProps[\"text\"]\n  /**\n   * Helper text which is looked up via i18n.\n   */\n  helperTx?: TextProps[\"tx\"]\n  /**\n   * Optional helper options to pass to i18n. Useful for interpolation\n   * as well as explicitly setting locale or translation fallbacks.\n   */\n  helperTxOptions?: TextProps[\"txOptions\"]\n  /**\n   * Pass any additional props directly to the helper Text component.\n   */\n  HelperTextProps?: TextProps\n  /**\n   * The input control for the type of toggle component\n   */\n  ToggleInput: FC<BaseToggleInputProps<T>>\n}\n\nexport interface BaseToggleInputProps<T> {\n  on: boolean\n  status: ToggleProps<T>[\"status\"]\n  disabled: boolean\n  outerStyle: ViewStyle\n  innerStyle: ViewStyle\n  detailStyle: ViewStyle | ImageStyle\n}\n\n/**\n * Renders a boolean input.\n * This is a controlled component that requires an onValueChange callback that updates the value prop in order for the component to reflect user actions. If the value prop is not updated, the component will continue to render the supplied value prop instead of the expected result of any user actions.\n * @param {ToggleProps} props - The props for the `Toggle` component.\n * @returns {JSX.Element} The rendered `Toggle` component.\n */\nexport function Toggle<T>(props: ToggleProps<T>) {\n  const {\n    editable = true,\n    status,\n    value,\n    onPress,\n    onValueChange,\n    labelPosition = \"right\",\n    helper,\n    helperTx,\n    helperTxOptions,\n    HelperTextProps,\n    containerStyle: $containerStyleOverride,\n    inputWrapperStyle: $inputWrapperStyleOverride,\n    ToggleInput,\n    accessibilityRole,\n    ...WrapperProps\n  } = props\n\n  const {\n    theme: { colors },\n    themed,\n  } = useAppTheme()\n\n  const disabled = editable === false || status === \"disabled\" || props.disabled\n\n  const Wrapper = useMemo(\n    () => (disabled ? View : TouchableOpacity) as ComponentType<TouchableOpacityProps | ViewProps>,\n    [disabled],\n  )\n\n  const $containerStyles = [$containerStyleOverride]\n  const $inputWrapperStyles = [$styles.row, $inputWrapper, $inputWrapperStyleOverride]\n  const $helperStyles = themed([\n    $helper,\n    status === \"error\" && { color: colors.error },\n    HelperTextProps?.style,\n  ])\n\n  /**\n   * @param {GestureResponderEvent} e - The event object.\n   */\n  function handlePress(e: GestureResponderEvent) {\n    if (disabled) return\n    onValueChange?.(!value)\n    onPress?.(e)\n  }\n\n  return (\n    <Wrapper\n      activeOpacity={1}\n      accessibilityRole={accessibilityRole}\n      accessibilityState={{ checked: value, disabled }}\n      {...WrapperProps}\n      style={$containerStyles}\n      onPress={handlePress}\n    >\n      <View style={$inputWrapperStyles}>\n        {labelPosition === \"left\" && <FieldLabel<T> {...props} labelPosition={labelPosition} />}\n\n        <ToggleInput\n          on={!!value}\n          disabled={!!disabled}\n          status={status}\n          outerStyle={props.inputOuterStyle ?? {}}\n          innerStyle={props.inputInnerStyle ?? {}}\n          detailStyle={props.inputDetailStyle ?? {}}\n        />\n\n        {labelPosition === \"right\" && <FieldLabel<T> {...props} labelPosition={labelPosition} />}\n      </View>\n\n      {!!(helper || helperTx) && (\n        <Text\n          preset=\"formHelper\"\n          text={helper}\n          tx={helperTx}\n          txOptions={helperTxOptions}\n          {...HelperTextProps}\n          style={$helperStyles}\n        />\n      )}\n    </Wrapper>\n  )\n}\n\n/**\n * @param {ToggleProps} props - The props for the `FieldLabel` component.\n * @returns {JSX.Element} The rendered `FieldLabel` component.\n */\nfunction FieldLabel<T>(props: ToggleProps<T>) {\n  const {\n    status,\n    label,\n    labelTx,\n    labelTxOptions,\n    LabelTextProps,\n    labelPosition,\n    labelStyle: $labelStyleOverride,\n  } = props\n\n  const {\n    theme: { colors },\n    themed,\n  } = useAppTheme()\n\n  if (!label && !labelTx && !LabelTextProps?.children) return null\n\n  const $labelStyle = themed([\n    $label,\n    status === \"error\" && { color: colors.error },\n    labelPosition === \"right\" && $labelRight,\n    labelPosition === \"left\" && $labelLeft,\n    $labelStyleOverride,\n    LabelTextProps?.style,\n  ])\n\n  return (\n    <Text\n      preset=\"formLabel\"\n      text={label}\n      tx={labelTx}\n      txOptions={labelTxOptions}\n      {...LabelTextProps}\n      style={$labelStyle}\n    />\n  )\n}\n\nconst $inputWrapper: ViewStyle = {\n  alignItems: \"center\",\n}\n\nexport const $inputOuterBase: ViewStyle = {\n  height: 24,\n  width: 24,\n  borderWidth: 2,\n  alignItems: \"center\",\n  overflow: \"hidden\",\n  flexGrow: 0,\n  flexShrink: 0,\n  justifyContent: \"space-between\",\n  flexDirection: \"row\",\n}\n\nconst $helper: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginTop: spacing.xs,\n})\n\nconst $label: TextStyle = {\n  flex: 1,\n}\n\nconst $labelRight: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginStart: spacing.md,\n})\n\nconst $labelLeft: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginEnd: spacing.md,\n})\n"
  },
  {
    "path": "boilerplate/app/config/config.base.ts",
    "content": "export interface ConfigBaseProps {\n  persistNavigation: \"always\" | \"dev\" | \"prod\" | \"never\"\n  catchErrors: \"always\" | \"dev\" | \"prod\" | \"never\"\n  exitRoutes: string[]\n}\n\nexport type PersistNavigationConfig = ConfigBaseProps[\"persistNavigation\"]\n\nconst BaseConfig: ConfigBaseProps = {\n  // This feature is particularly useful in development mode, but\n  // can be used in production as well if you prefer.\n  persistNavigation: \"dev\",\n\n  /**\n   * Only enable if we're catching errors in the right environment\n   */\n  catchErrors: \"always\",\n\n  /**\n   * This is a list of all the route names that will exit the app if the back button\n   * is pressed while in that screen. Only affects Android.\n   */\n  exitRoutes: [\"Welcome\"],\n}\n\nexport default BaseConfig\n"
  },
  {
    "path": "boilerplate/app/config/config.dev.ts",
    "content": "/**\n * These are configuration settings for the dev environment.\n *\n * Do not include API secrets in this file or anywhere in your JS.\n *\n * https://reactnative.dev/docs/security#storing-sensitive-info\n */\nexport default {\n  API_URL: \"https://api.rss2json.com/v1/\",\n}\n"
  },
  {
    "path": "boilerplate/app/config/config.prod.ts",
    "content": "/**\n * These are configuration settings for the production environment.\n *\n * Do not include API secrets in this file or anywhere in your JS.\n *\n * https://reactnative.dev/docs/security#storing-sensitive-info\n */\nexport default {\n  API_URL: \"https://api.rss2json.com/v1/\",\n}\n"
  },
  {
    "path": "boilerplate/app/config/index.ts",
    "content": "/**\n * This file imports configuration objects from either the config.dev.js file\n * or the config.prod.js file depending on whether we are in __DEV__ or not.\n *\n * Note that we do not gitignore these files. Unlike on web servers, just because\n * these are not checked into your repo doesn't mean that they are secure.\n * In fact, you're shipping a JavaScript bundle with every\n * config variable in plain text. Anyone who downloads your app can easily\n * extract them.\n *\n * If you doubt this, just bundle your app, and then go look at the bundle and\n * search it for one of your config variable values. You'll find it there.\n *\n * Read more here: https://reactnative.dev/docs/security#storing-sensitive-info\n */\nimport BaseConfig from \"./config.base\"\nimport DevConfig from \"./config.dev\"\nimport ProdConfig from \"./config.prod\"\n\nlet ExtraConfig = ProdConfig\n\nif (__DEV__) {\n  ExtraConfig = DevConfig\n}\n\nconst Config = { ...BaseConfig, ...ExtraConfig }\n\nexport default Config\n"
  },
  {
    "path": "boilerplate/app/context/AuthContext.tsx",
    "content": "import { createContext, FC, PropsWithChildren, useCallback, useContext, useMemo } from \"react\"\nimport { useMMKVString } from \"react-native-mmkv\"\n\nexport type AuthContextType = {\n  isAuthenticated: boolean\n  authToken?: string\n  authEmail?: string\n  setAuthToken: (token?: string) => void\n  setAuthEmail: (email: string) => void\n  logout: () => void\n  validationError: string\n}\n\nexport const AuthContext = createContext<AuthContextType | null>(null)\n\nexport interface AuthProviderProps {}\n\nexport const AuthProvider: FC<PropsWithChildren<AuthProviderProps>> = ({ children }) => {\n  const [authToken, setAuthToken] = useMMKVString(\"AuthProvider.authToken\")\n  const [authEmail, setAuthEmail] = useMMKVString(\"AuthProvider.authEmail\")\n\n  const logout = useCallback(() => {\n    setAuthToken(undefined)\n    setAuthEmail(\"\")\n  }, [setAuthEmail, setAuthToken])\n\n  const validationError = useMemo(() => {\n    if (!authEmail || authEmail.length === 0) return \"can't be blank\"\n    if (authEmail.length < 6) return \"must be at least 6 characters\"\n    if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(authEmail)) return \"must be a valid email address\"\n    return \"\"\n  }, [authEmail])\n\n  const value = {\n    isAuthenticated: !!authToken,\n    authToken,\n    authEmail,\n    setAuthToken,\n    setAuthEmail,\n    logout,\n    validationError,\n  }\n\n  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>\n}\n\nexport const useAuth = () => {\n  const context = useContext(AuthContext)\n  if (!context) throw new Error(\"useAuth must be used within an AuthProvider\")\n  return context\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/context/EpisodeContext.tsx",
    "content": "import {\n  createContext,\n  FC,\n  PropsWithChildren,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n} from \"react\"\n\nimport { translate } from \"@/i18n/translate\"\nimport { api } from \"@/services/api\"\nimport type { EpisodeItem } from \"@/services/api/types\"\nimport { formatDate } from \"@/utils/formatDate\"\n\nexport type EpisodeContextType = {\n  totalEpisodes: number\n  totalFavorites: number\n  episodesForList: EpisodeItem[]\n  fetchEpisodes: () => Promise<void>\n  favoritesOnly: boolean\n  toggleFavoritesOnly: () => void\n  hasFavorite: (episode: EpisodeItem) => boolean\n  toggleFavorite: (episode: EpisodeItem) => void\n}\n\nexport const EpisodeContext = createContext<EpisodeContextType | null>(null)\n\nexport interface EpisodeProviderProps {}\n\nexport const EpisodeProvider: FC<PropsWithChildren<EpisodeProviderProps>> = ({ children }) => {\n  const [episodes, setEpisodes] = useState<EpisodeItem[]>([])\n  const [favorites, setFavorites] = useState<string[]>([])\n  const [favoritesOnly, setFavoritesOnly] = useState<boolean>(false)\n\n  const fetchEpisodes = useCallback(async () => {\n    const response = await api.getEpisodes()\n    if (response.kind === \"ok\") {\n      setEpisodes(response.episodes)\n    } else {\n      console.error(`Error fetching episodes: ${JSON.stringify(response)}`)\n    }\n  }, [])\n\n  const toggleFavoritesOnly = useCallback(() => {\n    setFavoritesOnly((prev) => !prev)\n  }, [])\n\n  const toggleFavorite = useCallback(\n    (episode: EpisodeItem) => {\n      if (favorites.some((fav) => fav === episode.guid)) {\n        setFavorites((prev) => prev.filter((fav) => fav !== episode.guid))\n      } else {\n        setFavorites((prev) => [...prev, episode.guid])\n      }\n    },\n    [favorites],\n  )\n\n  const hasFavorite = useCallback(\n    (episode: EpisodeItem) => favorites.some((fav) => fav === episode.guid),\n    [favorites],\n  )\n\n  const episodesForList = useMemo(() => {\n    return favoritesOnly ? episodes.filter((episode) => favorites.includes(episode.guid)) : episodes\n  }, [episodes, favorites, favoritesOnly])\n\n  const value = {\n    totalEpisodes: episodes.length,\n    totalFavorites: favorites.length,\n    episodesForList,\n    fetchEpisodes,\n    favoritesOnly,\n    toggleFavoritesOnly,\n    hasFavorite,\n    toggleFavorite,\n  }\n\n  return <EpisodeContext.Provider value={value}>{children}</EpisodeContext.Provider>\n}\n\nexport const useEpisodes = () => {\n  const context = useContext(EpisodeContext)\n  if (!context) throw new Error(\"useEpisodes must be used within an EpisodeProvider\")\n  return context\n}\n\n// A helper hook to extract and format episode details\nexport const useEpisode = (episode: EpisodeItem) => {\n  const { hasFavorite } = useEpisodes()\n\n  const isFavorite = hasFavorite(episode)\n\n  let datePublished\n  try {\n    const formatted = formatDate(episode.pubDate)\n    datePublished = {\n      textLabel: formatted,\n      accessibilityLabel: translate(\"demoPodcastListScreen:accessibility.publishLabel\", {\n        date: formatted,\n      }),\n    }\n  } catch {\n    datePublished = { textLabel: \"\", accessibilityLabel: \"\" }\n  }\n\n  const seconds = Number(episode.enclosure?.duration ?? 0)\n  const h = Math.floor(seconds / 3600)\n  const m = Math.floor((seconds % 3600) / 60)\n  const s = Math.floor((seconds % 3600) % 60)\n  const duration = {\n    textLabel: `${h > 0 ? `${h}:` : \"\"}${m > 0 ? `${m}:` : \"\"}${s}`,\n    accessibilityLabel: translate(\"demoPodcastListScreen:accessibility.durationLabel\", {\n      hours: h,\n      minutes: m,\n      seconds: s,\n    }),\n  }\n\n  const trimmedTitle = episode.title?.trim()\n  const titleMatches = trimmedTitle?.match(/^(RNR.*\\d)(?: - )(.*$)/)\n  const parsedTitleAndSubtitle =\n    titleMatches && titleMatches.length === 3\n      ? { title: titleMatches[1], subtitle: titleMatches[2] }\n      : { title: trimmedTitle, subtitle: \"\" }\n\n  return {\n    isFavorite,\n    datePublished,\n    duration,\n    parsedTitleAndSubtitle,\n  }\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/devtools/ReactotronClient.ts",
    "content": "/**\n * This file is loaded in React Native and exports the RN version\n * of Reactotron's client.\n *\n * Web is loaded from ReactotronClient.web.ts.\n */\nimport Reactotron from \"reactotron-react-native\"\n\nexport { Reactotron }\n"
  },
  {
    "path": "boilerplate/app/devtools/ReactotronClient.web.ts",
    "content": "/**\n * This file is loaded in web and exports the React.js version\n * of Reactotron's client.\n *\n * React Native is loaded from ReactotronClient.ts.\n *\n * If your project does not need web support, you can delete this file and\n * remove reactotron-react-js from your package.json dependencies.\n */\nimport Reactotron from \"reactotron-react-js\"\n\nexport { Reactotron }\n"
  },
  {
    "path": "boilerplate/app/devtools/ReactotronConfig.ts",
    "content": "/**\n * This file does the setup for integration with Reactotron, which is a\n * free desktop app for inspecting and debugging your React Native app.\n * @see https://github.com/infinitered/reactotron\n */\nimport { Platform, NativeModules } from \"react-native\"\nimport { ArgType } from \"reactotron-core-client\"\nimport { ReactotronReactNative } from \"reactotron-react-native\"\nimport mmkvPlugin from \"reactotron-react-native-mmkv\"\n\nimport { goBack, resetRoot, navigate } from \"@/navigators/navigationUtilities\"\nimport { storage } from \"@/utils/storage\"\n\nimport { Reactotron } from \"./ReactotronClient\"\n\nconst reactotron = Reactotron.configure({\n  name: require(\"../../package.json\").name,\n  onConnect: () => {\n    /** since this file gets hot reloaded, let's clear the past logs every time we connect */\n    Reactotron.clear()\n  },\n})\n\nreactotron.use(mmkvPlugin<ReactotronReactNative>({ storage }))\n\nif (Platform.OS !== \"web\") {\n  reactotron.useReactNative({\n    networking: {\n      ignoreUrls: /symbolicate/,\n    },\n  })\n}\n\n/**\n * Reactotron allows you to define custom commands that you can run\n * from Reactotron itself, and they will run in your app.\n *\n * Define them in the section below with `onCustomCommand`. Use your\n * creativity -- this is great for development to quickly and easily\n * get your app into the state you want.\n *\n * NOTE: If you edit this file while running the app, you will need to do a full refresh\n * or else your custom commands won't be registered correctly.\n */\nreactotron.onCustomCommand({\n  title: \"Show Dev Menu\",\n  description: \"Opens the React Native dev menu\",\n  command: \"showDevMenu\",\n  handler: () => {\n    Reactotron.log(\"Showing React Native dev menu\")\n    NativeModules.DevMenu.show()\n  },\n})\n\nreactotron.onCustomCommand({\n  title: \"Reset Navigation State\",\n  description: \"Resets the navigation state\",\n  command: \"resetNavigation\",\n  handler: () => {\n    Reactotron.log(\"resetting navigation state\")\n    resetRoot({ index: 0, routes: [] })\n  },\n})\n\nreactotron.onCustomCommand<[{ name: \"route\"; type: ArgType.String }]>({\n  command: \"navigateTo\",\n  handler: (args) => {\n    const { route } = args ?? {}\n    if (route) {\n      Reactotron.log(`Navigating to: ${route}`)\n      // @ts-ignore\n      navigate(route as any) // this should be tied to the navigator, but since this is for debugging, we can navigate to illegal routes\n    } else {\n      Reactotron.log(\"Could not navigate. No route provided.\")\n    }\n  },\n  title: \"Navigate To Screen\",\n  description: \"Navigates to a screen by name.\",\n  args: [{ name: \"route\", type: ArgType.String }],\n})\n\nreactotron.onCustomCommand({\n  title: \"Go Back\",\n  description: \"Goes back\",\n  command: \"goBack\",\n  handler: () => {\n    Reactotron.log(\"Going back\")\n    goBack()\n  },\n})\n\n/**\n * We're going to add `console.tron` to the Reactotron object.\n * Now, anywhere in our app in development, we can use Reactotron like so:\n *\n * ```\n * if (__DEV__) {\n *  console.tron.display({\n *    name: 'JOKE',\n *    preview: 'What's the best thing about Switzerland?',\n *    value: 'I don't know, but the flag is a big plus!',\n *    important: true\n *  })\n * }\n * ```\n *\n * Use this power responsibly! :)\n */\nconsole.tron = reactotron\n\n/**\n * We tell typescript about our dark magic\n *\n * You can also import Reactotron yourself from ./reactotronClient\n * and use it directly, like Reactotron.log('hello world')\n */\ndeclare global {\n  interface Console {\n    /**\n     * Reactotron client for logging, displaying, measuring performance, and more.\n     * @see https://github.com/infinitered/reactotron\n     * @example\n     * if (__DEV__) {\n     *  console.tron.display({\n     *    name: 'JOKE',\n     *    preview: 'What's the best thing about Switzerland?',\n     *    value: 'I don't know, but the flag is a big plus!',\n     *    important: true\n     *  })\n     * }\n     */\n    tron: typeof reactotron\n  }\n}\n\n/**\n * Now that we've setup all our Reactotron configuration, let's connect!\n */\nreactotron.connect()\n"
  },
  {
    "path": "boilerplate/app/i18n/ar.ts",
    "content": "import demoAr from \"./demo-ar\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst ar: Translations = {\n  common: {\n    ok: \"نعم\",\n    cancel: \"حذف\",\n    back: \"خلف\",\n    logOut: \"تسجيل خروج\", // @demo remove-current-line\n  },\n  welcomeScreen: {\n    postscript:\n      \"ربما لا يكون هذا هو الشكل الذي يبدو عليه تطبيقك مالم يمنحك المصمم هذه الشاشات وشحنها في هذه الحالة\",\n    readyForLaunch: \"تطبيقك تقريبا جاهز للتشغيل\",\n    exciting: \"اوه هذا مثير\",\n    letsGo: \"لنذهب\", // @demo remove-current-line\n  },\n  errorScreen: {\n    title: \"هناك خطأ ما\",\n    friendlySubtitle:\n      \"هذه هي الشاشة التي سيشاهدها المستخدمون في عملية الانتاج عند حدوث خطأ. سترغب في تخصيص هذه الرسالة ( الموجودة في 'ts.en/i18n/app') وربما التخطيط ايضاً ('app/screens/ErrorScreen'). إذا كنت تريد إزالة هذا بالكامل، تحقق من 'app/app.tsp' من اجل عنصر <ErrorBoundary>.\",\n    reset: \"اعادة تعيين التطبيق\",\n    traceTitle: \"خطأ من مجموعة %{name}\", // @demo remove-current-line\n  },\n  emptyStateComponent: {\n    generic: {\n      heading: \"فارغة جداً....حزين\",\n      content: \"لا توجد بيانات حتى الآن. حاول النقر فوق الزر لتحديث التطبيق او اعادة تحميله.\",\n      button: \"لنحاول هذا مرّة أخرى\",\n    },\n  },\n  // @demo remove-block-start\n  errors: {\n    invalidEmail: \"عنوان البريد الالكتروني غير صالح\",\n  },\n  loginScreen: {\n    logIn: \"تسجيل الدخول\",\n    enterDetails:\n      \".ادخل التفاصيل الخاصة بك ادناه لفتح معلومات سرية للغاية. لن تخمن ابداً ما الذي ننتظره. او ربما ستفعل انها انها ليست علم الصواريخ\",\n    emailFieldLabel: \"البريد الالكتروني\",\n    passwordFieldLabel: \"كلمة السر\",\n    emailFieldPlaceholder: \"ادخل بريدك الالكتروني\",\n    passwordFieldPlaceholder: \"كلمة السر هنا فائقة السر\",\n    tapToLogIn: \"انقر لتسجيل الدخول!\",\n    hint: \"(: تلميح: يمكنك استخدام اي عنوان بريد الكتروني وكلمة السر المفضلة لديك\",\n  },\n  demoNavigator: {\n    componentsTab: \"عناصر\",\n    debugTab: \"تصحيح\",\n    communityTab: \"واصل اجتماعي\",\n    podcastListTab: \"البودكاست\",\n  },\n  demoCommunityScreen: {\n    title: \"تواصل مع المجتمع\",\n    tagLine:\n      \"قم بالتوصيل لمنتدى Infinite Red الذي يضم تفاعل المهندسين المحلّيين ورفع مستوى تطوير تطبيقك معنا\",\n    joinUsOnSlackTitle: \"انضم الينا على Slack\",\n    joinUsOnSlack:\n      \"هل ترغب في وجود مكان للتواصل مع مهندسي React Native حول العالم؟ الانضمام الى المحادثة في سلاك المجتمع الاحمر اللانهائي! مجتمعناالمتنامي هو مساحةآمنة لطرح الاسئلة والتعلم من الآخرين وتنمية شبكتك.\",\n    joinSlackLink: \"انضم الي مجتمع Slack\",\n    makeIgniteEvenBetterTitle: \"اجعل Ignite افضل\",\n    makeIgniteEvenBetter:\n      \"هل لديك فكرة لجعل Ignite افضل؟ نحن سعداء لسماع ذلك! نحن نبحث دائماً عن الآخرين الذين يرغبون في مساعدتنا في بناء افضل الادوات المحلية التفاعلية المتوفرة هناك. انضم الينا عبر GitHub للانضمام الينا في بناء مستقبل Ignite\",\n    contributeToIgniteLink: \"ساهم في Ignite\",\n    theLatestInReactNativeTitle: \"الاحدث في React Native\",\n    theLatestInReactNative: \"نخن هنا لنبقيك محدثاً على جميع React Native التي تعرضها\",\n    reactNativeRadioLink: \"راديو React Native\",\n    reactNativeNewsletterLink: \"نشرة اخبار React Native\",\n    reactNativeLiveLink: \"مباشر React Native\",\n    chainReactConferenceLink: \"مؤتمر Chain React\",\n    hireUsTitle: \"قم بتوظيف Infinite Red لمشروعك القادم\",\n    hireUs:\n      \"سواء كان الامر يتعلّق بتشغيل مشروع كامل او اعداد الفرق بسرعة من خلال التدريب العلمي لدينا، يمكن ان يساعد Infinite Red اللامتناهي في اي مشروع محلي يتفاعل معه.\",\n    hireUsLink: \"ارسل لنا رسالة\",\n  },\n  demoShowroomScreen: {\n    jumpStart: \"مكونات او عناصر لبدء مشروعك\",\n    lorem2Sentences:\n      \"عامل الناس بأخلاقك لا بأخلاقهم. عامل الناس بأخلاقك لا بأخلاقهم. عامل الناس بأخلاقك لا بأخلاقهم\",\n    demoHeaderTxExample: \"ياي\",\n    demoViaTxProp: \"عبر `tx` Prop\",\n    demoViaSpecifiedTxProp: \"Prop `{{prop}}Tx` عبر\",\n  },\n  demoDebugScreen: {\n    howTo: \"كيف\",\n    title: \"التصحيح\",\n    tagLine: \"مبروك، لديك نموذج اصلي متقدم للغاية للتفاعل هنا. الاستفادة من هذه النمذجة\",\n    reactotron: \"Reactotron ارسل إلى\",\n    reportBugs: \"الابلاغ عن اخطاء\",\n    demoList: \"قائمة تجريبية\",\n    demoPodcastList: \"قائمة البودكاست التجريبي\",\n    androidReactotronHint:\n      \"اذا لم ينجح ذللك، فتأكد من تشغيل تطبيق الحاسوب الخاص Reactotron، وقم بتشغيل عكس adb tcp:9090 \\ntcp:9090 من جهازك الطرفي ، واعد تحميل التطبيق\",\n    iosReactotronHint:\n      \"اذا لم ينجح ذلك، فتأكد من تشغيل تطبيق الحاسوب الخاص ب Reactotron وأعد تحميل التطبيق\",\n    macosReactotronHint: \"اذا لم ينجح ذلك، فتأكد من تشغيل الحاسوب ب Reactotron وأعد تحميل التطبيق\",\n    webReactotronHint: \"اذا لم ينجح ذلك، فتأكد من تشغيل الحاسوب ب Reactotron وأعد تحميل التطبيق\",\n    windowsReactotronHint:\n      \"اذا لم ينجح ذلك، فتأكد من تشغيل الحاسوب ب Reactotron وأعد تحميل التطبيق\",\n  },\n  demoPodcastListScreen: {\n    title: \"حلقات إذاعية React Native\",\n    onlyFavorites: \"المفضلة فقط\",\n    favoriteButton: \"المفضل\",\n    unfavoriteButton: \"غير مفضل\",\n    accessibility: {\n      cardHint: \"انقر مرّتين للاستماع على الحلقة. انقر مرّتين وانتظر لتفعيل {{action}} هذه الحلقة.\",\n      switch: \"قم بالتبديل لاظهار المفضّلة فقط.\",\n      favoriteAction: \"تبديل المفضلة\",\n      favoriteIcon: \"الحلقة الغير مفضّلة\",\n      unfavoriteIcon: \"الحلقة المفضّلة\",\n      publishLabel: \"نشرت {{date}}\",\n      durationLabel: \"المدّة: {{hours}} ساعات {{minutes}} دقائق {{seconds}} ثواني\",\n    },\n    noFavoritesEmptyState: {\n      heading: \"هذا يبدو فارغاً بعض الشيء.\",\n      content:\n        \"لم تتم اضافة اي مفضلات حتى الان. اضغط على القلب في إحدى الحلقات لإضافته الى المفضلة.\",\n    },\n  },\n  // @demo remove-block-start\n  ...demoAr,\n  // @demo remove-block-end\n}\n\nexport default ar\n"
  },
  {
    "path": "boilerplate/app/i18n/demo-ar.ts",
    "content": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoAr: DemoTranslations = {\n  demoIcon: {\n    description:\n      \"مكون لعرض أيقونة مسجلة.يتم تغليفه في <TouchableOpacity> يتم توفير 'OnPress'، وإلا يتم توفير <View\",\n    useCase: {\n      icons: {\n        name: \"Icons\",\n        description: \"قائمة الرموز المسجلة داخل المكون.\",\n      },\n      size: {\n        name: \"Size\",\n        description: \"هناك حجم الدعامة.\",\n      },\n      color: {\n        name: \"لون\",\n        description: \"هناك لون الدعامة.\",\n      },\n      styling: {\n        name: \"التصميم\",\n        description: \"يمكن تصميم المكون بسهولة.\",\n      },\n    },\n  },\n  demoTextField: {\n    description: \"TextField يسمح المكون بإدخال النص وتحريره.\",\n    useCase: {\n      statuses: {\n        name: \"الحالات\",\n        description:\n          \"هناك حالة مماثلة ل 'preset' في المكونات الأخرى، ولكنها تؤثر على وظيفة المكون ايضاً.\",\n        noStatus: {\n          label: \"لا يوجد حالات\",\n          helper: \"هذه هي الحالة الافتراضية\",\n          placeholder: \"النص يذهب هنا\",\n        },\n        error: {\n          label: \"حالة الخطأ\",\n          helper: \"الحالة التي يجب استخدامها عند وجود خطأ\",\n          placeholder: \"النص يذهب هنا\",\n        },\n        disabled: {\n          label: \"حالة الإعاقة\",\n          helper: \"يعطل إمكانية التحرير ويكتم النص\",\n          placeholder: \"النص يذهب هنا\",\n        },\n      },\n      passingContent: {\n        name: \"محتوى عابر\",\n        description: \"هناك عدة طرق مختلفة لتمرير المحتوى\",\n        viaLabel: {\n          labelTx: \"عبر 'label' الدعامة\",\n          helper: \"عبر 'helper' الدعامة\",\n          placeholder: \"عبر 'placeholder' الدعامة\",\n        },\n        rightAccessory: {\n          label: \"RightAccessory\",\n          helper: \"هذه الدعامة تأخذ دالة تقوم بإرجاع عنصر React\",\n        },\n        leftAccessory: {\n          label: \"LeftAccessory\",\n          helper: \"هذه الدعامة تأخذ دالة تقوم بإرجاع عنصر React\",\n        },\n        supportsMultiline: {\n          label: \"يدعم Multiline\",\n          helper: \"يتيح إدخالا اطول للنص متعدد الأسطر.\",\n        },\n      },\n      styling: {\n        name: \"التصميم\",\n        description: \"يمكن تصميم المكون بسهولة\",\n        styleInput: {\n          label: \"أسلوب الإدخال\",\n          helper: \"عبر دعامة 'Style'\",\n        },\n        styleInputWrapper: {\n          label: \"غلاف ادخال النمط\",\n          helper: \"عبر دعامة 'InputWrapperStyle'\",\n        },\n        styleContainer: {\n          label: \"حاوية النمط\",\n          helper: \"عبر دعامة 'containerstyle'\",\n        },\n        styleLabel: {\n          label: \"تسمية النمط والمساعد\",\n          helper: \"عبر أسلوب الدعامة 'LabelTextProps' & 'HelperTextProps'\",\n        },\n        styleAccessories: {\n          label: \"اكسسورات الاناقة\",\n          helper: \"عبر أسلوب الدعامة 'RightAccessory' & 'LeftAccessory'\",\n        },\n      },\n    },\n  },\n  demoToggle: {\n    description:\n      \"يقوم بعرض ادخال منطقي.هذا مكون خاضع للتحكم ويتطلب استدعاء OnValueChanger الذي يقوم بتحديث خاصية القيمة حتى يعكس المكون إجراءات المستخدم. إذا لم يتم تحديث خاصية القيمة، فسيستمر المكون في عرض خاصية القيمة المقدمة بدلا من النتيجة المتوقعة لأي إجراءات مستخدم.\",\n    useCase: {\n      variants: {\n        name: \"المتغيرات\",\n        description:\n          \"تدعم المكونات عددا قليلا من المتغيرات المختلفة. اذا كانت هناك حاجة إلى تخصيص كبير لمتغير معين، فيمكن إعادة صياغته بسهولة. الافتراضي هو 'checkbox'\",\n        checkbox: {\n          label: \"'checkbox' متغير\",\n          helper: \"يمكن استخدامه كمدخل تشغيل \\\\ إيقاف واحد\",\n        },\n        radio: {\n          label: \"'radio' متغير\",\n          helper: \"استخدام هذا عندما يكون لديك خيارات متعددة\",\n        },\n        switch: {\n          label: \"'switch' متغير\",\n          helper: \"مدخل تشغيل/إيقاف أكثر بروزا. يتمتع بدعم إمكانية الوصول بشكل أفضل.\",\n        },\n      },\n      statuses: {\n        name: \"الحالات\",\n        description:\n          \"هناك دعامة حالة مشابهة ل 'preset' في المكونات الأخرى، لكنها تؤثر على وظائف المكونات ايضاً\",\n        noStatus: \"لا توجد حالات- هذا هو الوضع الافتراضي\",\n        errorStatus: \"حالة الخطأ - استخدمها عندما يكون هناك خطأ\",\n        disabledStatus: \"حالة معطلة- تعطيل إمكانية التحرير وكتم صوت الإدخال\",\n      },\n      passingContent: {\n        name: \"محتوى عابر\",\n        description: \"هناك عدة طرق مختلفة لتمرير المحتوى\",\n        useCase: {\n          checkBox: {\n            label: \"عبر دعامة 'labelTx'\",\n            helper: \"عبر دعامة 'helpertx'\",\n          },\n          checkBoxMultiLine: {\n            helper: \"يدعم خطوط متعددة-Nulla provident consectetur labore sunt ea labore \",\n          },\n          radioChangeSides: {\n            helper: \"يمكنك تغيير الجانبين - Laborum labore adipisicing in eu ipsum deserunt.\",\n          },\n          customCheckBox: {\n            label: \"مرر أيقونة مربع الاختيار المخصص\",\n          },\n          switch: {\n            label: \"يمكن قراءة المفاتيح كنص\",\n            helper:\n              \"بشكل افتراضي، لا يستخدم هذا الخيار \\\"text' نظرا لأنه اعتمادا على الخط، قد تبدو الأحرف التي يتم تشغيلها/ايقافها غريبة. قم بالتخصيص حسب الحاجة\",\n          },\n          switchAid: {\n            label: \"او بمساعدة أيقونة\",\n          },\n        },\n      },\n      styling: {\n        name: \"التصميم\",\n        description: \"يمكن تصميم المكون بسهولة\",\n        outerWrapper: \"١- تصميم الغلاف الخارجي للإدخال\",\n        innerWrapper: \"٢- تصميم الغلاف الداخلي للإدخال\",\n        inputDetail: \"٣- تصميم تفاصيل الإدخال\",\n        labelTx: \"يمكنك ايضاً تصميم الملصق labelTx\",\n        styleContainer: \"او، قم بتصميم الحاوية بأكملها\",\n      },\n    },\n  },\n  demoButton: {\n    description:\n      \"مكون يسمح للمستخدمين بإتخاذ الإجراءات والاختيارات. يلف مكون النص بمكون قابل للضغط\",\n    useCase: {\n      presets: {\n        name: \"الإعدادات المسبقة\",\n        description: \"هناك عدد قليل من الإعدادات المسبقة التي تم تكوينها مسبقاً\",\n      },\n      passingContent: {\n        name: \"محتوى عابر\",\n        description: \"هناك عدة طرق مختلفة لتمرير المحتوى\",\n        viaTextProps: \"عبر الدعامة 'text'- Billum In\",\n        children: \"أولاد- Irure Reprehenderit\",\n        rightAccessory: \"RightAccessory - Duis Quis\",\n        leftAccessory: \"LeftAccessory - Duis Proident\",\n        nestedChildren: \"الأطفال المتداخلون-\\tprovident genial\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren3: \"Occaecat aliqua irure proident veniam.\",\n        multiLine:\n          \"Multiline - consequat veniam veniam reprehenderit. Fugiat id nisi quis duis sunt proident mollit dolor mollit adipisicing proident deserunt.\",\n      },\n      styling: {\n        name: \"التصميم\",\n        description: \"يمكن تصميم المكون بسهولة\",\n        styleContainer: \"حاوية الأسلوب- الإثارة\",\n        styleText: \"نص النمط- ِEa Anim\",\n        styleAccessories: \"اكسسوارات الاناقة - enim ea id fugiat anim ad.\",\n        pressedState: \"نمط الحالة المضغوطة - fugiat anim\",\n      },\n      disabling: {\n        name: \"تعطيل\",\n        description: \"يمكن تعطيل المكون، وتصميمه بناء على ذلك. سيتم تعطيل سلوك الضغط\",\n        standard: \"إبطال - معيار\",\n        filled: \"إبطال - مملوء\",\n        reversed: \"إبطال- معكوس\",\n        accessory: \"نمط الملحق المعطل\",\n        textStyle: \"نمط النص المعطل\",\n      },\n    },\n  },\n  demoListItem: {\n    description: \"مكون صف مصمم يمكن استخدامه في FlatList او SectionList او بمفرده\",\n    useCase: {\n      height: {\n        name: \"علو\",\n        description: \"يمكن ان يكون الصف بارتفاعات مختلفة\",\n        defaultHeight: \"الارتفاع الافتراضي (56px)\",\n        customHeight: \"ارتفاع مخصص عبر دعامة 'height'\",\n        textHeight:\n          \"الارتفاع يتم تحديده من خلال محتوى النص - Reprehenderit incididunt deserunt do do ea labore.\",\n        longText: \"تحديد النص إلى سطر واحد - Reprehenderit incididunt deserunt do do ea labore.\",\n      },\n      separators: {\n        name: \"الفواصل\",\n        description: \"الفاصل/ المقسم مهيّأ مسبقاً وهو اختياري\",\n        topSeparator: \"فقط فاصل علوي\",\n        topAndBottomSeparator: \"الفواصل العلوية والسفلية\",\n        bottomSeparator: \"فقط فاصل سفلي\",\n      },\n      icons: {\n        name: \"الأيقونات\",\n        description: \"يمكنك تخصيص الرموز على اليسار أو اليمين\",\n        leftIcon: \"أيقونة اليسار\",\n        rightIcon: \"أيقونة اليمين\",\n        leftRightIcons: \"أيقونة اليمين واليسار\",\n      },\n      customLeftRight: {\n        name: \"مكونات مخصصة لليسار /اليمين\",\n        description: \"اذا كنت بحاجة إلى مخصص لليسار/اليمين فيمكنك تمريره\",\n        customLeft: \"مكون يسار مخصص\",\n        customRight: \"مكون يمين مخصص\",\n      },\n      passingContent: {\n        name: \"محتوى عابر\",\n        description: \"هناك عدة طرق مختلفة لتمرير المحتوى\",\n        text: \"عبر دعامة 'text' - reprehenderit sint\",\n        children: \"أولاد- mostrud mollit\",\n        nestedChildren1: \"الأولاد المتداخلون - proident veniam.\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n      },\n      listIntegration: {\n        name: \"دمج مع/ FlatList\",\n        description: \"يمكن دمج المكون بسهولة مع واجهة القائمة المفضلة لديك\",\n      },\n      styling: {\n        name: \"التصميم\",\n        description: \"يمكن تصميم المكون بسهولة.\",\n        styledText: \"نص مصمم\",\n        styledContainer: \"حاوية مصممة (فواصل)\",\n        tintedIcons: \"أيقونات ملونة\",\n      },\n    },\n  },\n  demoCard: {\n    description:\n      \"البطاقات مفيدة لعرض المعلومات ذات الصلة بطريقة محددة. اذا كان ListItem يعرض المحتوى أفقياً، فيمكن استخدام البطاقة لعرض المحتوى رأسياً.\",\n    useCase: {\n      presets: {\n        name: \"الإعدادات المسبقة\",\n        description: \"هناك عدد قليل من الإعدادات المسبقة التي تم تكوينها مسبقاً\",\n        default: {\n          heading: \"الأعداد المسبق الافتراضي ( تقصير)\",\n          content: \"Incididunt magna ut aliquip consectetur mollit dolor.\",\n          footer: \"Consectetur nulla non aliquip velit.\",\n        },\n        reversed: {\n          heading: \"الأعداد المسبق المعكوس\",\n          content: \"Reprehenderit occaecat proident amet id laboris.\",\n          footer: \"Consectetur tempor ea non labore anim .\",\n        },\n      },\n      verticalAlignment: {\n        name: \"انحياز عمودي\",\n        description:\n          \"اعتمادا على ما هو مطلوب، تأتي البطاقة مهيأة مسبقاً باستراتيجيات محاذاة مختلفة\",\n        top: {\n          heading: \"قمة (تقصير)\",\n          content: \"يتم محاذاة كل محتوى تلقائياً إلى الأعلى\",\n          footer: \"حتى التذييل\",\n        },\n        center: {\n          heading: \"مركز\",\n          content: \"يتم تركيز المحتوى بالنسبة لارتفاع البطاقة\",\n          footer: \"أنا ايضاً!\",\n        },\n        spaceBetween: {\n          heading: \"مسافة بين الكلمات\",\n          content: \"يتم توزيع جميع المحتويات بالتساوي\",\n          footer: \"أنا حيث أريد ان أكون\",\n        },\n        reversed: {\n          heading: \"Force Footer Bottom\",\n          content: \"يؤدي هذا إلى دفع التذييل إلى المكان الذي ينتمي اليه.\",\n          footer: \"أنا وحد جداًهنا\",\n        },\n      },\n      passingContent: {\n        name: \"محتوى عابر\",\n        description: \"هناك عدة طرق مختلفة لتمرير المحتوى.\",\n        heading: \"عبر دعم 'heading'\",\n        content: \"عبر دعم 'content'\",\n        footer: \"أنا وحيد هنا.\",\n      },\n      customComponent: {\n        name: \"مكونات مخصصة\",\n        description:\n          \"يمكن استبدال اي من المكونات المعدة مسبقاً بمكوناتك الخاصة. يمكنك ايضاً اضافة مكونات إضافية.\",\n        rightComponent: \"RightComponent\",\n        leftComponent: \"LeftComponent\",\n      },\n      style: {\n        name: \"التصميم\",\n        description: \"يمكن تصميم المكون بسهولة.\",\n        heading: \"صمم العنوان\",\n        content: \"صمم المحتوى\",\n        footer: \"صمم التذييل\",\n      },\n    },\n  },\n  demoAutoImage: {\n    description: \"مكون صورة يحدد حجم الصورة البعيدة او صورة data-uri\",\n    useCase: {\n      remoteUri: {\n        name: \"عن بعد URI\",\n      },\n      base64Uri: {\n        name: \"Base64 URI\",\n      },\n      scaledToFitDimensions: {\n        name: \"تم قياسها لتناسب الأبعاد\",\n        description:\n          \" توفيرعرض  'maxWidth' و\\\\او 'maxHeight' ، سيتم عرض الصورة بنسبة عرض الى ارتفاع. كيف يختلف هذا عن 'resizeMode': 'contain'? اولاً،يمكنك تحديد حجم جانب واحد فقط. (ليس كلاهما). ثانياً، سيتم تغيير الصورة لتناسب الأبعاد المطلوبة بدلاً من مجرد احتوائها داخل حاوية الصورة الخاصة بها.\",\n        heightAuto: \" عرض : ٦٠ / طول:  auto\",\n        widthAuto: \"عرض: auto / طول: ٣٢\",\n        bothManual: \"عرض :٦٠ / طول : ٦٠\",\n      },\n    },\n  },\n  demoText: {\n    description:\n      \"لتلبية احتياجاتك في عرض النصوص. هذا المكون عبارة عن HOC فوق المكون المدمج Native React.\",\n    useCase: {\n      presets: {\n        name: \"الإعدادات المسبقة\",\n        description: \"هناك عدد قليل من الإعدادات المسبقة التي تم تكوينها مسبقاً.\",\n        default:\n          \"الأعداد المسبق الافتراضي - Cillum eu laboris in labore. Excepteur mollit tempor reprehenderit fugiat elit et eu consequat laborum.\",\n        bold: \"bold preset - Tempor et ullamco cupidatat in officia. Nulla ea duis elit id sunt ipsum cillum duis deserunt nostrud ut nostrud id.\",\n        subheading: \"subheading preset - In Cupidatat Cillum.\",\n        heading: \"heading preset - Voluptate Adipis.\",\n      },\n      sizes: {\n        name: \"قياسات\",\n        description: \"هناك حجم الدعامة\",\n        xs: \"xs - Ea ipsum est ea ex sunt.\",\n        sm: \"sm - Lorem sunt adipisicin.\",\n        md: \"md - Consequat id do lorem.\",\n        lg: \"lg - Nostrud ipsum ea.\",\n        xl: \"xl - Eiusmod ex excepteur.\",\n        xxl: \"xxl - Cillum eu laboris.\",\n      },\n      weights: {\n        name: \"أوزان\",\n        description: \"هناك وزن الدعامة\",\n        light:\n          \"light - Nulla magna incididunt excepteur est occaecat duis culpa dolore cupidatat enim et.\",\n        normal:\n          \"normal - Magna incididunt dolor ut veniam veniam laboris aliqua velit ea incididunt.\",\n        medium: \"medium - Non duis laborum quis laboris occaecat culpa cillum.\",\n        semibold: \"semiBold - Exercitation magna nostrud pariatur laborum occaecat aliqua.\",\n        bold: \"bold - Eiusmod ullamco magna exercitation est excepteur.\",\n      },\n      passingContent: {\n        name: \"محتوى عابر\",\n        description: \"هناك عدة طرق مختلفة لتمرير المحتوى.\",\n        viaText:\n          \"via `text` prop - Billum in aute fugiat proident nisi pariatur est. Cupidatat anim cillum eiusmod ad. Officia eu magna aliquip labore dolore consequat.\",\n        viaTx: \"عبر دعامة 'tx'\",\n        children: \"childrenreprehenderit eu qui amet veniam consectetur.\",\n        nestedChildren: \"الأطفال المتداخلون\",\n        nestedChildren2: \"Occaecat aliqua irure proident veniam.\",\n        nestedChildren3: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren4: \"Occaecat aliqua irure proident veniam.\",\n      },\n      styling: {\n        name: \"التصميم\",\n        description: \"يمكن تصميم المكون بسهولة.\",\n        text: \"Consequat ullamco veniam velit mollit proident excepteur aliquip id culpa ipsum velit sint nostrud.\",\n        text2:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n        text3:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n      },\n    },\n  },\n  demoHeader: {\n    description: \"المكون الذي يظهر على العديد من الشاشات، سيحمل ازرار التنقل وعنوان الشاشة.\",\n    useCase: {\n      actionIcons: {\n        name: \"أيقونة الإجرائات \",\n        description: \"يمكنك بسهولة تمرير الرموزالى مكونات الاجراء اليسرى او اليمنى.\",\n        leftIconTitle: \"الرمز الأيسر\",\n        rightIconTitle: \"الرمز الأيمن \",\n        bothIconsTitle: \"كلا الرمزين\",\n      },\n      actionText: {\n        name: \"نص العمل\",\n        description: \"يمكنك بسهولة تمرير النص الى مكونات الاجراء اليسرى او اليمنى.\",\n        leftTxTitle: \"عبر 'leftTx' \",\n        rightTextTitle: \"عبر `rightText`\",\n      },\n      customActionComponents: {\n        name: \"مكونات الاجراء المخصص\",\n        description:\n          \"اذا لم تكن خيارات الرمز او النسكافية، فيمكنك تمرير مكون الاجراء المخصص الخاص بك.\",\n        customLeftActionTitle: \"عمل يسار مخصص \",\n      },\n      titleModes: {\n        name: \"اوضاع العنوان\",\n        description:\n          \"يمكن اجبار العنوان على البقاء غي المنتصف ولكن قد يتم قطعه اذا كان طويلاً للغاية. يمكنك بشكل اختياري تعديله وفقاً لأزرار الإجراء.\",\n        centeredTitle: \"عنوان مركزي\",\n        flexTitle: \"عنوان مرن\",\n      },\n      styling: {\n        name: \"التصميم\",\n        description: \"يمكن تصميم المكون بسهولة\",\n        styledTitle: \"عنوان مصمم\",\n        styledWrapperTitle: \"غلاف مصمم\",\n        tintedIconsTitle: \"أيقونات ملونة\",\n      },\n    },\n  },\n  demoEmptyState: {\n    description:\n      \"مكون يتم استخدامه عندما لا يكون هناك بيانات لعرضها. ويمكن استخدامه لتوجيه المستخدم الى ما يجب فعله بعد ذلك.\",\n    useCase: {\n      presets: {\n        name: \"الإعدادات المسبقة\",\n        description:\n          \"يمكن إنشاء نص/صورة مختلفة مجموعات. واحد محدد مسبقاً يسمى 'generic'. لاحظ انه لا يوجد اي خيار افتراضي في حال رغبتك في الحصول على كامل  EmptyState مخصصة.\",\n      },\n      passingContent: {\n        name: \"محتوى عابر\",\n        description: \"هناك عدة طرق مختلفة لتمرير المحتوى.\",\n        customizeImageHeading: \"تخصيص الصورة\",\n        customizeImageContent: \"يمكنك تمرير اي مصدر للصورة\",\n        viaHeadingProp: \"عبر دعامة 'heading'\",\n        viaContentProp: \"عبر دعامة 'content'\",\n        viaButtonProp: \"عبر دعامة 'button'\",\n      },\n      styling: {\n        name: \"التصميم\",\n        description: \"يمكن تصميم المكون بسهولة.\",\n      },\n    },\n  },\n}\n\nexport default demoAr\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/i18n/demo-en.ts",
    "content": "export const demoEn = {\n  demoIcon: {\n    description:\n      \"A component to render a registered icon. It is wrapped in a <TouchableOpacity /> if `onPress` is provided, otherwise a <View />.\",\n    useCase: {\n      icons: {\n        name: \"Icons\",\n        description: \"List of icons registered inside the component.\",\n      },\n      size: {\n        name: \"Size\",\n        description: \"There's a size prop.\",\n      },\n      color: {\n        name: \"Color\",\n        description: \"There's a color prop.\",\n      },\n      styling: {\n        name: \"Styling\",\n        description: \"The component can be styled easily.\",\n      },\n    },\n  },\n  demoTextField: {\n    description: \"TextField component allows for the entering and editing of text.\",\n    useCase: {\n      statuses: {\n        name: \"Statuses\",\n        description:\n          \"There is a status prop - similar to `preset` in other components, but affects component functionality as well.\",\n        noStatus: {\n          label: \"No Status\",\n          helper: \"This is the default status\",\n          placeholder: \"Text goes here\",\n        },\n        error: {\n          label: \"Error Status\",\n          helper: \"Status to use when there is an error\",\n          placeholder: \"Text goes here\",\n        },\n        disabled: {\n          label: \"Disabled Status\",\n          helper: \"Disables the editability and mutes text\",\n          placeholder: \"Text goes here\",\n        },\n      },\n      passingContent: {\n        name: \"Passing Content\",\n        description: \"There are a few different ways to pass content.\",\n        viaLabel: {\n          labelTx: \"Via `label` prop\",\n          helper: \"Via `helper` prop\",\n          placeholder: \"Via `placeholder` prop\",\n        },\n        rightAccessory: {\n          label: \"RightAccessory\",\n          helper: \"This prop takes a function that returns a React element.\",\n        },\n        leftAccessory: {\n          label: \"LeftAccessory\",\n          helper: \"This prop takes a function that returns a React element.\",\n        },\n        supportsMultiline: {\n          label: \"Supports Multiline\",\n          helper: \"Enables a taller input for multiline text.\",\n        },\n      },\n      styling: {\n        name: \"Styling\",\n        description: \"The component can be styled easily.\",\n        styleInput: {\n          label: \"Style Input\",\n          helper: \"Via `style` prop\",\n        },\n        styleInputWrapper: {\n          label: \"Style Input Wrapper\",\n          helper: \"Via `inputWrapperStyle` prop\",\n        },\n        styleContainer: {\n          label: \"Style Container\",\n          helper: \"Via `containerStyle` prop\",\n        },\n        styleLabel: {\n          label: \"Style Label & Helper\",\n          helper: \"Via `LabelTextProps` & `HelperTextProps` style prop\",\n        },\n        styleAccessories: {\n          label: \"Style Accessories\",\n          helper: \"Via `RightAccessory` & `LeftAccessory` style prop\",\n        },\n      },\n    },\n  },\n  demoToggle: {\n    description:\n      \"Renders a boolean input. This is a controlled component that requires an onValueChange callback that updates the value prop in order for the component to reflect user actions. If the value prop is not updated, the component will continue to render the supplied value prop instead of the expected result of any user actions.\",\n    useCase: {\n      variants: {\n        name: \"Variants\",\n        description:\n          \"The component supports a few different variants. If heavy customization of a specific variant is needed, it can be easily refactored. The default is `checkbox`.\",\n        checkbox: {\n          label: \"`checkbox` variant\",\n          helper: \"This can be used for a single on/off input.\",\n        },\n        radio: {\n          label: \"`radio` variant\",\n          helper: \"Use this when you have multiple options.\",\n        },\n        switch: {\n          label: \"`switch` variant\",\n          helper: \"A more prominent on/off input. Has better accessibility support.\",\n        },\n      },\n      statuses: {\n        name: \"Statuses\",\n        description:\n          \"There is a status prop - similar to `preset` in other components, but affects component functionality as well.\",\n        noStatus: \"No status - this is the default\",\n        errorStatus: \"Error status - use when there is an error\",\n        disabledStatus: \"Disabled status - disables the editability and mutes input\",\n      },\n      passingContent: {\n        name: \"Passing Content\",\n        description: \"There are a few different ways to pass content.\",\n        useCase: {\n          checkBox: {\n            label: \"Via `labelTx` prop\",\n            helper: \"Via `helperTx` prop.\",\n          },\n          checkBoxMultiLine: {\n            helper: \"Supports multiline - Nulla proident consectetur labore sunt ea labore. \",\n          },\n          radioChangeSides: {\n            helper: \"You can change sides - Laborum labore adipisicing in eu ipsum deserunt.\",\n          },\n          customCheckBox: {\n            label: \"Pass in a custom checkbox icon.\",\n          },\n          switch: {\n            label: \"Switches can be read as text\",\n            helper:\n              \"By default, this option doesn't use `Text` since depending on the font, the on/off characters might look weird. Customize as needed.\",\n          },\n          switchAid: {\n            label: \"Or aided with an icon\",\n          },\n        },\n      },\n      styling: {\n        name: \"Styling\",\n        description: \"The component can be styled easily.\",\n        outerWrapper: \"1 - style the input outer wrapper\",\n        innerWrapper: \"2 - style the input inner wrapper\",\n        inputDetail: \"3 - style the input detail\",\n        labelTx: \"You can also style the labelTx\",\n        styleContainer: \"Or, style the entire container\",\n      },\n    },\n  },\n  demoButton: {\n    description:\n      \"A component that allows users to take actions and make choices. Wraps the Text component with a Pressable component.\",\n    useCase: {\n      presets: {\n        name: \"Presets\",\n        description: \"There are a few presets that are preconfigured.\",\n      },\n      passingContent: {\n        name: \"Passing Content\",\n        description: \"There are a few different ways to pass content.\",\n        viaTextProps: \"Via `text` Prop - Billum In\",\n        children: \"Children - Irure Reprehenderit\",\n        rightAccessory: \"RightAccessory - Duis Quis\",\n        leftAccessory: \"LeftAccessory - Duis Proident\",\n        nestedChildren: \"Nested children - proident veniam.\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren3: \"Occaecat aliqua irure proident veniam.\",\n        multiLine:\n          \"Multiline - consequat veniam veniam reprehenderit. Fugiat id nisi quis duis sunt proident mollit dolor mollit adipisicing proident deserunt.\",\n      },\n      styling: {\n        name: \"Styling\",\n        description: \"The component can be styled easily.\",\n        styleContainer: \"Style Container - Exercitation\",\n        styleText: \"Style Text - Ea Anim\",\n        styleAccessories: \"Style Accessories - enim ea id fugiat anim ad.\",\n        pressedState: \"Style Pressed State - fugiat anim\",\n      },\n      disabling: {\n        name: \"Disabling\",\n        description:\n          \"The component can be disabled, and styled based on that. Press behavior will be disabled.\",\n        standard: \"Disabled - standard\",\n        filled: \"Disabled - filled\",\n        reversed: \"Disabled - reversed\",\n        accessory: \"Disabled accessory style\",\n        textStyle: \"Disabled text style\",\n      },\n    },\n  },\n  demoListItem: {\n    description: \"A styled row component that can be used in FlatList, SectionList, or by itself.\",\n    useCase: {\n      height: {\n        name: \"Height\",\n        description: \"The row can be different heights.\",\n        defaultHeight: \"Default height (56px)\",\n        customHeight: \"Custom height via `height` prop\",\n        textHeight:\n          \"Height determined by text content - Reprehenderit incididunt deserunt do do ea labore.\",\n        longText:\n          \"Limit long text to one line - Reprehenderit incididunt deserunt do do ea labore.\",\n      },\n      separators: {\n        name: \"Separators\",\n        description: \"The separator / divider is preconfigured and optional.\",\n        topSeparator: \"Only top separator\",\n        topAndBottomSeparator: \"Top and bottom separators\",\n        bottomSeparator: \"Only bottom separator\",\n      },\n      icons: {\n        name: \"Icons\",\n        description: \"You can customize the icons on the left or right.\",\n        leftIcon: \"Left icon\",\n        rightIcon: \"Right Icon\",\n        leftRightIcons: \"Left & Right Icons\",\n      },\n      customLeftRight: {\n        name: \"Custom Left/Right Components\",\n        description: \"If you need a custom left/right component, you can pass it in.\",\n        customLeft: \"Custom left component\",\n        customRight: \"Custom right component\",\n      },\n      passingContent: {\n        name: \"Passing Content\",\n        description: \"There are a few different ways to pass content.\",\n        text: \"Via `text` prop - reprehenderit sint\",\n        children: \"Children - mostrud mollit\",\n        nestedChildren1: \"Nested children - proident veniam.\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n      },\n      listIntegration: {\n        name: \"Integrating w/ FlatList\",\n        description: \"The component can be easily integrated with your favorite list interface.\",\n      },\n      styling: {\n        name: \"Styling\",\n        description: \"The component can be styled easily.\",\n        styledText: \"Styled Text\",\n        styledContainer: \"Styled Container (separators)\",\n        tintedIcons: \"Tinted Icons\",\n      },\n    },\n  },\n  demoCard: {\n    description:\n      \"Cards are useful for displaying related information in a contained way. If a ListItem displays content horizontally, a Card can be used to display content vertically.\",\n    useCase: {\n      presets: {\n        name: \"Presets\",\n        description: \"There are a few presets that are preconfigured.\",\n        default: {\n          heading: \"Default Preset (default)\",\n          content: \"Incididunt magna ut aliquip consectetur mollit dolor.\",\n          footer: \"Consectetur nulla non aliquip velit.\",\n        },\n        reversed: {\n          heading: \"Reversed Preset\",\n          content: \"Reprehenderit occaecat proident amet id laboris.\",\n          footer: \"Consectetur tempor ea non labore anim .\",\n        },\n      },\n      verticalAlignment: {\n        name: \"Vertical Alignment\",\n        description:\n          \"Depending on what's required, the card comes preconfigured with different alignment strategies.\",\n        top: {\n          heading: \"Top (default)\",\n          content: \"All content is automatically aligned to the top.\",\n          footer: \"Even the footer\",\n        },\n        center: {\n          heading: \"Center\",\n          content: \"Content is centered relative to the card's height.\",\n          footer: \"Me too!\",\n        },\n        spaceBetween: {\n          heading: \"Space Between\",\n          content: \"All content is spaced out evenly.\",\n          footer: \"I am where I want to be.\",\n        },\n        reversed: {\n          heading: \"Force Footer Bottom\",\n          content: \"This pushes the footer where it belongs.\",\n          footer: \"I'm so lonely down here.\",\n        },\n      },\n      passingContent: {\n        name: \"Passing Content\",\n        description: \"There are a few different ways to pass content.\",\n        heading: \"Via `heading` Prop\",\n        content: \"Via `content` Prop\",\n        footer: \"I'm so lonely down here.\",\n      },\n      customComponent: {\n        name: \"Custom Components\",\n        description:\n          \"Any of the preconfigured components can be replaced with your own. You can also add additional ones.\",\n        rightComponent: \"RightComponent\",\n        leftComponent: \"LeftComponent\",\n      },\n      style: {\n        name: \"Styling\",\n        description: \"The component can be styled easily.\",\n        heading: \"Style the Heading\",\n        content: \"Style the Content\",\n        footer: \"Style the Footer\",\n      },\n    },\n  },\n  demoAutoImage: {\n    description: \"An Image component that automatically sizes a remote or data-uri image.\",\n    useCase: {\n      remoteUri: { name: \"Remote URI\" },\n      base64Uri: { name: \"Base64 URI\" },\n      scaledToFitDimensions: {\n        name: \"Scaled to Fit Dimensions\",\n        description:\n          \"Providing a `maxWidth` and/or `maxHeight` props, the image will automatically scale while retaining it's aspect ratio. How is this different from `resizeMode: 'contain'`? Firstly, you can specify only one side's size (not both). Secondly, the image will scale to fit the desired dimensions instead of just being contained within its image-container.\",\n        heightAuto: \"width: 60 / height: auto\",\n        widthAuto: \"width: auto / height: 32\",\n        bothManual: \"width: 60 / height: 60\",\n      },\n    },\n  },\n  demoText: {\n    description:\n      \"For your text displaying needs. This component is a HOC over the built-in React Native one.\",\n    useCase: {\n      presets: {\n        name: \"Presets\",\n        description: \"There are a few presets that are preconfigured.\",\n        default:\n          \"default preset - Cillum eu laboris in labore. Excepteur mollit tempor reprehenderit fugiat elit et eu consequat laborum.\",\n        bold: \"bold preset - Tempor et ullamco cupidatat in officia. Nulla ea duis elit id sunt ipsum cillum duis deserunt nostrud ut nostrud id.\",\n        subheading: \"subheading preset - In Cupidatat Cillum.\",\n        heading: \"heading preset - Voluptate Adipis.\",\n      },\n      sizes: {\n        name: \"Sizes\",\n        description: \"There's a size prop.\",\n        xs: \"xs - Ea ipsum est ea ex sunt.\",\n        sm: \"sm - Lorem sunt adipisicin.\",\n        md: \"md - Consequat id do lorem.\",\n        lg: \"lg - Nostrud ipsum ea.\",\n        xl: \"xl - Eiusmod ex excepteur.\",\n        xxl: \"xxl - Cillum eu laboris.\",\n      },\n      weights: {\n        name: \"Weights\",\n        description: \"There's a weight prop.\",\n        light:\n          \"light - Nulla magna incididunt excepteur est occaecat duis culpa dolore cupidatat enim et.\",\n        normal:\n          \"normal - Magna incididunt dolor ut veniam veniam laboris aliqua velit ea incididunt.\",\n        medium: \"medium - Non duis laborum quis laboris occaecat culpa cillum.\",\n        semibold: \"semiBold - Exercitation magna nostrud pariatur laborum occaecat aliqua.\",\n        bold: \"bold - Eiusmod ullamco magna exercitation est excepteur.\",\n      },\n      passingContent: {\n        name: \"Passing Content\",\n        description: \"There are a few different ways to pass content.\",\n        viaText:\n          \"via `text` prop - Billum in aute fugiat proident nisi pariatur est. Cupidatat anim cillum eiusmod ad. Officia eu magna aliquip labore dolore consequat.\",\n        viaTx: \"via `tx` prop -\",\n        children: \"children - Aliqua velit irure reprehenderit eu qui amet veniam consectetur.\",\n        nestedChildren: \"Nested children -\",\n        nestedChildren2: \"Occaecat aliqua irure proident veniam.\",\n        nestedChildren3: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren4: \"Occaecat aliqua irure proident veniam.\",\n      },\n      styling: {\n        name: \"Styling\",\n        description: \"The component can be styled easily.\",\n        text: \"Consequat ullamco veniam velit mollit proident excepteur aliquip id culpa ipsum velit sint nostrud.\",\n        text2:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n        text3:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n      },\n    },\n  },\n  demoHeader: {\n    description:\n      \"Component that appears on many screens. Will hold navigation buttons and screen title.\",\n    useCase: {\n      actionIcons: {\n        name: \"Action Icons\",\n        description: \"You can easily pass in icons to the left or right action components.\",\n        leftIconTitle: \"Left Icon\",\n        rightIconTitle: \"Right Icon\",\n        bothIconsTitle: \"Both Icons\",\n      },\n      actionText: {\n        name: \"Action Text\",\n        description: \"You can easily pass in text to the left or right action components.\",\n        leftTxTitle: \"Via `leftTx`\",\n        rightTextTitle: \"Via `rightText`\",\n      },\n      customActionComponents: {\n        name: \"Custom Action Components\",\n        description:\n          \"If the icon or text options are not enough, you can pass in your own custom action component.\",\n        customLeftActionTitle: \"Custom Left Action\",\n      },\n      titleModes: {\n        name: \"Title Modes\",\n        description:\n          \"Title can be forced to stay in center (default) but may be cut off if it's too long. You can optionally make it adjust to the action buttons.\",\n        centeredTitle: \"Centered Title\",\n        flexTitle: \"Flex Title\",\n      },\n      styling: {\n        name: \"Styling\",\n        description: \"The component can be styled easily.\",\n        styledTitle: \"Styled Title\",\n        styledWrapperTitle: \"Styled Wrapper\",\n        tintedIconsTitle: \"Tinted Icons\",\n      },\n    },\n  },\n  demoEmptyState: {\n    description:\n      \"A component to use when there is no data to display. It can be utilized to direct the user what to do next\",\n    useCase: {\n      presets: {\n        name: \"Presets\",\n        description:\n          \"You can create different text/image sets. One is predefined called `generic`. Note, there's no default in case you want to have a completely custom EmptyState.\",\n      },\n      passingContent: {\n        name: \"Passing Content\",\n        description: \"There are a few different ways to pass content.\",\n        customizeImageHeading: \"Customize Image\",\n        customizeImageContent: \"You can pass in any image source.\",\n        viaHeadingProp: \"Via `heading` Prop\",\n        viaContentProp: \"Via `content` prop.\",\n        viaButtonProp: \"Via `button` Prop\",\n      },\n      styling: {\n        name: \"Styling\",\n        description: \"The component can be styled easily.\",\n      },\n    },\n  },\n}\n\nexport default demoEn\nexport type DemoTranslations = typeof demoEn\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/i18n/demo-es.ts",
    "content": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoEs: DemoTranslations = {\n  demoIcon: {\n    description:\n      \"Un componente para dibujar un ícono pre-definido. Si se proporciona el atributo `onPress`, se rodea por un componente <TouchableOpacity />. De lo contrario, se rodea por un componente <View />.\",\n    useCase: {\n      icons: {\n        name: \"Íconos\",\n        description: \"Lista de los íconos pre-definidos para el componente.\",\n      },\n      size: {\n        name: \"Tamaño\",\n        description: \"Hay un atributo para el tamaño.\",\n      },\n      color: {\n        name: \"Color\",\n        description: \"Hay un atributo para el color.\",\n      },\n      styling: {\n        name: \"Estilo\",\n        description: \"El componente puede ser configurado fácilmente.\",\n      },\n    },\n  },\n  demoTextField: {\n    description: \"El componente <TextField /> permite el ingreso y edición de texto.\",\n    useCase: {\n      statuses: {\n        name: \"Estados\",\n        description:\n          \"Hay un atributo para el estado - similar a `preset` en otros componentes, pero que además impacta en la funcionalidad del componente.\",\n        noStatus: {\n          label: \"Sin estado\",\n          helper: \"Este es el estado por defecto\",\n          placeholder: \"El texto va acá\",\n        },\n        error: {\n          label: \"Estado de error\",\n          helper: \"Estado para usar en caso de error\",\n          placeholder: \"El texto va acá\",\n        },\n        disabled: {\n          label: \"Estado desactivado\",\n          helper: \"Desactiva la edición y atenúa el texto\",\n          placeholder: \"El texto va acá\",\n        },\n      },\n      passingContent: {\n        name: \"Entregando contenido\",\n        description: \"Hay varias formas de entregar contenido.\",\n        viaLabel: {\n          labelTx: \"A través del atributo `label`\",\n          helper: \"A través del atributo `helper`\",\n          placeholder: \"A través del atributo `placeholder`\",\n        },\n        rightAccessory: {\n          label: \"Complemento derecho\",\n          helper: \"Este atributo requiere una función que retorne un elemento React.\",\n        },\n        leftAccessory: {\n          label: \"Complemento izquierdo\",\n          helper: \"Este atributo requiere una función que retorne un elemento React.\",\n        },\n        supportsMultiline: {\n          label: \"Soporta múltilíneas\",\n          helper: \"Permite un input de texto más largo para texto multilinea.\",\n        },\n      },\n      styling: {\n        name: \"Estilo\",\n        description: \"El componente puede ser configurado fácilmente.\",\n        styleInput: {\n          label: \"Estilo del input\",\n          helper: \"A través de el atributo `style`\",\n        },\n        styleInputWrapper: {\n          label: \"Estilo del contenedor del input\",\n          helper: \"A través de el atributo `inputWrapperStyle`\",\n        },\n        styleContainer: {\n          label: \"Estilo del contenedor\",\n          helper: \"A través de el atributo `containerStyle`\",\n        },\n        styleLabel: {\n          label: \"Estilo de la etiqueta y texto de ayuda\",\n          helper: \"A través de las props de estilo `LabelTextProps` y `HelperTextProps`\",\n        },\n        styleAccessories: {\n          label: \"Estilo de los accesorios\",\n          helper: \"A través de las props de estilo `RightAccessory` y `LeftAccessory`\",\n        },\n      },\n    },\n  },\n  demoToggle: {\n    description:\n      \"Dibuja un switch de tipo booleano. Este componente requiere un callback `onValueChange` que actualice el atributo `value` para que este refleje las acciones del usuario. Si el atributo `value` no se actualiza, el componente seguirá mostrando el valor proporcionado por defecto en lugar de lo esperado por las acciones del usuario.\",\n    useCase: {\n      variants: {\n        name: \"Variantes\",\n        description:\n          \"El componente soporta diferentes variantes. Si se necesita una personalización más avanzada o variante específica, puede ser fácilmente refactorizada. El valor por defecto es `checkbox`.\",\n        checkbox: {\n          label: \"Variante `checkbox`\",\n          helper: \"Puede ser utilizada para un único valor del tipo on/off.\",\n        },\n        radio: {\n          label: \"Variante `radio`\",\n          helper: \"Usa esto cuando tengas múltiples opciones.\",\n        },\n        switch: {\n          label: \"Variante `switch`\",\n          helper:\n            \"Una entrada del tipo on/off que sobresale más. Tiene mejor soporte de accesibilidad.\",\n        },\n      },\n      statuses: {\n        name: \"Estados\",\n        description:\n          \"Hay un atributo de estado - similar a `preset` en otros componentes, pero que además impacta en la funcionalidad del componente.\",\n        noStatus: \"Sin estado - este es el valor por defecto\",\n        errorStatus: \"Estado de error - para usar cuando haya un error\",\n        disabledStatus: \"Estado desactivado - desactiva la edición y silencia el input\",\n      },\n      passingContent: {\n        name: \"Entregando contenido\",\n        description: \"Hay varias formas de entregar contenido.\",\n        useCase: {\n          checkBox: {\n            label: \"A través del atributo `labelTx`\",\n            helper: \"A través del atributo `helperTx`.\",\n          },\n          checkBoxMultiLine: {\n            helper: \"Soporta multi líneas - Nulla proident consectetur labore sunt ea labore.\",\n          },\n          radioChangeSides: {\n            helper: \"Puedes cambiarle el lado - Laborum labore adipisicing in eu ipsum deserunt.\",\n          },\n          customCheckBox: {\n            label: \"Pasa un ícono para un checkbox personalizado.\",\n          },\n          switch: {\n            label: \"Los interruptores pueden leerse como texto\",\n            helper:\n              \"Por defecto, esta opción no usa `Text` ya que, dependiendo de la fuente, los caracteres on/off podrían no dibujarse bien. Personalízalo según tus necesidades.\",\n          },\n          switchAid: {\n            label: \"O con la ayuda de un ícono\",\n          },\n        },\n      },\n      styling: {\n        name: \"Estilo\",\n        description: \"El componente puede ser configurado fácilmente.\",\n        outerWrapper: \"1 - configura el contenedor externo del input\",\n        innerWrapper: \"2 - configura el contenedor interno del input\",\n        inputDetail: \"3 - configura el detalle del input\",\n        labelTx: \"También puedes configurar el atributo labelTx\",\n        styleContainer: \"O, configura todo el contenedor\",\n      },\n    },\n  },\n  demoButton: {\n    description:\n      \"Un componente que permite a los usuarios realizar acciones y hacer elecciones. Rodea un componente Text con otro componente Pressable.\",\n    useCase: {\n      presets: {\n        name: \"Preajustes\",\n        description: \"Hay algunos preajustes por defecto.\",\n      },\n      passingContent: {\n        name: \"Entregando contenido\",\n        description: \"Hay varias formas de entregar contenido.\",\n        viaTextProps: \"A través del atributo `text` - Billum In\",\n        children: \"Contenido anidado (children) - Irure Reprehenderit\",\n        rightAccessory: \"Componente derecho - Duis Quis\",\n        leftAccessory: \"Componente izquierdo - Duis Proident\",\n        nestedChildren: \"Contenido anidado - proident veniam.\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren3: \"Occaecat aliqua irure proident veniam.\",\n        multiLine:\n          \"Multilínea - consequat veniam veniam reprehenderit. Fugiat id nisi quis duis sunt proident mollit dolor mollit adipisicing proident deserunt.\",\n      },\n      styling: {\n        name: \"Estilo\",\n        description: \"El componente puede ser configurando fácilmente.\",\n        styleContainer: \"Estilo del contenedor - Exercitation\",\n        styleText: \"Estilo del texto - Ea Anim\",\n        styleAccessories: \"Estilo de los componentes - enim ea id fugiat anim ad.\",\n        pressedState: \"Estilo para el estado presionado - fugiat anim\",\n      },\n      disabling: {\n        name: \"Desactivado\",\n        description:\n          \"El componente puede ser desactivado y como consecuencia, estilizado. El comportamiento para hacer clic será desactivado.\",\n        standard: \"Desactivado - estándar\",\n        filled: \"Desactivado - relleno\",\n        reversed: \"Desactivado - invertido\",\n        accessory: \"Estilo del componente desactivado\",\n        textStyle: \"Estilo del texto desactivado\",\n      },\n    },\n  },\n  demoListItem: {\n    description:\n      \"Un componente estilizado que representa una fila para ser utilizada dentro de un FlatList, SectionList o por sí solo.\",\n    useCase: {\n      height: {\n        name: \"Altura\",\n        description: \"La fila puede tener diferentes alturas.\",\n        defaultHeight: \"Altura por defecto (56px)\",\n        customHeight: \"Altura personalizada a través del atributo `height`\",\n        textHeight:\n          \"Altura determinada por el contenido del texto - Reprehenderit incididunt deserunt do do ea labore.\",\n        longText:\n          \"Limitar texto largo a solo una línea - Reprehenderit incididunt deserunt do do ea labore.\",\n      },\n      separators: {\n        name: \"Separadores\",\n        description: \"El separador/divisor está preconfigurado y es opcional.\",\n        topSeparator: \"Separador solo en la parte superior\",\n        topAndBottomSeparator: \"Separadores en la parte superior e inferior\",\n        bottomSeparator: \"Separador solo en la parte inferior\",\n      },\n      icons: {\n        name: \"Íconos\",\n        description: \"Puedes personalizar los íconos a la izquierda o a la derecha.\",\n        leftIcon: \"Ícono izquierdo\",\n        rightIcon: \"Ícono derecho\",\n        leftRightIcons: \"Íconos izquierdo y derecho\",\n      },\n      customLeftRight: {\n        name: \"Componentes personalizados en la izquierda o derecha\",\n        description:\n          \"Puede pasar un componente personalizado en la izquierda o derecha, si así lo necesitas.\",\n        customLeft: \"Componente personalizado a la izquierda\",\n        customRight: \"Componente personalizado a la derecha\",\n      },\n      passingContent: {\n        name: \"Entregando contenido\",\n        description: \"Hay varias formas de entregar contenido.\",\n        text: \"A través del atributo `text` - reprehenderit sint\",\n        children: \"Contenido anidado (children) - mostrud mollit\",\n        nestedChildren1: \"Contenido anidado 1 - proident veniam.\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n      },\n      listIntegration: {\n        name: \"Integración con FlatList\",\n        description:\n          \"El componente puede ser fácilmente integrado con tu interfaz de lista preferida.\",\n      },\n      styling: {\n        name: \"Estilo\",\n        description: \"El componente puede ser configurando fácilmente.\",\n        styledText: \"Texto estilizado\",\n        styledContainer: \"Contenedor estilizado (separadores)\",\n        tintedIcons: \"Íconos coloreados\",\n      },\n    },\n  },\n  demoCard: {\n    description:\n      \"Las tarjetas son útiles para mostrar información relacionada de forma englobada. Si un ListItem muestra el contenido horizontalmente, una tarjeta puede ser también utilizada para mostrar el contenido de manera vertical.\",\n    useCase: {\n      presets: {\n        name: \"Preajustes\",\n        description: \"Hay algunos ajustes preconfigurados.\",\n        default: {\n          heading: \"Preajuste por defecto (default)\",\n          content: \"Incididunt magna ut aliquip consectetur mollit dolor.\",\n          footer: \"Consectetur nulla non aliquip velit.\",\n        },\n        reversed: {\n          heading: \"Preajuste inverso\",\n          content: \"Reprehenderit occaecat proident amet id laboris.\",\n          footer: \"Consectetur tempor ea non labore anim.\",\n        },\n      },\n      verticalAlignment: {\n        name: \"Alineamiento vertical\",\n        description:\n          \"Dependiendo del requerimiento, la tarjeta está preconfigurada con diferentes estrategias de alineación.\",\n        top: {\n          heading: \"Arriba (por defecto)\",\n          content: \"Todo el contenido está automáticamente alineado en la parte superior.\",\n          footer: \"Incluso en el pie de página\",\n        },\n        center: {\n          heading: \"Centro\",\n          content: \"El contenido está centrado en relación con la altura de la tarjeta.\",\n          footer: \"¡Yo también!\",\n        },\n        spaceBetween: {\n          heading: \"Espacio entre\",\n          content: \"Todo el contenido está espaciado uniformemente.\",\n          footer: \"Estoy donde quiero estar.\",\n        },\n        reversed: {\n          heading: \"Forzar el pie de página hacia abajo\",\n          content: \"Esto empuja el pie de página hacia donde pertenece.\",\n          footer: \"Estoy tan solo aquí abajo.\",\n        },\n      },\n      passingContent: {\n        name: \"Entregando contenido\",\n        description: \"Hay varias formas de entregar contenido.\",\n        heading: \"A través del atributo `heading`\",\n        content: \"A través del atributo `content`\",\n        footer: \"Estoy tan solo aquí abajo.\",\n      },\n      customComponent: {\n        name: \"Componentes personalizados\",\n        description:\n          \"Cualquier componente preconfigurado puede ser reemplazado por uno específico. Puedes agregar otros si así lo requieres.\",\n        rightComponent: \"Componente derecho\",\n        leftComponent: \"Componente izquierdo\",\n      },\n      style: {\n        name: \"Estilo\",\n        description: \"El componente puede ser configurado fácilmente.\",\n        heading: \"Estilizar el encabezado\",\n        content: \"Estilizar el contenido\",\n        footer: \"Estilizar el pie de página\",\n      },\n    },\n  },\n  demoAutoImage: {\n    description:\n      \"Un componente que se ajusta automáticamente el tamaño de una imagen remota o utilizando el atributo data-uri.\",\n    useCase: {\n      remoteUri: { name: \"URI remota\" },\n      base64Uri: { name: \"URI Base64\" },\n      scaledToFitDimensions: {\n        name: \"Escalado que se ajusta a las dimensiones\",\n        description:\n          \"Al proporcionar los atributos `maxWidth` y/o `maxHeight`, la imagen se redimensionará automáticamente manteniendo el ratio. ¿En qué se diferencia de `resizeMode: 'contain'`? Para empezar, puedes especificar el tamaño de un solo lado (no ambos). Segundo, la imagen se ajustará a las dimensiones deseadas en lugar de simplemente estar contenida en su contenedor.\",\n        heightAuto: \"ancho: 60 / altura: auto\",\n        widthAuto: \"ancho: auto / altura: 32\",\n        bothManual: \"ancho: 60 / altura: 60\",\n      },\n    },\n  },\n  demoText: {\n    description:\n      \"Para todo tipo de requerimiento relacionado a mostrar texto. Este componente es un 'wrapper' (HOC) del componente Text de React Native.\",\n    useCase: {\n      presets: {\n        name: \"Preajustes\",\n        description: \"Hay algunos ajustes preconfigurados.\",\n        default:\n          \"ajuste por defecto - Cillum eu laboris in labore. Excepteur mollit tempor reprehenderit fugiat elit et eu consequat laborum.\",\n        bold: \"preajuste negrita - Tempor et ullamco cupidatat in officia. Nulla ea duis elit id sunt ipsum cillum duis deserunt nostrud ut nostrud id.\",\n        subheading: \"preajuste subtítulo - In Cupidatat Cillum.\",\n        heading: \"preajuste título - Voluptate Adipis.\",\n      },\n      sizes: {\n        name: \"Tamaños\",\n        description: \"Hay un atributo de tamaño.\",\n        xs: \"xs - Ea ipsum est ea ex sunt.\",\n        sm: \"sm - Lorem sunt adipisicin.\",\n        md: \"md - Consequat id do lorem.\",\n        lg: \"lg - Nostrud ipsum ea.\",\n        xl: \"xl - Eiusmod ex excepteur.\",\n        xxl: \"xxl - Cillum eu laboris.\",\n      },\n      weights: {\n        name: \"Grueso\",\n        description: \"Hay un atributo de grueso.\",\n        light:\n          \"ligero - Nulla magna incididunt excepteur est occaecat duis culpa dolore cupidatat enim et.\",\n        normal:\n          \"normal - Magna incididunt dolor ut veniam veniam laboris aliqua velit ea incididunt.\",\n        medium: \"medio - Non duis laborum quis laboris occaecat culpa cillum.\",\n        semibold: \"seminegrita - Exercitation magna nostrud pariatur laborum occaecat aliqua.\",\n        bold: \"negrita - Eiusmod ullamco magna exercitation est excepteur.\",\n      },\n      passingContent: {\n        name: \"Entregando contenido\",\n        description: \"Hay varias formas de entregar contenido.\",\n        viaText:\n          \"a través del atributo `text` - Billum in aute fugiat proident nisi pariatur est. Cupidatat anim cillum eiusmod ad. Officia eu magna aliquip labore dolore consequat.\",\n        viaTx: \"a través del atributo `tx` -\",\n        children:\n          \"Contenido anidado (children) - Aliqua velit irure reprehenderit eu qui amet veniam consectetur.\",\n        nestedChildren: \"Contenidos anidados -\",\n        nestedChildren2: \"Occaecat aliqua irure proident veniam.\",\n        nestedChildren3: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren4: \"Occaecat aliqua irure proident veniam.\",\n      },\n      styling: {\n        name: \"Estilo\",\n        description: \"El componente puede ser configurando fácilmente.\",\n        text: \"Consequat ullamco veniam velit mollit proident excepteur aliquip id culpa ipsum velit sint nostrud.\",\n        text2:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n        text3:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n      },\n    },\n  },\n  demoHeader: {\n    description:\n      \"Componente desplegado en varias pantallas. Va a contener botones de navegación y el título de la pantalla.\",\n    useCase: {\n      actionIcons: {\n        name: \"Íconos de acción\",\n        description: \"Puedes pasar fácilmente íconos a los componentes de la izquierda o derecha.\",\n        leftIconTitle: \"Ícono izquierdo\",\n        rightIconTitle: \"Ícono derecho\",\n        bothIconsTitle: \"Ambos íconos\",\n      },\n      actionText: {\n        name: \"Texto de acción\",\n        description: \"Puedes pasar fácilmente texto a los componentes de la izquierda o derecha.\",\n        leftTxTitle: \"A través de `leftTx`\",\n        rightTextTitle: \"A través de `rightText`\",\n      },\n      customActionComponents: {\n        name: \"Componentes personalizados de acción\",\n        description:\n          \"Si las opciones de ícono o texto no son suficientes, puedes pasar tu propio componente personalizado de acción.\",\n        customLeftActionTitle: \"Acción izquierda personalizada\",\n      },\n      titleModes: {\n        name: \"Alineamiento para el título\",\n        description:\n          \"El título puede ser forzado a permanecer centrado (por defecto), pero podría cortarse si es demasiado largo. También puedes hacer que se ajuste a los botones a la izquierda o derecha.\",\n        centeredTitle: \"Título centrado\",\n        flexTitle: \"Título flexible\",\n      },\n      styling: {\n        name: \"Estilo\",\n        description: \"El componente puede ser configurado fácilmente.\",\n        styledTitle: \"Título estilizado\",\n        styledWrapperTitle: \"Contenedor estilizado\",\n        tintedIconsTitle: \"Íconos coloreados\",\n      },\n    },\n  },\n  demoEmptyState: {\n    description:\n      \"Un componente para cuando no hay información que mostrar. Puede usarse también para guiar al usuario sobre qué hacer a continuación.\",\n    useCase: {\n      presets: {\n        name: \"Preajustes\",\n        description:\n          \"Puedes crear distintos conjuntos de texto/imagen. Por ejemplo, con un ajuste predefinido `generic`. Si quieres tener un EmptyState completamente personalizado, ten en cuenta que no hay un valor por defecto.\",\n      },\n      passingContent: {\n        name: \"Entregando contenido\",\n        description: \"Hay varias formas de entregar contenido.\",\n        customizeImageHeading: \"Personalizar la imagen\",\n        customizeImageContent: \"Puedes pasar cualquier una imagen de distintas fuentes.\",\n        viaHeadingProp: \"A través del atributo `heading`\",\n        viaContentProp: \"A través del atributo `content`.\",\n        viaButtonProp: \"A través del atributo `button`\",\n      },\n      styling: {\n        name: \"Estilo\",\n        description: \"El componente puede ser configurado fácilmente.\",\n      },\n    },\n  },\n}\n\nexport default demoEs\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/i18n/demo-fr.ts",
    "content": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoFr: DemoTranslations = {\n  demoIcon: {\n    description:\n      \"Un composant pour faire le rendu d’une icône enregistrée. Il est enveloppé dans un <TouchableOpacity /> si `onPress` est fourni, sinon dans une <View />.\",\n    useCase: {\n      icons: {\n        name: \"Icônes\",\n        description: \"Liste des icônes enregistrées dans le composant.\",\n      },\n      size: {\n        name: \"Taille\",\n        description: \"Il y a une prop de taille.\",\n      },\n      color: {\n        name: \"Couleur\",\n        description: \"Il y a une prop de couleur.\",\n      },\n      styling: {\n        name: \"Style\",\n        description: \"Le composant peut être facilement stylisé.\",\n      },\n    },\n  },\n  demoTextField: {\n    description: \"Le composant <TextField /> permet la saisie et l'édition de texte.\",\n    useCase: {\n      statuses: {\n        name: \"Statuts\",\n        description:\n          \"Il y a une prop de statut - similaire à `preset` dans d'autres composants, mais affecte également la fonctionnalité du composant.\",\n        noStatus: {\n          label: \"Pas de statut\",\n          helper: \"C'est le statut par défaut\",\n          placeholder: \"Le texte passe par là\",\n        },\n        error: {\n          label: \"Statut d'erreur\",\n          helper: \"Statut à utiliser en cas d’erreur\",\n          placeholder: \"Le texte passe par ici\",\n        },\n        disabled: {\n          label: \"Statut désactivé\",\n          helper: \"Désactive l’édition et atténue le texte\",\n          placeholder: \"Le texte repasse par là\",\n        },\n      },\n      passingContent: {\n        name: \"Transfert de contenu\",\n        description: \"Il y a plusieurs façons de transmettre du contenu.\",\n        viaLabel: {\n          labelTx: \"Via la prop `label`\",\n          helper: \"Via la prop `helper`\",\n          placeholder: \"Via la prop `placeholder`\",\n        },\n        rightAccessory: {\n          label: \"Accessoire droit\",\n          helper: \"Cette prop demande une fonction qui retourne un élément React.\",\n        },\n        leftAccessory: {\n          label: \"Accessoire gauche\",\n          helper: \"Cette prop demande une fonction qui retourne un élément React.\",\n        },\n        supportsMultiline: {\n          label: \"Supporte le multiligne\",\n          helper: \"Permet une saisie plus longue pour le texte multiligne.\",\n        },\n      },\n      styling: {\n        name: \"Style\",\n        description: \"Le composant peut être facilement stylisé.\",\n        styleInput: {\n          label: \"Style de saisie\",\n          helper: \"Via la prop `style`\",\n        },\n        styleInputWrapper: {\n          label: \"Style du wrapper de saisie\",\n          helper: \"Via la prop `inputWrapperStyle`\",\n        },\n        styleContainer: {\n          label: \"Style du conteneur\",\n          helper: \"Via la prop `containerStyle`\",\n        },\n        styleLabel: {\n          label: \"Style du label et de l’aide\",\n          helper: \"Via les props de style `LabelTextProps` et `HelperTextProps`\",\n        },\n        styleAccessories: {\n          label: \"Style des accessoires\",\n          helper: \"Via les props de style `RightAccessory` et `LeftAccessory`\",\n        },\n      },\n    },\n  },\n  demoToggle: {\n    description:\n      \"Fait le rendu d’un booléen. Ce composant contrôlé nécessite un callback `onValueChange` qui met à jour la prop `value` pour que le composant reflète les actions de l'utilisateur. Si la prop `value` n'est pas mise à jour, le composant continuera à rendre la prop `value` fournie au lieu du résultat attendu des actions de l'utilisateur.\",\n    useCase: {\n      variants: {\n        name: \"Variantes\",\n        description:\n          \"Le composant supporte différentes variantes. Si une personnalisation poussée d'une variante spécifique est nécessaire, elle peut être facilement refactorisée. La valeur par défaut est `checkbox`.\",\n        checkbox: {\n          label: \"Variante `checkbox`\",\n          helper: \"Peut être utilisée pour une seule valeure on/off.\",\n        },\n        radio: {\n          label: \"Variante `radio`\",\n          helper: \"Utilisez ceci quand vous avez plusieurs options.\",\n        },\n        switch: {\n          label: \"Variante `switch`\",\n          helper:\n            \"Une entrée on/off plus proéminente. Possède un meilleur support d’accessibilité.\",\n        },\n      },\n      statuses: {\n        name: \"Statuts\",\n        description:\n          \"Il y a une prop de statut - similaire à `preset` dans d'autres composants, mais affecte également la fonctionnalité du composant.\",\n        noStatus: \"Pas de statut - c'est le défaut\",\n        errorStatus: \"Statut d’erreur - à utiliser quand il y a une erreur\",\n        disabledStatus: \"Statut désactivé - désactive l’édition et atténue le style\",\n      },\n      passingContent: {\n        name: \"Transfert de contenu\",\n        description: \"Il y a plusieurs façons de transmettre du contenu.\",\n        useCase: {\n          checkBox: {\n            label: \"Via la prop `labelTx`\",\n            helper: \"Via la prop `helperTx`.\",\n          },\n          checkBoxMultiLine: {\n            helper: \"Supporte le multiligne - Nulla proident consectetur labore sunt ea labore. \",\n          },\n          radioChangeSides: {\n            helper:\n              \"Vous pouvez changer de côté - Laborum labore adipisicing in eu ipsum deserunt.\",\n          },\n          customCheckBox: {\n            label: \"Passez une icône de case à cocher personnalisée.\",\n          },\n          switch: {\n            label: \"Les interrupteurs peuvent être lus comme du texte\",\n            helper:\n              \"Par défaut, cette option n’utilise pas `Text` car selon la police, les caractères on/off pourraient paraître étranges. Personnalisez selon vos besoins.\",\n          },\n          switchAid: {\n            label: \"Ou aidé d’une icône\",\n          },\n        },\n      },\n      styling: {\n        name: \"Style\",\n        description: \"Le composant peut être facilement stylisé.\",\n        outerWrapper: \"1 - styliser le wrapper extérieur de l’entrée\",\n        innerWrapper: \"2 - styliser le wrapper intérieur de l’entrée\",\n        inputDetail: \"3 - styliser le détail de l’entrée\",\n        labelTx: \"Vous pouvez aussi styliser le labelTx\",\n        styleContainer: \"Ou, styliser le conteneur entier\",\n      },\n    },\n  },\n  demoButton: {\n    description:\n      \"Un composant qui permet aux utilisateurs d’effectuer des actions et de faire des choix. Enveloppe le composant Text avec un composant Pressable.\",\n    useCase: {\n      presets: {\n        name: \"Préréglages\",\n        description: \"Il y a quelques préréglages préconfigurés.\",\n      },\n      passingContent: {\n        name: \"Transfert de contenu\",\n        description: \"Il y a plusieurs façons de transmettre du contenu.\",\n        viaTextProps: \"Via la prop `text` - Billum In\",\n        children: \"Enfants - Irure Reprehenderit\",\n        rightAccessory: \"Accessoire droit - Duis Quis\",\n        leftAccessory: \"Accessoire gauche - Duis Proident\",\n        nestedChildren: \"Enfants imbriqués - proident veniam.\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren3: \"Occaecat aliqua irure proident veniam.\",\n        multiLine:\n          \"Multiligne - consequat veniam veniam reprehenderit. Fugiat id nisi quis duis sunt proident mollit dolor mollit adipisicing proident deserunt.\",\n      },\n      styling: {\n        name: \"Style\",\n        description: \"Le composant peut être facilement stylisé.\",\n        styleContainer: \"Style du conteneur - Exercitation\",\n        styleText: \"Style du texte - Ea Anim\",\n        styleAccessories: \"Style des accessoires - enim ea id fugiat anim ad.\",\n        pressedState: \"Style de l’état pressé - fugiat anim\",\n      },\n      disabling: {\n        name: \"Désactivation\",\n        description:\n          \"Le composant peut être désactivé et stylisé en conséquence. Le comportement de pression sera désactivé.\",\n        standard: \"Désactivé - standard\",\n        filled: \"Désactivé - rempli\",\n        reversed: \"Désactivé - inversé\",\n        accessory: \"Style d’accessoire désactivé\",\n        textStyle: \"Style de texte désactivé\",\n      },\n    },\n  },\n  demoListItem: {\n    description:\n      \"Un composant de ligne stylisé qui peut être utilisé dans FlatList, SectionList, ou seul.\",\n    useCase: {\n      height: {\n        name: \"Hauteur\",\n        description: \"La ligne peut avoir différentes hauteurs.\",\n        defaultHeight: \"Hauteur par défaut (56px)\",\n        customHeight: \"Hauteur personnalisée via la prop `height`\",\n        textHeight:\n          \"Hauteur déterminée par le contenu du texte - Reprehenderit incididunt deserunt do do ea labore.\",\n        longText:\n          \"Limiter le texte long à une ligne - Reprehenderit incididunt deserunt do do ea labore.\",\n      },\n      separators: {\n        name: \"Séparateurs\",\n        description: \"Le séparateur / diviseur est préconfiguré et optionnel.\",\n        topSeparator: \"Séparateur uniquement en haut\",\n        topAndBottomSeparator: \"Séparateurs en haut et en bas\",\n        bottomSeparator: \"Séparateur uniquement en bas\",\n      },\n      icons: {\n        name: \"Icônes\",\n        description: \"Vous pouvez personnaliser les icônes à gauche ou à droite.\",\n        leftIcon: \"Icône gauche\",\n        rightIcon: \"Icône droite\",\n        leftRightIcons: \"Icônes gauche et droite\",\n      },\n      customLeftRight: {\n        name: \"Composants personnalisés gauche/droite\",\n        description:\n          \"Si vous avez besoin d’un composant personnalisé à gauche/droite, vous pouvez le passer.\",\n        customLeft: \"Composant personnalisé à gauche\",\n        customRight: \"Composant personnalisé à droite\",\n      },\n      passingContent: {\n        name: \"Transfert de contenu\",\n        description: \"Il y a plusieurs façons de transmettre du contenu.\",\n        text: \"Via la prop `text` - reprehenderit sint\",\n        children: \"Enfants - mostrud mollit\",\n        nestedChildren1: \"Enfants imbriqués - proident veniam.\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n      },\n      listIntegration: {\n        name: \"Intégration avec FlatList\",\n        description:\n          \"Le composant peut être facilement intégré avec votre interface de liste préférée.\",\n      },\n      styling: {\n        name: \"Style\",\n        description: \"Le composant peut être facilement stylisé.\",\n        styledText: \"Texte stylisé\",\n        styledContainer: \"Conteneur stylisé (séparateurs)\",\n        tintedIcons: \"Icônes teintées\",\n      },\n    },\n  },\n  demoCard: {\n    description:\n      \"Les cartes sont utiles pour afficher des informations connexes de manière contenue. Si un ListItem affiche le contenu horizontalement, une Card peut être utilisée pour afficher le contenu verticalement.\",\n    useCase: {\n      presets: {\n        name: \"Préréglages\",\n        description: \"Il y a quelques préréglages préconfigurés.\",\n        default: {\n          heading: \"Préréglage par défaut (default)\",\n          content: \"Incididunt magna ut aliquip consectetur mollit dolor.\",\n          footer: \"Consectetur nulla non aliquip velit.\",\n        },\n        reversed: {\n          heading: \"Préréglage inversé\",\n          content: \"Reprehenderit occaecat proident amet id laboris.\",\n          footer: \"Consectetur tempor ea non labore anim .\",\n        },\n      },\n      verticalAlignment: {\n        name: \"Alignement vertical\",\n        description:\n          \"Selon les besoins, la carte est préconfigurée avec différentes stratégies d’alignement.\",\n        top: {\n          heading: \"Haut (par défaut)\",\n          content: \"Tout le contenu est automatiquement aligné en haut.\",\n          footer: \"Même le pied de page\",\n        },\n        center: {\n          heading: \"Centre\",\n          content: \"Le contenu est centré par rapport à la hauteur de la carte.\",\n          footer: \"Moi aussi !\",\n        },\n        spaceBetween: {\n          heading: \"Espace entre\",\n          content: \"Tout le contenu est espacé uniformément.\",\n          footer: \"Je suis là où je veux être.\",\n        },\n        reversed: {\n          heading: \"Forcer le pied de page en bas\",\n          content: \"Cela pousse le pied de page là où il appartient.\",\n          footer: \"Je suis si seul ici en bas.\",\n        },\n      },\n      passingContent: {\n        name: \"Transfert de contenu\",\n        description: \"Il y a plusieurs façons de transmettre du contenu.\",\n        heading: \"Via la prop `heading`\",\n        content: \"Via la prop `content`\",\n        footer: \"Je suis si seul ici en bas.\",\n      },\n      customComponent: {\n        name: \"Composants personnalisés\",\n        description:\n          \"N’importe quels composants préconfigurés peuvent être remplacé par le vôtre. Vous pouvez également en ajouter d’autres.\",\n        rightComponent: \"Composant droit\",\n        leftComponent: \"Composant gauche\",\n      },\n      style: {\n        name: \"Style\",\n        description: \"Le composant peut être facilement stylisé.\",\n        heading: \"Styliser l’en-tête\",\n        content: \"Styliser le contenu\",\n        footer: \"Styliser le pied de page\",\n      },\n    },\n  },\n  demoAutoImage: {\n    description:\n      \"Un composant Image qui dimensionne automatiquement une image distante ou data-uri.\",\n    useCase: {\n      remoteUri: { name: \"URI distante\" },\n      base64Uri: { name: \"URI Base64\" },\n      scaledToFitDimensions: {\n        name: \"Mis à l’échelle pour s’adapter aux dimensions\",\n        description:\n          \"En fournissant les props `maxWidth` et/ou `maxHeight`, l’image se redimensionnera automatiquement à l’échelle tout en conservant son rapport d’aspect. En quoi est-ce différent de `resizeMode: 'contain'` ? Premièrement, vous pouvez spécifier la taille d'un seul côté (pas les deux). Deuxièmement, l'image s'adaptera aux dimensions souhaitées au lieu d'être simplement contenue dans son conteneur d'image.\",\n        heightAuto: \"largeur: 60 / hauteur: auto\",\n        widthAuto: \"largeur: auto / hauteur: 32\",\n        bothManual: \"largeur: 60 / hauteur: 60\",\n      },\n    },\n  },\n  demoText: {\n    description:\n      \"Pour vos besoins d'affichage de texte. Ce composant est un HOC sur celui intégré à React Native.\",\n    useCase: {\n      presets: {\n        name: \"Préréglages\",\n        description: \"Il y a quelques réglages préconfigurés.\",\n        default:\n          \"préréglage par défaut - Cillum eu laboris in labore. Excepteur mollit tempor reprehenderit fugiat elit et eu consequat laborum.\",\n        bold: \"préréglage gras - Tempor et ullamco cupidatat in officia. Nulla ea duis elit id sunt ipsum cillum duis deserunt nostrud ut nostrud id.\",\n        subheading: \"préréglage sous-titre - In Cupidatat Cillum.\",\n        heading: \"préréglage titre - Voluptate Adipis.\",\n      },\n      sizes: {\n        name: \"Tailles\",\n        description: \"Il y a une prop de taille.\",\n        xs: \"xs - Ea ipsum est ea ex sunt.\",\n        sm: \"sm - Lorem sunt adipisicin.\",\n        md: \"md - Consequat id do lorem.\",\n        lg: \"lg - Nostrud ipsum ea.\",\n        xl: \"xl - Eiusmod ex excepteur.\",\n        xxl: \"xxl - Cillum eu laboris.\",\n      },\n      weights: {\n        name: \"Graisse\",\n        description: \"Il y a une prop de graisse.\",\n        light:\n          \"léger - Nulla magna incididunt excepteur est occaecat duis culpa dolore cupidatat enim et.\",\n        normal:\n          \"normal - Magna incididunt dolor ut veniam veniam laboris aliqua velit ea incididunt.\",\n        medium: \"moyen - Non duis laborum quis laboris occaecat culpa cillum.\",\n        semibold: \"demi-gras - Exercitation magna nostrud pariatur laborum occaecat aliqua.\",\n        bold: \"gras - Eiusmod ullamco magna exercitation est excepteur.\",\n      },\n      passingContent: {\n        name: \"Transfert de contenu\",\n        description: \"Il y a plusieurs façons de transférer du contenu.\",\n        viaText:\n          \"via la prop `text` - Billum in aute fugiat proident nisi pariatur est. Cupidatat anim cillum eiusmod ad. Officia eu magna aliquip labore dolore consequat.\",\n        viaTx: \"via la prop `tx` -\",\n        children: \"enfants - Aliqua velit irure reprehenderit eu qui amet veniam consectetur.\",\n        nestedChildren: \"Enfants imbriqués -\",\n        nestedChildren2: \"Occaecat aliqua irure proident veniam.\",\n        nestedChildren3: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren4: \"Occaecat aliqua irure proident veniam.\",\n      },\n      styling: {\n        name: \"Style\",\n        description: \"Le composant peut être facilement stylisé.\",\n        text: \"Consequat ullamco veniam velit mollit proident excepteur aliquip id culpa ipsum velit sint nostrud.\",\n        text2:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n        text3:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n      },\n    },\n  },\n  demoHeader: {\n    description:\n      \"Composant qui apparaît sur de nombreux écrans. Contiendra les boutons de navigation et le titre de l’écran.\",\n    useCase: {\n      actionIcons: {\n        name: \"Icônes d’action\",\n        description:\n          \"Vous pouvez facilement passer des icônes aux composants d’action gauche ou droit.\",\n        leftIconTitle: \"Icône gauche\",\n        rightIconTitle: \"Icône droite\",\n        bothIconsTitle: \"Les deux icônes\",\n      },\n      actionText: {\n        name: \"Texte d’action\",\n        description:\n          \"Vous pouvez facilement passer du texte aux composants d’action gauche ou droit.\",\n        leftTxTitle: \"Via `leftTx`\",\n        rightTextTitle: \"Via `rightText`\",\n      },\n      customActionComponents: {\n        name: \"Composants d’action personnalisés\",\n        description:\n          \"Si les options d’icône ou de texte ne suffisent pas, vous pouvez passer votre propre composant d’action personnalisé.\",\n        customLeftActionTitle: \"Action gauche personnalisée\",\n      },\n      titleModes: {\n        name: \"Modes de titre\",\n        description:\n          \"Le titre peut être forcé à rester au centre (par défaut) mais peut être coupé s’il est trop long. Vous pouvez éventuellement le faire s’ajuster aux boutons d’action.\",\n        centeredTitle: \"Titre centré\",\n        flexTitle: \"Titre flexible\",\n      },\n      styling: {\n        name: \"Style\",\n        description: \"Le composant peut être facilement stylisé.\",\n        styledTitle: \"Titre stylisé\",\n        styledWrapperTitle: \"Wrapper stylisé\",\n        tintedIconsTitle: \"Icônes teintées\",\n      },\n    },\n  },\n  demoEmptyState: {\n    description:\n      \"Un composant à utiliser lorsqu’il n’y a pas de données à afficher. Il peut être utilisé pour diriger l’utilisateur sur ce qu’il faut faire ensuite.\",\n    useCase: {\n      presets: {\n        name: \"Préréglages\",\n        description:\n          \"Vous pouvez créer différents ensembles de texte/image. Un est prédéfini appelé `generic`. Notez qu’il n’y a pas de valeur par défaut au cas où vous voudriez avoir un EmptyState complètement personnalisé.\",\n      },\n      passingContent: {\n        name: \"Transfert de contenu\",\n        description: \"Il y a plusieurs façons de transférer du contenu.\",\n        customizeImageHeading: \"Personnaliser l’image\",\n        customizeImageContent: \"Vous pouvez passer n’importe quelle source d'image.\",\n        viaHeadingProp: \"Via la prop `heading`\",\n        viaContentProp: \"Via la prop `content`.\",\n        viaButtonProp: \"Via la prop `button`\",\n      },\n      styling: {\n        name: \"Style\",\n        description: \"Le composant peut être facilement stylisé.\",\n      },\n    },\n  },\n}\n\nexport default demoFr\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/i18n/demo-hi.ts",
    "content": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoHi: DemoTranslations = {\n  demoIcon: {\n    description:\n      \"एक पंजीकृत आइकन को रेंडर करने के लिए एक कंपोनेंट। यदि `onPress` प्रदान किया जाता है तो यह <TouchableOpacity /> में लपेटा जाता है, अन्यथा <View /> में।\",\n    useCase: {\n      icons: {\n        name: \"आइकन\",\n        description: \"कंपोनेंट के अंदर पंजीकृत आइकनों की सूची।\",\n      },\n      size: {\n        name: \"आकार\",\n        description: \"एक आकार प्रॉप है।\",\n      },\n      color: {\n        name: \"रंग\",\n        description: \"एक रंग प्रॉप है।\",\n      },\n      styling: {\n        name: \"स्टाइलिंग\",\n        description: \"कंपोनेंट को आसानी से स्टाइल किया जा सकता है।\",\n      },\n    },\n  },\n  demoTextField: {\n    description: \"टेक्स्टफील्ड कंपोनेंट टेक्स्ट दर्ज करने और संपादित करने की अनुमति देता है।\",\n    useCase: {\n      statuses: {\n        name: \"स्थितियाँ\",\n        description:\n          \"एक स्थिति प्रॉप है - अन्य कंपोनेंट्स में `preset` के समान, लेकिन कंपोनेंट की कार्यक्षमता को भी प्रभावित करता है।\",\n        noStatus: {\n          label: \"कोई स्थिति नहीं\",\n          helper: \"यह डिफ़ॉल्ट स्थिति है\",\n          placeholder: \"टेक्स्ट यहाँ जाता है\",\n        },\n        error: {\n          label: \"त्रुटि स्थिति\",\n          helper: \"त्रुटि होने पर उपयोग करने के लिए स्थिति\",\n          placeholder: \"टेक्स्ट यहाँ जाता है\",\n        },\n        disabled: {\n          label: \"अक्षम स्थिति\",\n          helper: \"संपादन को अक्षम करता है और टेक्स्ट को मंद करता है\",\n          placeholder: \"टेक्स्ट यहाँ जाता है\",\n        },\n      },\n      passingContent: {\n        name: \"सामग्री पास करना\",\n        description: \"सामग्री पास करने के कई तरीके हैं।\",\n        viaLabel: {\n          labelTx: \"`label` प्रॉप के माध्यम से\",\n          helper: \"`helper` प्रॉप के माध्यम से\",\n          placeholder: \"`placeholder` प्रॉप के माध्यम से\",\n        },\n        rightAccessory: {\n          label: \"दायाँ सहायक\",\n          helper: \"यह प्रॉप एक फ़ंक्शन लेता है जो एक React तत्व लौटाता है।\",\n        },\n        leftAccessory: {\n          label: \"बायाँ सहायक\",\n          helper: \"यह प्रॉप एक फ़ंक्शन लेता है जो एक React तत्व लौटाता है।\",\n        },\n        supportsMultiline: {\n          label: \"मल्टीलाइन का समर्थन करता है\",\n          helper: \"मल्टीलाइन टेक्स्ट के लिए एक लंबा इनपुट सक्षम करता है।\",\n        },\n      },\n      styling: {\n        name: \"स्टाइलिंग\",\n        description: \"कंपोनेंट को आसानी से स्टाइल किया जा सकता है।\",\n        styleInput: {\n          label: \"इनपुट स्टाइल\",\n          helper: \"`style` प्रॉप के माध्यम से\",\n        },\n        styleInputWrapper: {\n          label: \"इनपुट रैपर स्टाइल\",\n          helper: \"`inputWrapperStyle` प्रॉप के माध्यम से\",\n        },\n        styleContainer: {\n          label: \"कंटेनर स्टाइल\",\n          helper: \"`containerStyle` प्रॉप के माध्यम से\",\n        },\n        styleLabel: {\n          label: \"लेबल और हेल्पर स्टाइल\",\n          helper: \"`LabelTextProps` और `HelperTextProps` स्टाइल प्रॉप के माध्यम से\",\n        },\n        styleAccessories: {\n          label: \"सहायक स्टाइल\",\n          helper: \"`RightAccessory` और `LeftAccessory` स्टाइल प्रॉप के माध्यम से\",\n        },\n      },\n    },\n  },\n  demoToggle: {\n    description:\n      \"एक बूलियन इनपुट रेंडर करता है। यह एक नियंत्रित कंपोनेंट है जिसे उपयोगकर्ता क्रियाओं को दर्शाने के लिए value प्रॉप को अपडेट करने वाले onValueChange कॉलबैक की आवश्यकता होती है। यदि value प्रॉप अपडेट नहीं की जाती है, तो कंपोनेंट उपयोगकर्ता क्रियाओं के अपेक्षित परिणाम के बजाय आपूर्ति की गई value प्रॉप को रेंडर करना जारी रखेगा।\",\n    useCase: {\n      variants: {\n        name: \"विविधताएँ\",\n        description:\n          \"कंपोनेंट कुछ अलग-अलग विविधताओं का समर्थन करता है। यदि किसी विशिष्ट विविधता के भारी अनुकूलन की आवश्यकता है, तो इसे आसानी से पुनर्गठित किया जा सकता है। डिफ़ॉल्ट `checkbox` है।\",\n        checkbox: {\n          label: \"`checkbox` विविधता\",\n          helper: \"इसका उपयोग एकल चालू/बंद इनपुट के लिए किया जा सकता है।\",\n        },\n        radio: {\n          label: \"`radio` विविधता\",\n          helper: \"जब आपके पास कई विकल्प हों तो इसका उपयोग करें।\",\n        },\n        switch: {\n          label: \"`switch` विविधता\",\n          helper: \"एक अधिक प्रमुख चालू/बंद इनपुट। बेहतर पहुँच समर्थन है।\",\n        },\n      },\n      statuses: {\n        name: \"स्थितियाँ\",\n        description:\n          \"एक स्थिति प्रॉप है - अन्य कंपोनेंट्स में `preset` के समान, लेकिन कंपोनेंट की कार्यक्षमता को भी प्रभावित करता है।\",\n        noStatus: \"कोई स्थिति नहीं - यह डिफ़ॉल्ट है\",\n        errorStatus: \"त्रुटि स्थिति - जब कोई त्रुटि हो तो उपयोग करें\",\n        disabledStatus: \"अक्षम स्थिति - संपादन को अक्षम करता है और इनपुट को मंद करता है\",\n      },\n      passingContent: {\n        name: \"सामग्री पास करना\",\n        description: \"सामग्री पास करने के कई तरीके हैं।\",\n        useCase: {\n          checkBox: {\n            label: \"`labelTx` प्रॉप के माध्यम से\",\n            helper: \"`helperTx` प्रॉप के माध्यम से।\",\n          },\n          checkBoxMultiLine: {\n            helper:\n              \"मल्टीलाइन का समर्थन करता है - Nulla proident consectetur labore sunt ea labore. \",\n          },\n          radioChangeSides: {\n            helper: \"आप पक्ष बदल सकते हैं - Laborum labore adipisicing in eu ipsum deserunt.\",\n          },\n          customCheckBox: {\n            label: \"एक कस्टम चेकबॉक्स आइकन पास करें।\",\n          },\n          switch: {\n            label: \"स्विच को टेक्स्ट के रूप में पढ़ा जा सकता है\",\n            helper:\n              \"डिफ़ॉल्ट रूप से, यह विकल्प `Text` का उपयोग नहीं करता है क्योंकि फ़ॉन्ट के आधार पर, चालू/बंद अक्षर अजीब दिख सकते हैं। आवश्यकतानुसार अनुकूलित करें।\",\n          },\n          switchAid: {\n            label: \"या एक आइकन की मदद से\",\n          },\n        },\n      },\n      styling: {\n        name: \"स्टाइलिंग\",\n        description: \"कंपोनेंट को आसानी से स्टाइल किया जा सकता है।\",\n        outerWrapper: \"1 - इनपुट के बाहरी रैपर को स्टाइल करें\",\n        innerWrapper: \"2 - इनपुट के आंतरिक रैपर को स्टाइल करें\",\n        inputDetail: \"3 - इनपुट विवरण को स्टाइल करें\",\n        labelTx: \"आप labelTx को भी स्टाइल कर सकते हैं\",\n        styleContainer: \"या, पूरे कंटेनर को स्टाइल करें\",\n      },\n    },\n  },\n  demoButton: {\n    description:\n      \"एक कंपोनेंट जो उपयोगकर्ताओं को कार्रवाई करने और विकल्प चुनने की अनुमति देता है। Text कंपोनेंट को Pressable कंपोनेंट के साथ लपेटता है।\",\n    useCase: {\n      presets: {\n        name: \"प्रीसेट\",\n        description: \"कुछ पूर्व-कॉन्फ़िगर किए गए प्रीसेट हैं।\",\n      },\n      passingContent: {\n        name: \"सामग्री पास करना\",\n        description: \"सामग्री पास करने के कई तरीके हैं।\",\n        viaTextProps: \"`text` प्रॉप के माध्यम से - Billum In\",\n        children: \"चिल्ड्रन - Irure Reprehenderit\",\n        rightAccessory: \"दायां एक्सेसरी - Duis Quis\",\n        leftAccessory: \"बायां एक्सेसरी - Duis Proident\",\n        nestedChildren: \"नेस्टेड चिल्ड्रन - proident veniam.\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren3: \"Occaecat aliqua irure proident veniam.\",\n        multiLine:\n          \"मल्टीलाइन - consequat veniam veniam reprehenderit. Fugiat id nisi quis duis sunt proident mollit dolor mollit adipisicing proident deserunt.\",\n      },\n      styling: {\n        name: \"स्टाइलिंग\",\n        description: \"कंपोनेंट को आसानी से स्टाइल किया जा सकता है।\",\n        styleContainer: \"कंटेनर स्टाइल - Exercitation\",\n        styleText: \"टेक्स्ट स्टाइल - Ea Anim\",\n        styleAccessories: \"एक्सेसरीज़ स्टाइल - enim ea id fugiat anim ad.\",\n        pressedState: \"दबाए गए स्थिति का स्टाइल - fugiat anim\",\n      },\n      disabling: {\n        name: \"अक्षम करना\",\n        description:\n          \"कंपोनेंट को अक्षम किया जा सकता है और उसके आधार पर स्टाइल किया जा सकता है। दबाने का व्यवहार अक्षम हो जाएगा।\",\n        standard: \"अक्षम - मानक\",\n        filled: \"अक्षम - भरा हुआ\",\n        reversed: \"अक्षम - उलटा\",\n        accessory: \"अक्षम एक्सेसरी स्टाइल\",\n        textStyle: \"अक्षम टेक्स्ट स्टाइल\",\n      },\n    },\n  },\n  demoListItem: {\n    description:\n      \"एक स्टाइल किया गया पंक्ति कंपोनेंट जो FlatList, SectionList, या अकेले उपयोग किया जा सकता है।\",\n    useCase: {\n      height: {\n        name: \"ऊँचाई\",\n        description: \"पंक्ति की विभिन्न ऊँचाइयाँ हो सकती हैं।\",\n        defaultHeight: \"डिफ़ॉल्ट ऊँचाई (56px)\",\n        customHeight: \"`height` प्रॉप के माध्यम से कस्टम ऊँचाई\",\n        textHeight:\n          \"टेक्स्ट सामग्री द्वारा निर्धारित ऊँचाई - Reprehenderit incididunt deserunt do do ea labore.\",\n        longText:\n          \"लंबे टेक्स्ट को एक पंक्ति तक सीमित करें - Reprehenderit incididunt deserunt do do ea labore.\",\n      },\n      separators: {\n        name: \"विभाजक\",\n        description: \"विभाजक / डिवाइडर पूर्व-कॉन्फ़िगर किया गया है और वैकल्पिक है।\",\n        topSeparator: \"केवल ऊपरी विभाजक\",\n        topAndBottomSeparator: \"ऊपरी और निचले विभाजक\",\n        bottomSeparator: \"केवल निचला विभाजक\",\n      },\n      icons: {\n        name: \"आइकन\",\n        description: \"आप बाएँ या दाएँ आइकन को कस्टमाइज़ कर सकते हैं।\",\n        leftIcon: \"बायाँ आइकन\",\n        rightIcon: \"दायाँ आइकन\",\n        leftRightIcons: \"बाएँ और दाएँ आइकन\",\n      },\n      customLeftRight: {\n        name: \"कस्टम बायाँ/दायाँ कंपोनेंट\",\n        description:\n          \"यदि आपको कस्टम बायाँ/दायाँ कंपोनेंट की आवश्यकता है, तो आप इसे पास कर सकते हैं।\",\n        customLeft: \"कस्टम बायाँ कंपोनेंट\",\n        customRight: \"कस्टम दायाँ कंपोनेंट\",\n      },\n      passingContent: {\n        name: \"सामग्री पास करना\",\n        description: \"सामग्री पास करने के कई तरीके हैं।\",\n        text: \"`text` प्रॉप के माध्यम से - reprehenderit sint\",\n        children: \"चिल्ड्रन - mostrud mollit\",\n        nestedChildren1: \"नेस्टेड चिल्ड्रन - proident veniam.\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n      },\n      listIntegration: {\n        name: \"FlatList के साथ एकीकरण\",\n        description:\n          \"कंपोनेंट को आसानी से आपके पसंदीदा सूची इंटरफेस के साथ एकीकृत किया जा सकता है।\",\n      },\n      styling: {\n        name: \"स्टाइलिंग\",\n        description: \"कंपोनेंट को आसानी से स्टाइल किया जा सकता है।\",\n        styledText: \"स्टाइल किया गया टेक्स्ट\",\n        styledContainer: \"स्टाइल किया गया कंटेनर (विभाजक)\",\n        tintedIcons: \"रंगीन आइकन\",\n      },\n    },\n  },\n  demoCard: {\n    description:\n      \"कार्ड संबंधित जानकारी को एक संयमित तरीके से प्रदर्शित करने के लिए उपयोगी हैं। यदि एक ListItem सामग्री को क्षैतिज रूप से प्रदर्शित करता है, तो एक कार्ड का उपयोग सामग्री को लंबवत रूप से प्रदर्शित करने के लिए किया जा सकता है।\",\n    useCase: {\n      presets: {\n        name: \"प्रीसेट\",\n        description: \"कुछ पूर्व-कॉन्फ़िगर किए गए प्रीसेट हैं।\",\n        default: {\n          heading: \"डिफ़ॉल्ट प्रीसेट (डिफ़ॉल्ट)\",\n          content: \"Incididunt magna ut aliquip consectetur mollit dolor.\",\n          footer: \"Consectetur nulla non aliquip velit.\",\n        },\n        reversed: {\n          heading: \"रिवर्स्ड प्रीसेट\",\n          content: \"Reprehenderit occaecat proident amet id laboris.\",\n          footer: \"Consectetur tempor ea non labore anim .\",\n        },\n      },\n      verticalAlignment: {\n        name: \"ऊर्ध्वाधर संरेखण\",\n        description:\n          \"आवश्यकता के अनुसार, कार्ड विभिन्न संरेखण रणनीतियों के साथ पूर्व-कॉन्फ़िगर किया गया है।\",\n        top: {\n          heading: \"शीर्ष (डिफ़ॉल्ट)\",\n          content: \"सभी सामग्री स्वचालित रूप से शीर्ष पर संरेखित होती है।\",\n          footer: \"यहां तक कि फुटर भी\",\n        },\n        center: {\n          heading: \"मध्य\",\n          content: \"सामग्री कार्ड की ऊंचाई के सापेक्ष केंद्रित होती है।\",\n          footer: \"मैं भी!\",\n        },\n        spaceBetween: {\n          heading: \"स्पेस बिटवीन\",\n          content: \"सभी सामग्री समान रूप से फैली हुई है।\",\n          footer: \"मैं वहां हूं जहां मैं होना चाहता हूं।\",\n        },\n        reversed: {\n          heading: \"फुटर को नीचे रखें\",\n          content: \"यह फुटर को उसके सही स्थान पर धकेलता है।\",\n          footer: \"मैं यहां नीचे बहुत अकेला हूं।\",\n        },\n      },\n      passingContent: {\n        name: \"सामग्री पास करना\",\n        description: \"सामग्री पास करने के कई तरीके हैं।\",\n        heading: \"`heading` प्रॉप के माध्यम से\",\n        content: \"`content` प्रॉप के माध्यम से\",\n        footer: \"मैं यहां नीचे बहुत अकेला हूं।\",\n      },\n      customComponent: {\n        name: \"कस्टम कंपोनेंट्स\",\n        description:\n          \"किसी भी पूर्व-कॉन्फ़िगर किए गए कंपोनेंट को आपके अपने कंपोनेंट से बदला जा सकता है। आप अतिरिक्त कंपोनेंट भी जोड़ सकते हैं।\",\n        rightComponent: \"दायाँ कंपोनेंट\",\n        leftComponent: \"बायाँ कंपोनेंट\",\n      },\n      style: {\n        name: \"स्टाइलिंग\",\n        description: \"कंपोनेंट को आसानी से स्टाइल किया जा सकता है।\",\n        heading: \"शीर्षक को स्टाइल करें\",\n        content: \"सामग्री को स्टाइल करें\",\n        footer: \"फुटर को स्टाइल करें\",\n      },\n    },\n  },\n  demoAutoImage: {\n    description:\n      \"एक छवि कंपोनेंट जो स्वचालित रूप से रिमोट या डेटा-यूआरआई छवि का आकार निर्धारित करता है।\",\n    useCase: {\n      remoteUri: { name: \"रिमोट यूआरआई\" },\n      base64Uri: { name: \"बेस64 यूआरआई\" },\n      scaledToFitDimensions: {\n        name: \"आयामों के अनुरूप स्केल किया गया\",\n        description:\n          \"`maxWidth` और/या `maxHeight` प्रॉप्स प्रदान करने पर, छवि स्वचालित रूप से अपने आस्पेक्ट अनुपात को बनाए रखते हुए स्केल होगी। यह `resizeMode: 'contain'` से कैसे अलग है? पहला, आप केवल एक तरफ का आकार निर्दिष्ट कर सकते हैं (दोनों नहीं)। दूसरा, छवि वांछित आयामों के अनुरूप स्केल होगी, न कि केवल अपने छवि-कंटेनर के भीतर समाहित होगी।\",\n        heightAuto: \"चौड़ाई: 60 / ऊंचाई: स्वचालित\",\n        widthAuto: \"चौड़ाई: स्वचालित / ऊंचाई: 32\",\n        bothManual: \"चौड़ाई: 60 / ऊंचाई: 60\",\n      },\n    },\n  },\n  demoText: {\n    description:\n      \"आपकी टेक्स्ट प्रदर्शन आवश्यकताओं के लिए। यह कंपोनेंट अंतर्निहित React Native कंपोनेंट पर एक HOC है।\",\n    useCase: {\n      presets: {\n        name: \"प्रीसेट\",\n        description: \"कुछ पूर्व-कॉन्फ़िगर किए गए प्रीसेट हैं।\",\n        default:\n          \"डिफ़ॉल्ट प्रीसेट - Cillum eu laboris in labore. Excepteur mollit tempor reprehenderit fugiat elit et eu consequat laborum.\",\n        bold: \"बोल्ड प्रीसेट - Tempor et ullamco cupidatat in officia. Nulla ea duis elit id sunt ipsum cillum duis deserunt nostrud ut nostrud id.\",\n        subheading: \"सबहेडिंग प्रीसेट - In Cupidatat Cillum.\",\n        heading: \"हेडिंग प्रीसेट - Voluptate Adipis.\",\n      },\n      sizes: {\n        name: \"आकार\",\n        description: \"एक आकार प्रॉप है।\",\n        xs: \"xs - Ea ipsum est ea ex sunt.\",\n        sm: \"sm - Lorem sunt adipisicin.\",\n        md: \"md - Consequat id do lorem.\",\n        lg: \"lg - Nostrud ipsum ea.\",\n        xl: \"xl - Eiusmod ex excepteur.\",\n        xxl: \"xxl - Cillum eu laboris.\",\n      },\n      weights: {\n        name: \"वजन\",\n        description: \"एक वजन प्रॉप है।\",\n        light:\n          \"लाइट - Nulla magna incididunt excepteur est occaecat duis culpa dolore cupidatat enim et.\",\n        normal:\n          \"सामान्य - Magna incididunt dolor ut veniam veniam laboris aliqua velit ea incididunt.\",\n        medium: \"मध्यम - Non duis laborum quis laboris occaecat culpa cillum.\",\n        semibold: \"सेमीबोल्ड - Exercitation magna nostrud pariatur laborum occaecat aliqua.\",\n        bold: \"बोल्ड - Eiusmod ullamco magna exercitation est excepteur.\",\n      },\n      passingContent: {\n        name: \"सामग्री पास करना\",\n        description: \"सामग्री पास करने के कई तरीके हैं।\",\n        viaText:\n          \"`text` प्रॉप के माध्यम से - Billum in aute fugiat proident nisi pariatur est. Cupidatat anim cillum eiusmod ad. Officia eu magna aliquip labore dolore consequat.\",\n        viaTx: \"`tx` प्रॉप के माध्यम से -\",\n        children: \"चिल्ड्रन - Aliqua velit irure reprehenderit eu qui amet veniam consectetur.\",\n        nestedChildren: \"नेस्टेड चिल्ड्रन -\",\n        nestedChildren2: \"Occaecat aliqua irure proident veniam.\",\n        nestedChildren3: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren4: \"Occaecat aliqua irure proident veniam.\",\n      },\n      styling: {\n        name: \"स्टाइलिंग\",\n        description: \"कंपोनेंट को आसानी से स्टाइल किया जा सकता है।\",\n        text: \"Consequat ullamco veniam velit mollit proident excepteur aliquip id culpa ipsum velit sint nostrud.\",\n        text2:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n        text3:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n      },\n    },\n  },\n  demoHeader: {\n    description:\n      \"कई स्क्रीन पर दिखाई देने वाला कंपोनेंट। यह नेविगेशन बटन और स्क्रीन शीर्षक धारण करेगा।\",\n    useCase: {\n      actionIcons: {\n        name: \"एक्शन आइकन\",\n        description: \"आप आसानी से बाएँ या दाएँ एक्शन कंपोनेंट्स में आइकन पास कर सकते हैं।\",\n        leftIconTitle: \"बायाँ आइकन\",\n        rightIconTitle: \"दायाँ आइकन\",\n        bothIconsTitle: \"दोनों आइकन\",\n      },\n      actionText: {\n        name: \"एक्शन टेक्स्ट\",\n        description: \"आप आसानी से बाएँ या दाएँ एक्शन कंपोनेंट्स में टेक्स्ट पास कर सकते हैं।\",\n        leftTxTitle: \"`leftTx` के माध्यम से\",\n        rightTextTitle: \"`rightText` के माध्यम से\",\n      },\n      customActionComponents: {\n        name: \"कस्टम एक्शन कंपोनेंट्स\",\n        description:\n          \"यदि आइकन या टेक्स्ट विकल्प पर्याप्त नहीं हैं, तो आप अपना खुद का कस्टम एक्शन कंपोनेंट पास कर सकते हैं।\",\n        customLeftActionTitle: \"कस्टम बायाँ एक्शन\",\n      },\n      titleModes: {\n        name: \"शीर्षक मोड\",\n        description:\n          \"शीर्षक को मध्य में रहने के लिए मजबूर किया जा सकता है (डिफ़ॉल्ट) लेकिन यदि यह बहुत लंबा है तो काटा जा सकता है। वैकल्पिक रूप से आप इसे एक्शन बटनों के अनुसार समायोजित कर सकते हैं।\",\n        centeredTitle: \"केंद्रित शीर्षक\",\n        flexTitle: \"फ्लेक्स शीर्षक\",\n      },\n      styling: {\n        name: \"स्टाइलिंग\",\n        description: \"कंपोनेंट को आसानी से स्टाइल किया जा सकता है।\",\n        styledTitle: \"स्टाइल किया गया शीर्षक\",\n        styledWrapperTitle: \"स्टाइल किया गया रैपर\",\n        tintedIconsTitle: \"रंगीन आइकन\",\n      },\n    },\n  },\n  demoEmptyState: {\n    description:\n      \"जब प्रदर्शित करने के लिए कोई डेटा नहीं है तो उपयोग करने के लिए एक कंपोनेंट। इसका उपयोग उपयोगकर्ता को अगला क्या करना है, यह निर्देशित करने के लिए किया जा सकता है।\",\n    useCase: {\n      presets: {\n        name: \"प्रीसेट\",\n        description:\n          \"आप विभिन्न टेक्स्ट/छवि सेट बना सकते हैं। एक पूर्व-परिभाषित है जिसे `generic` कहा जाता है। ध्यान दें, कोई डिफ़ॉल्ट नहीं है यदि आप पूरी तरह से कस्टम EmptyState चाहते हैं।\",\n      },\n      passingContent: {\n        name: \"सामग्री पास करना\",\n        description: \"सामग्री पास करने के कई तरीके हैं।\",\n        customizeImageHeading: \"छवि को अनुकूलित करें\",\n        customizeImageContent: \"आप कोई भी छवि स्रोत पास कर सकते हैं।\",\n        viaHeadingProp: \"`heading` प्रॉप के माध्यम से\",\n        viaContentProp: \"`content` प्रॉप के माध्यम से।\",\n        viaButtonProp: \"`button` प्रॉप के माध्यम से\",\n      },\n      styling: {\n        name: \"स्टाइलिंग\",\n        description: \"कंपोनेंट को आसानी से स्टाइल किया जा सकता है।\",\n      },\n    },\n  },\n}\n\nexport default demoHi\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/i18n/demo-ja.ts",
    "content": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoJa: DemoTranslations = {\n  demoIcon: {\n    description:\n      \"あらかじめ登録されたアイコンを描画するコンポーネントです。 `onPress` が提供されている場合は <TouchableOpacity /> にラップされますが、それ以外の場合は <View /> にラップされます。\",\n    useCase: {\n      icons: {\n        name: \"アイコン\",\n        description: \"登録されたアイコンのリストです。\",\n      },\n      size: {\n        name: \"サイズ\",\n        description: \"sizeのpropsです。\",\n      },\n      color: {\n        name: \"カラー\",\n        description: \"colorのpropsです。\",\n      },\n      styling: {\n        name: \"スタイリング\",\n        description: \"このコンポーネントはスタイリングの変更ができます。\",\n      },\n    },\n  },\n  demoTextField: {\n    description: \"このコンポーネントはテキストの入力と編集ができます。\",\n    useCase: {\n      statuses: {\n        name: \"ステータス\",\n        description:\n          \"status - これは他コンポーネントの`preset`の似ていますが、これはコンポーネントの機能も変えるpropsです。\",\n        noStatus: {\n          label: \"ステータスなし\",\n          helper: \"デフォルトのステータスです\",\n          placeholder: \"テキストが入力されます\",\n        },\n        error: {\n          label: \"エラーステータス\",\n          helper: \"エラーが発生した場合に使用されるステータスです\",\n          placeholder: \"ここにテキストが入力されます\",\n        },\n        disabled: {\n          label: \"無効(disabled)ステータス\",\n          helper: \"編集不可となるステータスです\",\n          placeholder: \"ここにテキストが入力されます\",\n        },\n      },\n      passingContent: {\n        name: \"コンテントを渡す\",\n        description: \"コンテントを渡す方法はいくつかあります。\",\n        viaLabel: {\n          labelTx: \"`label` から\",\n          helper: \"`helper` から\",\n          placeholder: \"`placeholder` から\",\n        },\n        rightAccessory: {\n          label: \"右側にアクセサリー\",\n          helper: \"このpropsはReact要素を返す関数をうけとります。\",\n        },\n        leftAccessory: {\n          label: \"左側にアクセサリー\",\n          helper: \"このpropsはReact要素を返す関数をうけとります。\",\n        },\n        supportsMultiline: {\n          label: \"複数行サポート\",\n          helper: \"複数行の入力が出来るようになります。\",\n        },\n      },\n      styling: {\n        name: \"スタイリング\",\n        description: \"このコンポーネントはスタイリングの変更ができます。\",\n        styleInput: {\n          label: \"インプットのスタイル\",\n          helper: \"`style`から\",\n        },\n        styleInputWrapper: {\n          label: \"インプットラッパーのスタイル\",\n          helper: \"`inputWrapperStyle`から\",\n        },\n        styleContainer: {\n          label: \"スタイルコンテナのスタイル\",\n          helper: \"`containerStyle`から\",\n        },\n        styleLabel: {\n          label: \"ラベルとヘルパーのスタイル\",\n          helper: \"`LabelTextProps` & `HelperTextProps`から\",\n        },\n        styleAccessories: {\n          label: \"アクセサリーのスタイル\",\n          helper: \"`RightAccessory` & `LeftAccessory`から\",\n        },\n      },\n    },\n  },\n  demoToggle: {\n    description:\n      \"ブーリアンの入力を表示するコンポーネントです。コンポーネントはvalueの値を使用して描画するので、onValueChangeコールバックを使って値を変更し、valueを更新する必要があります。valueの値が変更されていない場合は、描画が更新されません。\",\n    useCase: {\n      variants: {\n        name: \"バリエーション\",\n        description:\n          \"このコンポーネントは数種類のバリエーションをサポートしています。もしカスタマイズが必要な場合、これらのバリエーションをリファクタリングできます。デフォルトは`checkbox`です。\",\n        checkbox: {\n          label: \"`checkbox`バリエーション\",\n          helper: \"シンプルなon/offのインプットに使えます。\",\n        },\n        radio: {\n          label: \"`radio`バリエーション\",\n          helper: \"数個のオプションがある場合に使えます。\",\n        },\n        switch: {\n          label: \"`switch`バリエーション\",\n          helper:\n            \"代表的なon/offのインプットです。他と比べアクセシビリティのサポートが充実しています。\",\n        },\n      },\n      statuses: {\n        name: \"ステータス\",\n        description:\n          \"status - これは他コンポーネントの`preset`の似ていますが、これはコンポーネントの機能も変えるpropsです。\",\n        noStatus: \"ステータスなし - デフォルトです。\",\n        errorStatus: \"エラー - エラーがある際に使えるステータスです。\",\n        disabledStatus: \"無効(disabled) - 編集不可となるステータスです\",\n      },\n      passingContent: {\n        name: \"コンテントを渡す\",\n        description: \"コンテントを渡す方法はいくつかあります。\",\n        useCase: {\n          checkBox: {\n            label: \"`labelTx`から\",\n            helper: \"`helperTx`から\",\n          },\n          checkBoxMultiLine: {\n            helper: \"複数行サポート - Nulla proident consectetur labore sunt ea labore. \",\n          },\n          radioChangeSides: {\n            helper: \"左右に変更 - Laborum labore adipisicing in eu ipsum deserunt.\",\n          },\n          customCheckBox: {\n            label: \"カスタムアイコンも渡せます\",\n          },\n          switch: {\n            label: \"スイッチはテキストとして読むこともできます。\",\n            helper:\n              \"デフォルトでは、このオプションはフォントの影響を受け、見た目が見苦しくなる可能性がある為`Text`コンポーネントを使用していません。必要に応じてカスタマイズしてください。\",\n          },\n          switchAid: {\n            label: \"または補助アイコンもつけられます\",\n          },\n        },\n      },\n      styling: {\n        name: \"スタイリング\",\n        description: \"このコンポーネントはスタイリングの変更ができます。\",\n        outerWrapper: \"1 - インプットの外側のラッパー\",\n        innerWrapper: \"2 - インプットの内側のラッパー\",\n        inputDetail: \"3 - インプットのそのもの\",\n        labelTx: \"ラベルのスタイルも変更できます。\",\n        styleContainer: \"もしくは、コンポーネントのコンテナ全体をスタイルすることもできます。\",\n      },\n    },\n  },\n  demoButton: {\n    description:\n      \"ユーザーにアクションや選択を促すコンポーネントです。`Text`コンポーネントを`Pressable`コンポーネントでラップしています。\",\n    useCase: {\n      presets: {\n        name: \"プリセット\",\n        description: \"数種類のプリセットが用意されています。\",\n      },\n      passingContent: {\n        name: \"コンテントを渡す\",\n        description: \"コンテントを渡す方法はいくつかあります。\",\n        viaTextProps: \"`text`から - Billum In\",\n        children: \"Childrenから - Irure Reprehenderit\",\n        rightAccessory: \"RightAccessoryから - Duis Quis\",\n        leftAccessory: \"LeftAccessoryから - Duis Proident\",\n        nestedChildren: \"ネストされたchildrenから - proident veniam.\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren3: \"Occaecat aliqua irure proident veniam.\",\n        multiLine:\n          \"Multilineから - consequat veniam veniam reprehenderit. Fugiat id nisi quis duis sunt proident mollit dolor mollit adipisicing proident deserunt.\",\n      },\n      styling: {\n        name: \"スタイリング\",\n        description: \"このコンポーネントはスタイリングの変更ができます。\",\n        styleContainer: \"コンテナのスタイル - Exercitation\",\n        styleText: \"テキストのスタイル - Ea Anim\",\n        styleAccessories: \"アクセサリーのスタイル - enim ea id fugiat anim ad.\",\n        pressedState: \"押された状態のスタイル - fugiat anim\",\n      },\n      disabling: {\n        name: \"無効化\",\n        description:\n          \"このコンポーネントは無効化できます。スタイルも同時に変更され、押した際の挙動も無効化されます。\",\n        standard: \"無効化 - standard\",\n        filled: \"無効化 - filled\",\n        reversed: \"無効化 - reversed\",\n        accessory: \"無効化されたアクセサリーのスタイル\",\n        textStyle: \"無効化されたテキストのスタイル\",\n      },\n    },\n  },\n  demoListItem: {\n    description:\n      \"スタイルを指定されたリストの行のコンポーネントです。FlatListやSectionListなどのコンポーネントを使用することもできますし、単体でも使用できます。\",\n    useCase: {\n      height: {\n        name: \"高さ\",\n        description: \"高さの指定ができます。\",\n        defaultHeight: \"デフォルトの高さ (56px)\",\n        customHeight: \"`height`を使ったカスタムの高さ\",\n        textHeight:\n          \"テキストによって決まった高さ - Reprehenderit incididunt deserunt do do ea labore.\",\n        longText: \"テキストを1行に制限する- Reprehenderit incididunt deserunt do do ea labore.\",\n      },\n      separators: {\n        name: \"セパレーター\",\n        description: \"セパレーター/ディバイダーは用意されてるかつ任意です。\",\n        topSeparator: \"トップセパレーターのみ\",\n        topAndBottomSeparator: \"トップとボトムのセパレーター\",\n        bottomSeparator: \"ボトムのセパレーター\",\n      },\n      icons: {\n        name: \"アイコン\",\n        description: \"右または左のアイコンをカスタマイズすることができます。\",\n        leftIcon: \"左のアイコン\",\n        rightIcon: \"右のアイコン\",\n        leftRightIcons: \"左右のアイコン\",\n      },\n      customLeftRight: {\n        name: \"左右のコンポーネントのカスタマイズ\",\n        description: \"左右のコンポーネントをカスタマイズすることができます。\",\n        customLeft: \"カスタムされた左コンポーネント\",\n        customRight: \"カスタムされた右コンポーネント\",\n      },\n      passingContent: {\n        name: \"コンテントを渡す\",\n        description: \"コンテントを渡す方法はいくつかあります。\",\n        text: \"`text`から - reprehenderit sint\",\n        children: \"Childrenから - mostrud mollit\",\n        nestedChildren1: \"ネストされたchildrenから - proident veniam.\",\n        nestedChildren2: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n      },\n      listIntegration: {\n        name: \"FlatList に組みこむ場合\",\n        description:\n          \"このコンポーネントはお好みのリスト系のコンポーネントへ容易に組み込むことができます。\",\n      },\n      styling: {\n        name: \"スタイリング\",\n        description: \"このコンポーネントはスタイリングの変更ができます。\",\n        styledText: \"スタイルされたテキスト\",\n        styledContainer: \"スタイルされたコンテナ(セパレーター)\",\n        tintedIcons: \"アイコンに色をつける\",\n      },\n    },\n  },\n  demoCard: {\n    description:\n      \"カードは関連する情報同士をまとめるのに役立ちます。ListItemが横に情報を表示するのに使え、こちらは縦に表示するのに使えます。\",\n    useCase: {\n      presets: {\n        name: \"プリセット\",\n        description: \"数種類のプリセットが用意されています。\",\n        default: {\n          heading: \"デフォルトのプリセット\",\n          content: \"Incididunt magna ut aliquip consectetur mollit dolor.\",\n          footer: \"Consectetur nulla non aliquip velit.\",\n        },\n        reversed: {\n          heading: \"リバースのプリセット\",\n          content: \"Reprehenderit occaecat proident amet id laboris.\",\n          footer: \"Consectetur tempor ea non labore anim .\",\n        },\n      },\n      verticalAlignment: {\n        name: \"縦の位置調整\",\n        description: \"カードは用意されたプリセットを使っての縦位置調整ができます。\",\n        top: {\n          heading: \"Top(デフォルト)\",\n          content: \"全てのコンテンツは自動的に上に配置されます。\",\n          footer: \"Footerも同じように上に配置されます。\",\n        },\n        center: {\n          heading: \"センター\",\n          content: \"全てのコンテンツはカードの高さから見て中央に配置されます。\",\n          footer: \"Footerである私も!\",\n        },\n        spaceBetween: {\n          heading: \"Space Between\",\n          content: \"全てのコンテンツは均等に分配されます。\",\n          footer: \"Footerの私はここが一番落ち着くね\",\n        },\n        reversed: {\n          heading: \"Footerのみを下に配置する\",\n          content: \"その名の通り、Footerのみを下に配置することができます。\",\n          footer: \"Footerは一人で寂しい\",\n        },\n      },\n      passingContent: {\n        name: \"コンテントを渡す\",\n        description: \"コンテントを渡す方法はいくつかあります。\",\n        heading: \"`heading`から\",\n        content: \"`content`から\",\n        footer: \"`footer`から\",\n      },\n      customComponent: {\n        name: \"カスタムコンポーネント\",\n        description:\n          \"全てのプリセットはカスタムコンポーネントを使って拡張/変更することができます。\",\n        rightComponent: \"右コンポーネント\",\n        leftComponent: \"左コンポーネント\",\n      },\n      style: {\n        name: \"スタイリング\",\n        description: \"このコンポーネントはスタイリングの変更ができます。\",\n        heading: \"ヘディングのスタイル\",\n        content: \"コンテントのスタイル\",\n        footer: \"フッターのスタイル\",\n      },\n    },\n  },\n  demoAutoImage: {\n    description: \"リモートまたはデータURIによって自動的にサイズを変更する画像コンポーネントです。\",\n    useCase: {\n      remoteUri: { name: \"リモート URI\" },\n      base64Uri: { name: \"Base64 URI\" },\n      scaledToFitDimensions: {\n        name: \"ディメンションにフィットするように拡大する\",\n        description:\n          \"`maxWidth` と/または `maxHeight`を指定することで、アスペクト比を維持したままサイズを変更することができます。`resizeMode: 'contain'`との違いとしては: \\n1. 一方のサイズの指定でも良い（両方の指定の必要がない）。 \\n2. 画像のコンテナに押し込められるのではなく、画像のディメンションを保ったまま指定したサイズに拡大、縮小を行うことができます。\",\n        heightAuto: \"width: 60 / height: auto\",\n        widthAuto: \"width: auto / height: 32\",\n        bothManual: \"width: 60 / height: 60\",\n      },\n    },\n  },\n  demoText: {\n    description:\n      \"テキストを表示する為のコンポーネントです。これはReact NativeのTextコンポーネントを内包する高階コンポーネント(Higher Order Component)です。\",\n    useCase: {\n      presets: {\n        name: \"プリセット\",\n        description: \"数種類のプリセットが用意されています。\",\n        default:\n          \"デフォルトのプリセット - Cillum eu laboris in labore. Excepteur mollit tempor reprehenderit fugiat elit et eu consequat laborum.\",\n        bold: \"ボールドのプリセット - Tempor et ullamco cupidatat in officia. Nulla ea duis elit id sunt ipsum cillum duis deserunt nostrud ut nostrud id.\",\n        subheading: \"サブヘディングのプリセット - In Cupidatat Cillum.\",\n        heading: \"ヘディングのプリセット - Voluptate Adipis.\",\n      },\n      sizes: {\n        name: \"サイズ\",\n        description: \"サイズ用のpropsです.\",\n        xs: \"xs - Ea ipsum est ea ex sunt.\",\n        sm: \"sm - Lorem sunt adipisicin.\",\n        md: \"md - Consequat id do lorem.\",\n        lg: \"lg - Nostrud ipsum ea.\",\n        xl: \"xl - Eiusmod ex excepteur.\",\n        xxl: \"xxl - Cillum eu laboris.\",\n      },\n      weights: {\n        name: \"ウエイト\",\n        description: \"ウエイト用のpropです。\",\n        light:\n          \"ライト - Nulla magna incididunt excepteur est occaecat duis culpa dolore cupidatat enim et.\",\n        normal:\n          \"ノーマル - Magna incididunt dolor ut veniam veniam laboris aliqua velit ea incididunt.\",\n        medium: \"ミディアム - Non duis laborum quis laboris occaecat culpa cillum.\",\n        semibold: \"セミボールド - Exercitation magna nostrud pariatur laborum occaecat aliqua.\",\n        bold: \"ボールド - Eiusmod ullamco magna exercitation est excepteur.\",\n      },\n      passingContent: {\n        name: \"コンテントを渡す\",\n        description: \"コンテントを渡す方法はいくつかあります。\",\n        viaText:\n          \"`text`から - Billum in aute fugiat proident nisi pariatur est. Cupidatat anim cillum eiusmod ad. Officia eu magna aliquip labore dolore consequat.\",\n        viaTx: \"`tx`から -\",\n        children: \"childrenから - Aliqua velit irure reprehenderit eu qui amet veniam consectetur.\",\n        nestedChildren: \"ネストされたchildrenから -\",\n        nestedChildren2: \"Occaecat aliqua irure proident veniam.\",\n        nestedChildren3: \"Ullamco cupidatat officia exercitation velit non ullamco nisi..\",\n        nestedChildren4: \"Occaecat aliqua irure proident veniam.\",\n      },\n      styling: {\n        name: \"スタイリング\",\n        description: \"このコンポーネントはスタイリングの変更ができます。\",\n        text: \"Consequat ullamco veniam velit mollit proident excepteur aliquip id culpa ipsum velit sint nostrud.\",\n        text2:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n        text3:\n          \"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.\",\n      },\n    },\n  },\n  demoHeader: {\n    description:\n      \"様々なスクリーンで登場するコンポーネントです。ナビゲーションのボタンとスクリーンタイトルを含みます。\",\n    useCase: {\n      actionIcons: {\n        name: \"アクションアイコン\",\n        description: \"左右にアイコンを表示させることができます。\",\n        leftIconTitle: \"左アイコン\",\n        rightIconTitle: \"右アイコン\",\n        bothIconsTitle: \"両方のアイコン\",\n      },\n      actionText: {\n        name: \"アクションテキスト\",\n        description: \"左右にテキストを表示させることができます。\",\n        leftTxTitle: \"`leftTx`から\",\n        rightTextTitle: \"`rightText`から\",\n      },\n      customActionComponents: {\n        name: \"カスタムアクションコンポーネント\",\n        description:\n          \"アイコンまたはテキスト以外のものが必要な場合は、カスタムのアクションコンポーネントを渡すことができます。\",\n        customLeftActionTitle: \"カスタムの左アクション\",\n      },\n      titleModes: {\n        name: \"タイトルモード\",\n        description:\n          \"タイトルはデフォルトで中央に配置されますが、長すぎるとカットされてしまいます。Flexを使うことでアクションボタンから自動的にポジションを調整することもできます。\",\n        centeredTitle: \"Centered Title\",\n        flexTitle: \"Flex Title\",\n      },\n      styling: {\n        name: \"スタイリング\",\n        description: \"このコンポーネントはスタイリングの変更ができます。\",\n        styledTitle: \"スタイルされたタイトル\",\n        styledWrapperTitle: \"スタイルされたラッパー\",\n        tintedIconsTitle: \"色付けされたアイコン\",\n      },\n    },\n  },\n  demoEmptyState: {\n    description:\n      \"表示する為のデータが存在しない場合に使えるコンポーネントです。ユーザーに取るべきアクションをお勧めする際に有用です。\",\n    useCase: {\n      presets: {\n        name: \"プリセット\",\n        description:\n          \"text/imageのセットを使ってカスタマイズすることができます。これは`generic`のものです。カスタマイズが必要になることを想定して、このコンポーネントにデフォルトのプリセットは存在しません。\",\n      },\n      passingContent: {\n        name: \"コンテントを渡す\",\n        description: \"コンテントを渡す方法はいくつかあります。\",\n        customizeImageHeading: \"画像をカスタマイズ\",\n        customizeImageContent: \"画像のソースを渡すことができます。\",\n        viaHeadingProp: \"`heading`から\",\n        viaContentProp: \"`content`から\",\n        viaButtonProp: \"`button`から\",\n      },\n      styling: {\n        name: \"スタイリング\",\n        description: \"このコンポーネントはスタイリングの変更ができます。\",\n      },\n    },\n  },\n}\n\nexport default demoJa\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/i18n/demo-ko.ts",
    "content": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoKo: DemoTranslations = {\n  demoIcon: {\n    description:\n      \"등록된 아이콘을 렌더링하는 컴포넌트입니다. `onPress`가 구현되어 있으면 <TouchableOpacity />로, 그렇지 않으면 <View />로 감쌉니다.\",\n    useCase: {\n      icons: {\n        name: \"아이콘\",\n        description: \"컴포넌트에 등록된 아이콘 목록입니다.\",\n      },\n      size: {\n        name: \"크기\",\n        description: \"크기 속성이 있습니다.\",\n      },\n      color: {\n        name: \"색상\",\n        description: \"색상 속성이 있습니다.\",\n      },\n      styling: {\n        name: \"스타일링\",\n        description: \"컴포넌트는 쉽게 스타일링할 수 있습니다.\",\n      },\n    },\n  },\n  demoTextField: {\n    description: \"TextField 컴포넌트는 텍스트 입력 및 편집을 허용합니다.\",\n    useCase: {\n      statuses: {\n        name: \"상태\",\n        description:\n          \"다른 컴포넌트의 `preset`과 유사한 상태 속성이 있으며, 컴포넌트의 기능에도 영향을 미칩니다.\",\n        noStatus: {\n          label: \"상태 없음\",\n          helper: \"이것이 기본 상태입니다\",\n          placeholder: \"텍스트가 여기에 들어갑니다\",\n        },\n        error: {\n          label: \"오류 상태\",\n          helper: \"오류가 있을 때 사용하는 상태입니다\",\n          placeholder: \"텍스트가 여기에 들어갑니다\",\n        },\n        disabled: {\n          label: \"비활성 상태\",\n          helper: \"편집 기능을 비활성화하고 텍스트를 표시하지 않습니다\",\n          placeholder: \"텍스트가 여기에 들어갑니다\",\n        },\n      },\n      passingContent: {\n        name: \"내용 전달\",\n        description: \"내용을 전달하는 몇 가지 방법이 있습니다.\",\n        viaLabel: {\n          labelTx: \"`label` 속성으로\",\n          helper: \"`helper` 속성으로\",\n          placeholder: \"`placeholder` 속성으로\",\n        },\n        rightAccessory: {\n          label: \"오른쪽 액세서리\",\n          helper: \"이 속성은 React 요소를 반환하는 함수를 받습니다.\",\n        },\n        leftAccessory: {\n          label: \"왼쪽 액세서리\",\n          helper: \"이 속성은 React 요소를 반환하는 함수를 받습니다.\",\n        },\n        supportsMultiline: {\n          label: \"멀티라인 지원\",\n          helper: \"멀티라인 텍스트를 위한 더 높은 입력을 활성화합니다.\",\n        },\n      },\n      styling: {\n        name: \"스타일링\",\n        description: \"컴포넌트는 쉽게 스타일링할 수 있습니다.\",\n        styleInput: {\n          label: \"입력 스타일\",\n          helper: \"`style` 속성으로\",\n        },\n        styleInputWrapper: {\n          label: \"입력 래퍼 스타일\",\n          helper: \"`inputWrapperStyle` 속성으로\",\n        },\n        styleContainer: {\n          label: \"컨테이너 스타일\",\n          helper: \"`containerStyle` 속성으로\",\n        },\n        styleLabel: {\n          label: \"레이블 및 헬퍼 스타일\",\n          helper: \"`LabelTextProps` 및 `HelperTextProps` 스타일 속성으로\",\n        },\n        styleAccessories: {\n          label: \"액세서리 스타일\",\n          helper: \"`RightAccessory` 및 `LeftAccessory` 스타일 속성으로\",\n        },\n      },\n    },\n  },\n  demoToggle: {\n    description:\n      \"불리언 입력을 렌더링합니다. 사용자가 수행한 작업을 반영하기 위해 값 속성을 업데이트하는 onValueChange 콜백이 필요한 제어된 컴포넌트입니다. 값 속성이 업데이트되지 않으면, 컴포넌트는 사용자 작업의 예상 결과 대신 제공된 값 속성을 계속 렌더링합니다.\",\n    useCase: {\n      variants: {\n        name: \"변형\",\n        description:\n          \"이 컴포넌트는 몇 가지 변형을 지원합니다. 특정 변형을 대폭 커스터마이즈해야 하는 경우에는 쉽게 리팩토링할 수 있습니다. 기본값은 `체크박스`입니다.\",\n        checkbox: {\n          label: \"`체크박스` 변형\",\n          helper: \"단일 켜기/끄기 입력에 사용할 수 있습니다.\",\n        },\n        radio: {\n          label: \"`라디오` 변형\",\n          helper: \"여러 옵션이 있는 경우 사용하십시오.\",\n        },\n        switch: {\n          label: \"`스위치` 변형\",\n          helper: \"더 눈에 띄는 켜기/끄기 입력입니다. 접근성 지원이 더 좋습니다.\",\n        },\n      },\n      statuses: {\n        name: \"상태\",\n        description:\n          \"다른 컴포넌트의 `preset`과 유사한 상태 속성이 있으며, 컴포넌트의 기능에도 영향을 미칩니다.\",\n        noStatus: \"상태 없음 - 기본 상태\",\n        errorStatus: \"오류 상태 - 오류가 있을 때 사용\",\n        disabledStatus: \"비활성 상태 - 편집 기능을 비활성화하고 입력을 표시하지 않음\",\n      },\n      passingContent: {\n        name: \"내용 전달\",\n        description: \"내용을 전달하는 몇 가지 방법이 있습니다.\",\n        useCase: {\n          checkBox: {\n            label: \"`labelTx` 속성으로\",\n            helper: \"`helperTx` 속성으로\",\n          },\n          checkBoxMultiLine: {\n            helper: \"멀티라인 지원 - 멀티라인 지원을 위한 예제 문장입니다. 하나 둘 셋.\",\n          },\n          radioChangeSides: {\n            helper: \"양쪽을 변경할 수 있습니다 - 양쪽 변경을 위한 예제 문장입니다. 하나 둘 셋.\",\n          },\n          customCheckBox: {\n            label: \"맞춤 체크박스 아이콘 전달.\",\n          },\n          switch: {\n            label: \"스위치는 텍스트로 읽을 수 있습니다\",\n            helper:\n              \"기본적으로 이 옵션은 `Text`를 사용하지 않습니다. 폰트에 따라 켜기/끄기 문자가 이상하게 보일 수 있기 때문입니다. 필요에 따라 커스터마이즈하세요.\",\n          },\n          switchAid: {\n            label: \"또는 아이콘으로 보조\",\n          },\n        },\n      },\n      styling: {\n        name: \"스타일링\",\n        description: \"컴포넌트는 쉽게 스타일링할 수 있습니다.\",\n        outerWrapper: \"1 - 입력 외부 래퍼 스타일링\",\n        innerWrapper: \"2 - 입력 내부 래퍼 스타일링\",\n        inputDetail: \"3 - 입력 디테일 스타일링\",\n        labelTx: \"labelTx도 스타일링할 수 있습니다\",\n        styleContainer: \"또는 전체 컨테이너 스타일링\",\n      },\n    },\n  },\n  demoButton: {\n    description:\n      \"사용자가 작업을 수행하고 선택을 할 수 있도록 하는 컴포넌트입니다. Text 컴포넌트를 Pressable 컴포넌트로 감쌉니다.\",\n    useCase: {\n      presets: {\n        name: \"프리셋\",\n        description: \"사전 구성된 몇 가지 프리셋이 있습니다.\",\n      },\n      passingContent: {\n        name: \"내용 전달\",\n        description: \"내용을 전달하는 몇 가지 방법이 있습니다.\",\n        viaTextProps: \"`text` 속성으로 - 예제 문장입니다.\",\n        children: \"자식 - 또 다른 예제 문장입니다.\",\n        rightAccessory: \"오른쪽 액세서리 - 예제 문장입니다.\",\n        leftAccessory: \"왼쪽 액세서리 - 예제 문장입니다.\",\n        nestedChildren: \"중첩 자식 - 별 하나에 추억과 별 하나에 사랑과 별 하나에 쓸쓸함과\",\n        nestedChildren2: \"별 하나에 동경과 별 하나에 시와 \",\n        nestedChildren3: \"별 하나에 어머니, 어머니.\",\n        multiLine:\n          \"멀티라인 - 죽는 날까지 하늘을 우러러 한 점 부끄럼이 없기를, 잎새에 이는 바람에도 나는 괴로워했다. 별을 노래하는 마음으로 모든 죽어 가는 것을 사랑해야지 그리고 나한테 주어진 길을 걸어가야겠다. 오늘 밤에도 별이 바람에 스치운다.\",\n      },\n      styling: {\n        name: \"스타일링\",\n        description: \"컴포넌트는 쉽게 스타일링할 수 있습니다.\",\n        styleContainer: \"스타일 컨테이너 - 예제 문장\",\n        styleText: \"스타일 텍스트 - 예제 문장\",\n        styleAccessories: \"스타일 액세서리 - 또 다른 예제 문장\",\n        pressedState: \"스타일 눌린 상태 - 예제 문장\",\n      },\n      disabling: {\n        name: \"비활성화\",\n        description:\n          \"컴포넌트는 비활성화할 수 있으며, 그에 따라 스타일링할 수 있습니다. 누르는 동작이 비활성화됩니다.\",\n        standard: \"비활성화 - 표준\",\n        filled: \"비활성화 - 채워진\",\n        reversed: \"비활성화 - 역방향\",\n        accessory: \"비활성화된 액세서리 스타일\",\n        textStyle: \"비활성화된 텍스트 스타일\",\n      },\n    },\n  },\n  demoListItem: {\n    description: \"FlatList, SectionList 또는 자체적으로 사용할 수 있는 스타일된 행 컴포넌트입니다.\",\n    useCase: {\n      height: {\n        name: \"높이\",\n        description: \"행은 다른 높이를 가질 수 있습니다.\",\n        defaultHeight: \"기본 높이 (56px)\",\n        customHeight: \"`height` 속성을 통해 사용자 정의 높이\",\n        textHeight:\n          \"텍스트 내용에 의해 결정된 높이 - 예제를 위한 긴 문장입니다. 하나 둘 셋. 안녕하세요.\",\n        longText:\n          \"긴 텍스트를 한 줄로 제한 - 이것 역시 예제를 위한 긴 문장입니다. 오늘 날씨는 어떤가요?\",\n      },\n      separators: {\n        name: \"구분선\",\n        description: \"구분선 / 디바이더가 사전 구성되어 있으며 선택 사항입니다.\",\n        topSeparator: \"상단 구분선만\",\n        topAndBottomSeparator: \"상단 및 하단 구분선\",\n        bottomSeparator: \"하단 구분선만\",\n      },\n      icons: {\n        name: \"아이콘\",\n        description: \"왼쪽 또는 오른쪽 아이콘을 사용자 정의할 수 있습니다.\",\n        leftIcon: \"왼쪽 아이콘\",\n        rightIcon: \"오른쪽 아이콘\",\n        leftRightIcons: \"왼쪽 및 오른쪽 아이콘\",\n      },\n      customLeftRight: {\n        name: \"사용자 정의 왼쪽/오른쪽 컴포넌트\",\n        description: \"필요시에는 사용자가 정의한 왼쪽/오른쪽 컴포넌트를 전달할 수 있습니다.\",\n        customLeft: \"사용자 정의 왼쪽 컴포넌트\",\n        customRight: \"사용자 정의 오른쪽 컴포넌트\",\n      },\n      passingContent: {\n        name: \"내용 전달\",\n        description: \"내용을 전달하는 몇 가지 방법이 있습니다.\",\n        text: \"`text` 속성으로 - 예제 문장입니다.\",\n        children: \"자식 - 또 다른 예제 문장입니다.\",\n        nestedChildren1: \"중첩 자식 - 이것도 예제 문장입니다..\",\n        nestedChildren2: \"또 다른 예제 문장, 중첩이 된 형태입니다.\",\n      },\n      listIntegration: {\n        name: \"FlatList 통합\",\n        description: \"이 컴포넌트는 선호하는 리스트 인터페이스와 쉽게 통합할 수 있습니다.\",\n      },\n      styling: {\n        name: \"스타일링\",\n        description: \"컴포넌트는 쉽게 스타일링할 수 있습니다.\",\n        styledText: \"스타일된 텍스트\",\n        styledContainer: \"스타일된 컨테이너 (구분선)\",\n        tintedIcons: \"색이 입혀진 아이콘\",\n      },\n    },\n  },\n  demoCard: {\n    description:\n      \"카드는 관련 정보를 컨테이너에 담아 표시하는 데 유용합니다. ListItem이 내용을 수평으로 표시한다면, 카드는 내용을 수직으로 표시할 수 있습니다.\",\n    useCase: {\n      presets: {\n        name: \"프리셋\",\n        description: \"사전 구성된 몇 가지 프리셋이 있습니다.\",\n        default: {\n          heading: \"기본 프리셋 (기본값)\",\n          content: \"예제 문장입니다. 그믐밤 반디불은 부서진 달조각\",\n          footer: \"숲으로 가자 달조각을 주으려 숲으로 가자.\",\n        },\n        reversed: {\n          heading: \"역방향 프리셋\",\n          content: \"예제 문장입니다. 그믐밤 반디불은 부서진 달조각\",\n          footer: \"숲으로 가자 달조각을 주으려 숲으로 가자.\",\n        },\n      },\n      verticalAlignment: {\n        name: \"수직 정렬\",\n        description: \"카드는 필요에 따라 미리 구성된 다양한 정렬방법으로 제공됩니다.\",\n        top: {\n          heading: \"상단 (기본값)\",\n          content: \"모든 콘텐츠가 자동으로 상단에 정렬됩니다.\",\n          footer: \"심지어 푸터도\",\n        },\n        center: {\n          heading: \"중앙\",\n          content: \"콘텐츠는 카드 높이에 상대적으로 중앙에 배치됩니다.\",\n          footer: \"나도!\",\n        },\n        spaceBetween: {\n          heading: \"공간 사이\",\n          content: \"모든 콘텐츠가 고르게 간격을 둡니다.\",\n          footer: \"나는 내가 있고 싶은 곳에 있어요.\",\n        },\n        reversed: {\n          heading: \"푸터 강제 하단\",\n          content: \"푸터를 원하는 위치에 밀어 넣습니다.\",\n          footer: \"여기 너무 외로워요.\",\n        },\n      },\n      passingContent: {\n        name: \"내용 전달\",\n        description: \"내용을 전달하는 몇 가지 방법이 있습니다.\",\n        heading: \"`heading` 속성으로\",\n        content: \"`content` 속성으로\",\n        footer: \"푸터도 외로워요.\",\n      },\n      customComponent: {\n        name: \"사용자 정의 컴포넌트\",\n        description:\n          \"사전 구성된 컴포넌트 중 하나를 직접 만든 자신의 컴포넌트로 대체할 수 있습니다. 추가 컴포넌트도 덧붙여 넣을 수 있습니다.\",\n        rightComponent: \"오른쪽 컴포넌트\",\n        leftComponent: \"왼쪽 컴포넌트\",\n      },\n      style: {\n        name: \"스타일링\",\n        description: \"컴포넌트는 쉽게 스타일링할 수 있습니다.\",\n        heading: \"헤딩 스타일링\",\n        content: \"컨텐츠 스타일링\",\n        footer: \"푸터 스타일링\",\n      },\n    },\n  },\n  demoAutoImage: {\n    description: \"원격 또는 data-uri 이미지의 크기를 자동으로 조정하는 Image 컴포넌트입니다.\",\n    useCase: {\n      remoteUri: { name: \"원격 URI\" },\n      base64Uri: { name: \"Base64 URI\" },\n      scaledToFitDimensions: {\n        name: \"치수에 맞게 조정\",\n        description:\n          \"`maxWidth` 단독으로, 혹은 `maxHeight` 속성과 함께 제공하면, 이미지는 비율을 유지하면서 자동으로 크기가 조정됩니다. 이것이 `resizeMode: 'contain'`과 다른 점은 무엇일까요? 첫째, 한쪽 크기만 지정할 수 있습니다. 둘째, 이미지가 이미지 컨테이너 내에 포함되는 대신 원하는 치수에 맞게 조정됩니다.\",\n        heightAuto: \"너비: 60 / 높이: 자동\",\n        widthAuto: \"너비: 자동 / 높이: 32\",\n        bothManual: \"너비: 60 / 높이: 60\",\n      },\n    },\n  },\n  demoText: {\n    description:\n      \"텍스트 표시가 필요한 경우를 위해, 이 컴포넌트는 기본 React Native 컴포넌트 위에 HOC로 제작되었습니다.\",\n    useCase: {\n      presets: {\n        name: \"프리셋\",\n        description: \"사전 구성된 몇 가지 프리셋이 있습니다.\",\n        default: \"기본 프리셋 - 예제 문장입니다. 하나 둘 셋.\",\n        bold: \"볼드 프리셋 - 예제 문장입니다. 하나 둘 셋.\",\n        subheading: \"서브헤딩 프리셋 - 예제 문장입니다. 하나 둘 셋.\",\n        heading: \"헤딩 프리셋 - 예제 문장입니다. 하나 둘 셋.\",\n      },\n      sizes: {\n        name: \"크기\",\n        description: \"크기 속성이 있습니다.\",\n        xs: \"xs - 조금 더 작은 크기 속성입니다.\",\n        sm: \"sm - 작은 크기 속성입니다.\",\n        md: \"md - 중간 크기 속성입니다.\",\n        lg: \"lg - 큰 크기 속성입니다.\",\n        xl: \"xl - 조금 더 큰 크기 속성입니다.\",\n        xxl: \"xxl - 아주 큰 크기 속성입니다.\",\n      },\n      weights: {\n        name: \"굵기\",\n        description: \"굵기 속성이 있습니다.\",\n        light: \"가벼움 - 예제 문장입니다. 안녕하세요. 하나 둘 셋.\",\n        normal: \"보통 - 예제 문장입니다. 안녕하세요. 하나 둘 셋.\",\n        medium: \"중간 - 예제 문장입니다. 안녕하세요. 하나 둘 셋.\",\n        semibold: \"세미볼드 - 예제 문장입니다. 안녕하세요. 하나 둘 셋.\",\n        bold: \"볼드 - 예제 문장입니다. 안녕하세요. 하나 둘 셋.\",\n      },\n      passingContent: {\n        name: \"내용 전달\",\n        description: \"내용을 전달하는 몇 가지 방법이 있습니다.\",\n        viaText:\n          \"`text` 속성으로 - 죽는 날까지 하늘을 우러러 한 점 부끄럼이 없기를, 잎새에 이는 바람에도 나는 괴로워했다. 별을 노래하는 마음으로 모든 죽어 가는 것을 사랑해야지 그리고 나한테 주어진 길을 걸어가야겠다. 오늘 밤에도 별이 바람에 스치운다.\",\n        viaTx: \"`tx` 속성으로\",\n        children: \"자식 - 또 다른 예제 문장입니다. 하나 둘 셋.\",\n        nestedChildren: \"중첩 자식\",\n        nestedChildren2: \"죽는 날까지 하늘을 우러러 한 점 부끄럼이 없기를, \",\n        nestedChildren3: \"잎새에 이는 바람에도 나는 괴로워했다.\",\n        nestedChildren4: \"별을 노래하는 마음으로 모든 죽어 가는 것을 사랑해야지.\",\n      },\n      styling: {\n        name: \"스타일링\",\n        description: \"컴포넌트는 쉽게 스타일링할 수 있습니다.\",\n        text: \"그리고 나한테 주어진 길을 걸어가야겠다.\",\n        text2: \"오늘 밤에도 별이 바람에 스치운다.\",\n        text3: \"계속 이어지는 예제 문장입니다. 하나 둘 셋.\",\n      },\n    },\n  },\n  demoHeader: {\n    description:\n      \"여러 화면에 나타나는 컴포넌트입니다. 네비게이션 버튼과 화면 제목을 포함할 것입니다.\",\n    useCase: {\n      actionIcons: {\n        name: \"액션 아이콘\",\n        description: \"왼쪽 또는 오른쪽 액션 컴포넌트에 아이콘을 쉽게 전달할 수 있습니다.\",\n        leftIconTitle: \"왼쪽 아이콘\",\n        rightIconTitle: \"오른쪽 아이콘\",\n        bothIconsTitle: \"양쪽 아이콘\",\n      },\n      actionText: {\n        name: \"액션 텍스트\",\n        description: \"왼쪽 또는 오른쪽 액션 컴포넌트에 텍스트를 쉽게 전달할 수 있습니다.\",\n        leftTxTitle: \"`leftTx`를 통해\",\n        rightTextTitle: \"`rightText`를 통해\",\n      },\n      customActionComponents: {\n        name: \"사용자 정의 액션 컴포넌트\",\n        description:\n          \"아이콘이나 텍스트 옵션이 충분하지 않은 경우, 사용자 정의 액션 컴포넌트를 전달할 수 있습니다.\",\n        customLeftActionTitle: \"사용자 정의 왼쪽 액션\",\n      },\n      titleModes: {\n        name: \"제목 모드\",\n        description:\n          \"제목은 기본적으로 중앙에 고정되지만 너무 길면 잘릴 수 있습니다. 액션 버튼에 맞춰 조정할 수 있습니다.\",\n        centeredTitle: \"중앙 제목\",\n        flexTitle: \"유연한 제목\",\n      },\n      styling: {\n        name: \"스타일링\",\n        description: \"컴포넌트는 쉽게 스타일링할 수 있습니다.\",\n        styledTitle: \"스타일된 제목\",\n        styledWrapperTitle: \"스타일된 래퍼\",\n        tintedIconsTitle: \"색이 입혀진 아이콘\",\n      },\n    },\n  },\n  demoEmptyState: {\n    description:\n      \"표시할 데이터가 없을 때 사용할 수 있는 컴포넌트입니다. 사용자가 다음에 무엇을 할지 안내할 수 있습니다.\",\n    useCase: {\n      presets: {\n        name: \"프리셋\",\n        description:\n          \"다양한 텍스트/이미지 세트를 만들 수 있습니다. `generic`이라는 사전 정의된 세트가 하나 있습니다. 기본값이 없으므로 완전히 사용자 정의된 EmptyState를 원할 경우 사용할 수 있습니다.\",\n      },\n      passingContent: {\n        name: \"내용 전달\",\n        description: \"내용을 전달하는 몇 가지 방법이 있습니다.\",\n        customizeImageHeading: \"이미지 맞춤 설정\",\n        customizeImageContent: \"어떤 이미지 소스도 전달할 수 있습니다.\",\n        viaHeadingProp: \"`heading` 속성으로\",\n        viaContentProp: \"`content` 속성으로\",\n        viaButtonProp: \"`button` 속성으로\",\n      },\n      styling: {\n        name: \"스타일링\",\n        description: \"컴포넌트는 쉽게 스타일링할 수 있습니다.\",\n      },\n    },\n  },\n}\n\nexport default demoKo\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/i18n/en.ts",
    "content": "import demoEn from \"./demo-en\" // @demo remove-current-line\n\nconst en = {\n  common: {\n    ok: \"OK!\",\n    cancel: \"Cancel\",\n    back: \"Back\",\n    logOut: \"Log Out\", // @demo remove-current-line\n  },\n  welcomeScreen: {\n    postscript:\n      \"psst  — This probably isn't what your app looks like. (Unless your designer handed you these screens, and in that case, ship it!)\",\n    readyForLaunch: \"Your app, almost ready for launch!\",\n    exciting: \"(ohh, this is exciting!)\",\n    letsGo: \"Let's go!\", // @demo remove-current-line\n  },\n  errorScreen: {\n    title: \"Something went wrong!\",\n    friendlySubtitle:\n      \"This is the screen that your users will see in production when an error is thrown. You'll want to customize this message (located in `app/i18n/en.ts`) and probably the layout as well (`app/screens/ErrorScreen`). If you want to remove this entirely, check `app/app.tsx` for the <ErrorBoundary> component.\",\n    reset: \"RESET APP\",\n    traceTitle: \"Error from %{name} stack\", // @demo remove-current-line\n  },\n  emptyStateComponent: {\n    generic: {\n      heading: \"So empty... so sad\",\n      content: \"No data found yet. Try clicking the button to refresh or reload the app.\",\n      button: \"Let's try this again\",\n    },\n  },\n  // @demo remove-block-start\n  errors: {\n    invalidEmail: \"Invalid email address.\",\n  },\n  loginScreen: {\n    logIn: \"Log In\",\n    enterDetails:\n      \"Enter your details below to unlock top secret info. You'll never guess what we've got waiting. Or maybe you will; it's not rocket science here.\",\n    emailFieldLabel: \"Email\",\n    passwordFieldLabel: \"Password\",\n    emailFieldPlaceholder: \"Enter your email address\",\n    passwordFieldPlaceholder: \"Super secret password here\",\n    tapToLogIn: \"Tap to log in!\",\n    hint: \"Hint: you can use any email address and your favorite password :)\",\n  },\n  demoNavigator: {\n    componentsTab: \"Components\",\n    debugTab: \"Debug\",\n    communityTab: \"Community\",\n    podcastListTab: \"Podcast\",\n  },\n  demoCommunityScreen: {\n    title: \"Connect with the community\",\n    tagLine:\n      \"Plug in to Infinite Red's community of React Native engineers and level up your app development with us!\",\n    joinUsOnSlackTitle: \"Join us on Slack\",\n    joinUsOnSlack:\n      \"Wish there was a place to connect with React Native engineers around the world? Join the conversation in the Infinite Red Community Slack! Our growing community is a safe space to ask questions, learn from others, and grow your network.\",\n    joinSlackLink: \"Join the Slack Community\",\n    makeIgniteEvenBetterTitle: \"Make Ignite even better\",\n    makeIgniteEvenBetter:\n      \"Have an idea to make Ignite even better? We're happy to hear that! We're always looking for others who want to help us build the best React Native tooling out there. Join us over on GitHub to join us in building the future of Ignite.\",\n    contributeToIgniteLink: \"Contribute to Ignite\",\n    theLatestInReactNativeTitle: \"The latest in React Native\",\n    theLatestInReactNative: \"We're here to keep you current on all React Native has to offer.\",\n    reactNativeRadioLink: \"React Native Radio\",\n    reactNativeNewsletterLink: \"React Native Newsletter\",\n    reactNativeLiveLink: \"React Native Live\",\n    chainReactConferenceLink: \"Chain React Conference\",\n    hireUsTitle: \"Hire Infinite Red for your next project\",\n    hireUs:\n      \"Whether it's running a full project or getting teams up to speed with our hands-on training, Infinite Red can help with just about any React Native project.\",\n    hireUsLink: \"Send us a message\",\n  },\n  demoShowroomScreen: {\n    jumpStart: \"Components to jump start your project!\",\n    lorem2Sentences:\n      \"Nulla cupidatat deserunt amet quis aliquip nostrud do adipisicing. Adipisicing excepteur elit laborum Lorem adipisicing do duis.\",\n    demoHeaderTxExample: \"Yay\",\n    demoViaTxProp: \"Via `tx` Prop\",\n    demoViaSpecifiedTxProp: \"Via `{{prop}}Tx` Prop\",\n  },\n  demoDebugScreen: {\n    howTo: \"HOW TO\",\n    title: \"Debug\",\n    tagLine:\n      \"Congratulations, you've got a very advanced React Native app template here.  Take advantage of this boilerplate!\",\n    reactotron: \"Send to Reactotron\",\n    reportBugs: \"Report Bugs\",\n    demoList: \"Demo List\",\n    demoPodcastList: \"Demo Podcast List\",\n    androidReactotronHint:\n      \"If this doesn't work, ensure the Reactotron desktop app is running, run adb reverse tcp:9090 tcp:9090 from your terminal, and reload the app.\",\n    iosReactotronHint:\n      \"If this doesn't work, ensure the Reactotron desktop app is running and reload app.\",\n    macosReactotronHint:\n      \"If this doesn't work, ensure the Reactotron desktop app is running and reload app.\",\n    webReactotronHint:\n      \"If this doesn't work, ensure the Reactotron desktop app is running and reload app.\",\n    windowsReactotronHint:\n      \"If this doesn't work, ensure the Reactotron desktop app is running and reload app.\",\n  },\n  demoPodcastListScreen: {\n    title: \"React Native Radio episodes\",\n    onlyFavorites: \"Only Show Favorites\",\n    favoriteButton: \"Favorite\",\n    unfavoriteButton: \"Unfavorite\",\n    accessibility: {\n      cardHint:\n        \"Double tap to listen to the episode. Double tap and hold to {{action}} this episode.\",\n      switch: \"Switch on to only show favorites\",\n      favoriteAction: \"Toggle Favorite\",\n      favoriteIcon: \"Episode not favorited\",\n      unfavoriteIcon: \"Episode favorited\",\n      publishLabel: \"Published {{date}}\",\n      durationLabel: \"Duration: {{hours}} hours {{minutes}} minutes {{seconds}} seconds\",\n    },\n    noFavoritesEmptyState: {\n      heading: \"This looks a bit empty\",\n      content:\n        \"No favorites have been added yet. Tap the heart on an episode to add it to your favorites!\",\n    },\n  },\n  // @demo remove-block-start\n  ...demoEn,\n  // @demo remove-block-end\n}\n\nexport default en\nexport type Translations = typeof en\n"
  },
  {
    "path": "boilerplate/app/i18n/es.ts",
    "content": "import demoEs from \"./demo-es\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst es: Translations = {\n  common: {\n    ok: \"OK\",\n    cancel: \"Cancelar\",\n    back: \"Volver\",\n    logOut: \"Cerrar sesión\", // @demo remove-current-line\n  },\n  welcomeScreen: {\n    postscript:\n      \"psst — Esto probablemente no es cómo se va a ver tu app. (A menos que tu diseñador te haya enviado estas pantallas, y en ese caso, ¡lánzalas en producción!)\",\n    readyForLaunch: \"Tu app, casi lista para su lanzamiento\",\n    exciting: \"(¡ohh, esto es emocionante!)\",\n    letsGo: \"¡Vamos!\", // @demo remove-current-line\n  },\n  errorScreen: {\n    title: \"¡Algo salió mal!\",\n    friendlySubtitle:\n      \"Esta es la pantalla que verán tus usuarios en producción cuando haya un error. Vas a querer personalizar este mensaje (que está ubicado en `app/i18n/es.ts`) y probablemente también su diseño (`app/screens/ErrorScreen`). Si quieres eliminarlo completamente, revisa `app/app.tsx` y el componente <ErrorBoundary>.\",\n    reset: \"REINICIA LA APP\",\n    traceTitle: \"Error desde %{name}\", // @demo remove-current-line\n  },\n  emptyStateComponent: {\n    generic: {\n      heading: \"Muy vacío... muy triste\",\n      content:\n        \"No se han encontrado datos por el momento. Intenta darle clic en el botón para refrescar o recargar la app.\",\n      button: \"Intentemos de nuevo\",\n    },\n  },\n  // @demo remove-block-start\n  errors: {\n    invalidEmail: \"Email inválido.\",\n  },\n  loginScreen: {\n    logIn: \"Iniciar sesión\",\n    enterDetails:\n      \"Ingresa tus datos a continuación para desbloquear información ultra secreta. Nunca vas a adivinar lo que te espera al otro lado. O quizás si lo harás; la verdad no hay mucha ciencia alrededor.\",\n    emailFieldLabel: \"Email\",\n    passwordFieldLabel: \"Contraseña\",\n    emailFieldPlaceholder: \"Ingresa tu email\",\n    passwordFieldPlaceholder: \"Contraseña super secreta aquí\",\n    tapToLogIn: \"¡Presiona acá para iniciar sesión!\",\n    hint: \"Consejo: puedes usar cualquier email y tu contraseña preferida :)\",\n  },\n  demoNavigator: {\n    componentsTab: \"Componentes\",\n    debugTab: \"Debug\",\n    communityTab: \"Comunidad\",\n    podcastListTab: \"Podcasts\",\n  },\n  demoCommunityScreen: {\n    title: \"Conecta con la comunidad\",\n    tagLine:\n      \"Únete a la comunidad React Native con los ingenieros de Infinite Red y mejora con nosotros tus habilidades para el desarrollo de apps.\",\n    joinUsOnSlackTitle: \"Únete a nosotros en Slack\",\n    joinUsOnSlack:\n      \"¿Quieres conectar con desarrolladores de React Native de todo el mundo? Únete a la conversación en nuestra comunidad de Slack. Nuestra comunidad, que crece día a día, es un espacio seguro para hacer preguntas, aprender de los demás y ampliar tu red.\",\n    joinSlackLink: \"Únete a la comunidad de Slack\",\n    makeIgniteEvenBetterTitle: \"Haz que Ignite sea aún mejor\",\n    makeIgniteEvenBetter:\n      \"¿Tienes una idea para hacer que Ignite sea aún mejor? ¡Nos encantaría escucharla! Estamos siempre buscando personas que quieran ayudarnos a construir las mejores herramientas para React Native. Únete a nosotros en GitHub para ayudarnos a construir el futuro de Ignite.\",\n    contributeToIgniteLink: \"Contribuir a Ignite\",\n    theLatestInReactNativeTitle: \"Lo último en el mundo de React Native\",\n    theLatestInReactNative:\n      \"Estamos aquí para mantenerte al día con todo lo que React Native tiene para ofrecer.\",\n    reactNativeRadioLink: \"React Native Radio\",\n    reactNativeNewsletterLink: \"Newsletter de React Native\",\n    reactNativeLiveLink: \"React Native Live\",\n    chainReactConferenceLink: \"Conferencia Chain React\",\n    hireUsTitle: \"Trabaja con Infinite Red en tu próximo proyecto\",\n    hireUs:\n      \"Ya sea para gestionar un proyecto de inicio a fin o educación a equipos a través de nuestros cursos y capacitación práctica, Infinite Red puede ayudarte en casi cualquier proyecto de React Native.\",\n    hireUsLink: \"Envíanos un mensaje\",\n  },\n  demoShowroomScreen: {\n    jumpStart: \"Componentes para comenzar tu proyecto\",\n    lorem2Sentences:\n      \"Nulla cupidatat deserunt amet quis aliquip nostrud do adipisicing. Adipisicing excepteur elit laborum Lorem adipisicing do duis.\",\n    demoHeaderTxExample: \"Yay\",\n    demoViaTxProp: \"A través de el atributo `tx`\",\n    demoViaSpecifiedTxProp: \"A través de el atributo específico `{{prop}}Tx`\",\n  },\n  demoDebugScreen: {\n    howTo: \"CÓMO HACERLO\",\n    title: \"Debug\",\n    tagLine:\n      \"Felicidades, aquí tienes una propuesta de arquitectura y base de código avanzada para una app en React Native. ¡Disfrutalos!\",\n    reactotron: \"Enviar a Reactotron\",\n    reportBugs: \"Reportar errores\",\n    demoList: \"Lista demo\",\n    demoPodcastList: \"Lista demo de podcasts\",\n    androidReactotronHint:\n      \"Si esto no funciona, asegúrate de que la app de escritorio de Reactotron se esté ejecutando, corre adb reverse tcp:9090 tcp:9090 desde tu terminal, y luego recarga la app.\",\n    iosReactotronHint:\n      \"Si esto no funciona, asegúrate de que la app de escritorio de Reactotron se esté ejecutando, y luego recarga la app.\",\n    macosReactotronHint:\n      \"Si esto no funciona, asegúrate de que la app de escritorio de Reactotron se esté ejecutando, y luego recarga la app.\",\n    webReactotronHint:\n      \"Si esto no funciona, asegúrate de que la app de escritorio de Reactotron se esté ejecutando, y luego recarga la app.\",\n    windowsReactotronHint:\n      \"Si esto no funciona, asegúrate de que la app de escritorio de Reactotron se esté ejecutando, y luego recarga la app.\",\n  },\n  demoPodcastListScreen: {\n    title: \"Episodios de React Native Radio\",\n    onlyFavorites: \"Mostrar solo favoritos\",\n    favoriteButton: \"Favorito\",\n    unfavoriteButton: \"No favorito\",\n    accessibility: {\n      cardHint:\n        \"Haz doble clic para escuchar el episodio. Haz doble clic y mantén presionado para {{action}} este episodio.\",\n      switch: \"Activa para mostrar solo favoritos\",\n      favoriteAction: \"Cambiar a favorito\",\n      favoriteIcon: \"Episodio no favorito\",\n      unfavoriteIcon: \"Episodio favorito\",\n      publishLabel: \"Publicado el {{date}}\",\n      durationLabel: \"Duración: {{hours}} horas {{minutes}} minutos {{seconds}} segundos\",\n    },\n    noFavoritesEmptyState: {\n      heading: \"Esto está un poco vacío\",\n      content:\n        \"No se han agregado episodios favoritos todavía. ¡Presiona el corazón dentro de un episodio para agregarlo a tus favoritos!\",\n    },\n  },\n  // @demo remove-block-start\n  ...demoEs,\n  // @demo remove-block-end\n}\n\nexport default es\n"
  },
  {
    "path": "boilerplate/app/i18n/fr.ts",
    "content": "import demoFr from \"./demo-fr\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst fr: Translations = {\n  common: {\n    ok: \"OK !\",\n    cancel: \"Annuler\",\n    back: \"Retour\",\n    logOut: \"Déconnexion\", // @demo remove-current-line\n  },\n  welcomeScreen: {\n    postscript:\n      \"psst  — Ce n'est probablement pas à quoi ressemble votre application. (À moins que votre designer ne vous ait donné ces écrans, dans ce cas, mettez la en prod !)\",\n    readyForLaunch: \"Votre application, presque prête pour le lancement !\",\n    exciting: \"(ohh, c'est excitant !)\",\n    letsGo: \"Allons-y !\", // @demo remove-current-line\n  },\n  errorScreen: {\n    title: \"Quelque chose s'est mal passé !\",\n    friendlySubtitle:\n      \"C'est l'écran que vos utilisateurs verront en production lorsqu'une erreur sera lancée. Vous voudrez personnaliser ce message (situé dans `app/i18n/fr.ts`) et probablement aussi la mise en page (`app/screens/ErrorScreen`). Si vous voulez le supprimer complètement, vérifiez `app/app.tsx` pour le composant <ErrorBoundary>.\",\n    reset: \"RÉINITIALISER L'APPLICATION\",\n    traceTitle: \"Erreur depuis %{name}\", // @demo remove-current-line\n  },\n  emptyStateComponent: {\n    generic: {\n      heading: \"Si vide... si triste\",\n      content:\n        \"Aucune donnée trouvée pour le moment. Essayez de cliquer sur le bouton pour rafraîchir ou recharger l'application.\",\n      button: \"Essayons à nouveau\",\n    },\n  },\n  // @demo remove-block-start\n  errors: {\n    invalidEmail: \"Adresse e-mail invalide.\",\n  },\n  loginScreen: {\n    logIn: \"Se connecter\",\n    enterDetails:\n      \"Entrez vos informations ci-dessous pour débloquer des informations top secrètes. Vous ne devinerez jamais ce que nous avons en attente. Ou peut-être que vous le ferez ; ce n'est pas de la science spatiale ici.\",\n    emailFieldLabel: \"E-mail\",\n    passwordFieldLabel: \"Mot de passe\",\n    emailFieldPlaceholder: \"Entrez votre adresse e-mail\",\n    passwordFieldPlaceholder: \"Mot de passe super secret ici\",\n    tapToLogIn: \"Appuyez pour vous connecter!\",\n    hint: \"Astuce : vous pouvez utiliser n'importe quelle adresse e-mail et votre mot de passe préféré :)\",\n  },\n  demoNavigator: {\n    componentsTab: \"Composants\",\n    debugTab: \"Débogage\",\n    communityTab: \"Communauté\",\n    podcastListTab: \"Podcasts\",\n  },\n  demoCommunityScreen: {\n    title: \"Connectez-vous avec la communauté\",\n    tagLine:\n      \"Rejoignez la communauté d'ingénieurs React Native d'Infinite Red et améliorez votre développement d'applications avec nous !\",\n    joinUsOnSlackTitle: \"Rejoignez-nous sur Slack\",\n    joinUsOnSlack:\n      \"Vous souhaitez vous connecter avec des ingénieurs React Native du monde entier ? Rejoignez la conversation dans la communauté Slack d'Infinite Red ! Notre communauté en pleine croissance est un espace sûr pour poser des questions, apprendre des autres et développer votre réseau.\",\n    joinSlackLink: \"Rejoindre la communauté Slack\",\n    makeIgniteEvenBetterTitle: \"Rendre Ignite encore meilleur\",\n    makeIgniteEvenBetter:\n      \"Vous avez une idée pour rendre Ignite encore meilleur ? Nous sommes heureux de l'entendre ! Nous cherchons toujours des personnes qui veulent nous aider à construire les meilleurs outils React Native. Rejoignez-nous sur GitHub pour nous aider à construire l'avenir d'Ignite.\",\n    contributeToIgniteLink: \"Contribuer à Ignite\",\n    theLatestInReactNativeTitle: \"Les dernières nouvelles de React Native\",\n    theLatestInReactNative:\n      \"Nous sommes là pour vous tenir au courant de tout ce que React Native a à offrir.\",\n    reactNativeRadioLink: \"React Native Radio\",\n    reactNativeNewsletterLink: \"React Native Newsletter\",\n    reactNativeLiveLink: \"React Native Live\",\n    chainReactConferenceLink: \"Conférence Chain React\",\n    hireUsTitle: \"Engagez Infinite Red pour votre prochain projet\",\n    hireUs:\n      \"Que ce soit pour gérer un projet complet ou pour former des équipes à notre formation pratique, Infinite Red peut vous aider pour presque tous les projets React Native.\",\n    hireUsLink: \"Envoyez-nous un message\",\n  },\n  demoShowroomScreen: {\n    jumpStart: \"Composants pour démarrer votre projet !\",\n    lorem2Sentences:\n      \"Nulla cupidatat deserunt amet quis aliquip nostrud do adipisicing. Adipisicing excepteur elit laborum Lorem adipisicing do duis.\",\n    demoHeaderTxExample: \"Yay\",\n    demoViaTxProp: \"Via la propriété `tx`\",\n    demoViaSpecifiedTxProp: \"Via la propriété `{{prop}}Tx` spécifiée\",\n  },\n  demoDebugScreen: {\n    howTo: \"COMMENT FAIRE\",\n    title: \"Débugage\",\n    tagLine:\n      \"Félicitations, vous avez un modèle d'application React Native très avancé ici. Profitez de cette base de code !\",\n    reactotron: \"Envoyer à Reactotron\",\n    reportBugs: \"Signaler des bugs\",\n    demoList: \"Liste de démonstration\",\n    demoPodcastList: \"Liste de podcasts de démonstration\",\n    androidReactotronHint:\n      \"Si cela ne fonctionne pas, assurez-vous que l'application de bureau Reactotron est en cours d'exécution, exécutez adb reverse tcp:9090 tcp:9090 à partir de votre terminal, puis rechargez l'application.\",\n    iosReactotronHint:\n      \"Si cela ne fonctionne pas, assurez-vous que l'application de bureau Reactotron est en cours d'exécution, puis rechargez l'application.\",\n    macosReactotronHint:\n      \"Si cela ne fonctionne pas, assurez-vous que l'application de bureau Reactotron est en cours d'exécution, puis rechargez l'application.\",\n    webReactotronHint:\n      \"Si cela ne fonctionne pas, assurez-vous que l'application de bureau Reactotron est en cours d'exécution, puis rechargez l'application.\",\n    windowsReactotronHint:\n      \"Si cela ne fonctionne pas, assurez-vous que l'application de bureau Reactotron est en cours d'exécution, puis rechargez l'application.\",\n  },\n  demoPodcastListScreen: {\n    title: \"Épisodes de Radio React Native\",\n    onlyFavorites: \"Afficher uniquement les favoris\",\n    favoriteButton: \"Favori\",\n    unfavoriteButton: \"Non favori\",\n    accessibility: {\n      cardHint:\n        \"Double-cliquez pour écouter l'épisode. Double-cliquez et maintenez pour {{action}} cet épisode.\",\n      switch: \"Activez pour afficher uniquement les favoris\",\n      favoriteAction: \"Basculer en favori\",\n      favoriteIcon: \"Épisode non favori\",\n      unfavoriteIcon: \"Épisode favori\",\n      publishLabel: \"Publié le {{date}}\",\n      durationLabel: \"Durée : {{hours}} heures {{minutes}} minutes {{seconds}} secondes\",\n    },\n    noFavoritesEmptyState: {\n      heading: \"C'est un peu vide ici\",\n      content:\n        \"Aucun favori n'a été ajouté pour le moment. Appuyez sur le cœur d'un épisode pour l'ajouter à vos favoris !\",\n    },\n  },\n  // @demo remove-block-start\n  ...demoFr,\n  // @demo remove-block-end\n}\n\nexport default fr\n"
  },
  {
    "path": "boilerplate/app/i18n/hi.ts",
    "content": "import demoHi from \"./demo-hi\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst hi: Translations = {\n  common: {\n    ok: \"ठीक है!\",\n    cancel: \"रद्द करें\",\n    back: \"वापस\",\n    logOut: \"लॉग आउट\", // @demo remove-current-line\n  },\n  welcomeScreen: {\n    postscript:\n      \"psst - शायद आपका ऐप ऐसा नहीं दिखता है। (जब तक कि आपके डिजाइनर ने आपको ये स्क्रीन नहीं दी हों, और उस स्थिति में, इसे लॉन्च करें!)\",\n    readyForLaunch: \"आपका ऐप, लगभग लॉन्च के लिए तैयार है!\",\n    exciting: \"(ओह, यह रोमांचक है!)\",\n    letsGo: \"चलो चलते हैं!\", // @demo remove-current-line\n  },\n  errorScreen: {\n    title: \"कुछ गलत हो गया!\",\n    friendlySubtitle:\n      \"यह वह स्क्रीन है जो आपके उपयोगकर्ता संचालन में देखेंगे जब कोई त्रुटि होगी। आप इस संदेश को बदलना चाहेंगे (जो `app/i18n/hi.ts` में स्थित है) और शायद लेआउट भी (`app/screens/ErrorScreen`)। यदि आप इसे पूरी तरह से हटाना चाहते हैं, तो `app/app.tsx` में <ErrorBoundary> कंपोनेंट की जांच करें।\",\n    reset: \"ऐप रीसेट करें\",\n    traceTitle: \"%{name} स्टैक से त्रुटि\", // @demo remove-current-line\n  },\n  emptyStateComponent: {\n    generic: {\n      heading: \"इतना खाली... इतना उदास\",\n      content: \"अभी तक कोई डेटा नहीं मिला। रीफ्रेश करने या ऐप को पुनः लोड करने के लिए बटन दबाएं।\",\n      button: \"चलो फिर से कोशिश करते हैं\",\n    },\n  },\n  // @demo remove-block-start\n  errors: {\n    invalidEmail: \"अमान्य ईमेल पता।\",\n  },\n  loginScreen: {\n    logIn: \"लॉग इन करें\",\n    enterDetails:\n      \"सर्वश्रेष्ठ रहस्य पता करने के लिए नीचे अपना विवरण दर्ज करें। आप कभी अनुमान नहीं लगा पाएंगे कि हमारे पास क्या इंतजार कर रहा है। या शायद आप कर सकते हैं; यह रॉकेट साइंस नहीं है।\",\n    emailFieldLabel: \"ईमेल\",\n    passwordFieldLabel: \"पासवर्ड\",\n    emailFieldPlaceholder: \"अपना ईमेल पता दर्ज करें\",\n    passwordFieldPlaceholder: \"सुपर सीक्रेट पासवर्ड यहाँ\",\n    tapToLogIn: \"लॉग इन करने के लिए टैप करें!\",\n    hint: \"संकेत: आप किसी भी ईमेल पते और अपने पसंदीदा पासवर्ड का उपयोग कर सकते हैं :)\",\n  },\n  demoNavigator: {\n    componentsTab: \"कंपोनेंट्स\",\n    debugTab: \"डीबग\",\n    communityTab: \"समुदाय\",\n    podcastListTab: \"पॉडकास्ट\",\n  },\n  demoCommunityScreen: {\n    title: \"समुदाय से जुड़ें\",\n    tagLine:\n      \"Infinite Red के React Native इंजीनियरों के समुदाय से जुड़ें और हमारे साथ अपने ऐप विकास को बेहतर बनाएं!\",\n    joinUsOnSlackTitle: \"Slack पर हमसे जुड़ें\",\n    joinUsOnSlack:\n      \"क्या आप चाहते हैं कि दुनिया भर के React Native इंजीनियरों से जुड़ने के लिए कोई जगह हो? Infinite Red Community Slack में बातचीत में शामिल हों! हमारा बढ़ता हुआ समुदाय प्रश्न पूछने, दूसरों से सीखने और अपने नेटवर्क को बढ़ाने के लिए एक सुरक्षित स्थान है।\",\n    joinSlackLink: \"Slack समुदाय में शामिल हों\",\n    makeIgniteEvenBetterTitle: \"Ignite को और बेहतर बनाएं\",\n    makeIgniteEvenBetter:\n      \"Ignite को और बेहतर बनाने का कोई विचार है? हमें यह सुनकर खुशी होगी! हम हमेशा ऐसे लोगों की तलाश में रहते हैं जो हमें सर्वश्रेष्ठ React Native टूलिंग बनाने में मदद करना चाहते हैं। Ignite के भविष्य को बनाने में हमारे साथ शामिल होने के लिए GitHub पर हमसे जुड़ें।\",\n    contributeToIgniteLink: \"Ignite में योगदान दें\",\n    theLatestInReactNativeTitle: \"React Native में नवीनतम\",\n    theLatestInReactNative: \"हम आपको React Native के सभी प्रस्तावों पर अपडेट रखने के लिए यहां हैं।\",\n    reactNativeRadioLink: \"React Native रेडियो\",\n    reactNativeNewsletterLink: \"React Native न्यूजलेटर\",\n    reactNativeLiveLink: \"React Native लाइव\",\n    chainReactConferenceLink: \"Chain React कॉन्फ्रेंस\",\n    hireUsTitle: \"अपने अगले प्रोजेक्ट के लिए Infinite Red को काम पर रखें\",\n    hireUs:\n      \"चाहे वह एक पूरा प्रोजेक्ट चलाना हो या हमारे हैंड्स-ऑन प्रशिक्षण के साथ टीमों को गति देना हो, Infinite Red लगभग किसी भी React Native प्रोजेक्ट में मदद कर सकता है।\",\n    hireUsLink: \"हमें एक संदेश भेजें\",\n  },\n  demoShowroomScreen: {\n    jumpStart: \"अपने प्रोजेक्ट को जंप स्टार्ट करने के लिए कंपोनेंट्स!\",\n    lorem2Sentences:\n      \"कोई भी काम जो आप नहीं करना चाहते, उसे करने के लिए किसी और को ढूंढना चाहिए। जो लोग दूसरों की मदद करते हैं, वे खुद की भी मदद करते हैं।\",\n    demoHeaderTxExample: \"हाँ\",\n    demoViaTxProp: \"`tx` प्रॉप के माध्यम से\",\n    demoViaSpecifiedTxProp: \"`{{prop}}Tx` प्रॉप के माध्यम से\",\n  },\n  demoDebugScreen: {\n    howTo: \"कैसे करें\",\n    title: \"डीबग\",\n    tagLine:\n      \"बधाई हो, आपके पास यहां एक बहुत उन्नत React Native ऐप टेम्पलेट है। इस बॉयलरप्लेट का लाभ उठाएं!\",\n    reactotron: \"Reactotron को भेजें\",\n    reportBugs: \"बग्स की रिपोर्ट करें\",\n    demoList: \"डेमो सूची\",\n    demoPodcastList: \"डेमो पॉडकास्ट सूची\",\n    androidReactotronHint:\n      \"यदि यह काम नहीं करता है, तो सुनिश्चित करें कि Reactotron डेस्कटॉप ऐप चल रहा है, अपने टर्मिनल से adb reverse tcp:9090 tcp:9090 चलाएं, और ऐप को पुनः लोड करें।\",\n    iosReactotronHint:\n      \"यदि यह काम नहीं करता है, तो सुनिश्चित करें कि Reactotron डेस्कटॉप ऐप चल रहा है और ऐप को पुनः लोड करें।\",\n    macosReactotronHint:\n      \"यदि यह काम नहीं करता है, तो सुनिश्चित करें कि Reactotron डेस्कटॉप ऐप चल रहा है और ऐप को पुनः लोड करें।\",\n    webReactotronHint:\n      \"यदि यह काम नहीं करता है, तो सुनिश्चित करें कि Reactotron डेस्कटॉप ऐप चल रहा है और ऐप को पुनः लोड करें।\",\n    windowsReactotronHint:\n      \"यदि यह काम नहीं करता है, तो सुनिश्चित करें कि Reactotron डेस्कटॉप ऐप चल रहा है और ऐप को पुनः लोड करें।\",\n  },\n  demoPodcastListScreen: {\n    title: \"React Native रेडियो एपिसोड\",\n    onlyFavorites: \"केवल पसंदीदा दिखाएं\",\n    favoriteButton: \"पसंदीदा\",\n    unfavoriteButton: \"नापसंद\",\n    accessibility: {\n      cardHint:\n        \"एपिसोड सुनने के लिए डबल टैप करें। इस एपिसोड को {{action}} करने के लिए डबल टैप करें और होल्ड करें।\",\n      switch: \"केवल पसंदीदा दिखाने के लिए स्विच करें\",\n      favoriteAction: \"पसंदीदा टॉगल करें\",\n      favoriteIcon: \"एपिसोड पसंदीदा नहीं है\",\n      unfavoriteIcon: \"एपिसोड पसंदीदा है\",\n      publishLabel: \"{{date}} को प्रकाशित\",\n      durationLabel: \"अवधि: {{hours}} घंटे {{minutes}} मिनट {{seconds}} सेकंड\",\n    },\n    noFavoritesEmptyState: {\n      heading: \"यह थोड़ा खाली लगता है\",\n      content:\n        \"अभी तक कोई पसंदीदा नहीं जोड़ा गया है। इसे अपने पसंदीदा में जोड़ने के लिए किसी एपिसोड पर दिल पर टैप करें!\",\n    },\n  },\n  // @demo remove-block-end\n  // @demo remove-block-start\n  ...demoHi,\n  // @demo remove-block-end\n}\n\nexport default hi\n"
  },
  {
    "path": "boilerplate/app/i18n/index.ts",
    "content": "import { I18nManager } from \"react-native\"\nimport * as Localization from \"expo-localization\"\nimport i18n from \"i18next\"\nimport { initReactI18next } from \"react-i18next\"\nimport \"intl-pluralrules\"\n\n// if English isn't your default language, move Translations to the appropriate language file.\nimport ar from \"./ar\"\nimport en, { Translations } from \"./en\"\nimport es from \"./es\"\nimport fr from \"./fr\"\nimport hi from \"./hi\"\nimport ja from \"./ja\"\nimport ko from \"./ko\"\n\nconst fallbackLocale = \"en-US\"\n\nconst systemLocales = Localization.getLocales()\n\nconst resources = { ar, en, ko, es, fr, ja, hi }\nconst supportedTags = Object.keys(resources)\n\n// Checks to see if the device locale matches any of the supported locales\n// Device locale may be more specific and still match (e.g., en-US matches en)\nconst systemTagMatchesSupportedTags = (deviceTag: string) => {\n  const primaryTag = deviceTag.split(\"-\")[0]\n  return supportedTags.includes(primaryTag)\n}\n\nconst pickSupportedLocale: () => Localization.Locale | undefined = () => {\n  return systemLocales.find((locale) => systemTagMatchesSupportedTags(locale.languageTag))\n}\n\nconst locale = pickSupportedLocale()\n\nexport let isRTL = false\n\n// Need to set RTL ASAP to ensure the app is rendered correctly. Waiting for i18n to init is too late.\nif (locale?.languageTag && locale?.textDirection === \"rtl\") {\n  I18nManager.allowRTL(true)\n  isRTL = true\n} else {\n  I18nManager.allowRTL(false)\n}\n\nexport const initI18n = async () => {\n  i18n.use(initReactI18next)\n\n  await i18n.init({\n    resources,\n    lng: locale?.languageTag ?? fallbackLocale,\n    fallbackLng: fallbackLocale,\n    interpolation: {\n      escapeValue: false,\n    },\n  })\n\n  return i18n\n}\n\n/**\n * Builds up valid keypaths for translations.\n */\n\nexport type TxKeyPath = RecursiveKeyOf<Translations>\n\n// via: https://stackoverflow.com/a/65333050\ntype RecursiveKeyOf<TObj extends object> = {\n  [TKey in keyof TObj & (string | number)]: RecursiveKeyOfHandleValue<TObj[TKey], `${TKey}`, true>\n}[keyof TObj & (string | number)]\n\ntype RecursiveKeyOfInner<TObj extends object> = {\n  [TKey in keyof TObj & (string | number)]: RecursiveKeyOfHandleValue<TObj[TKey], `${TKey}`, false>\n}[keyof TObj & (string | number)]\n\ntype RecursiveKeyOfHandleValue<\n  TValue,\n  Text extends string,\n  IsFirstLevel extends boolean,\n> = TValue extends any[]\n  ? Text\n  : TValue extends object\n    ? IsFirstLevel extends true\n      ? Text | `${Text}:${RecursiveKeyOfInner<TValue>}`\n      : Text | `${Text}.${RecursiveKeyOfInner<TValue>}`\n    : Text\n"
  },
  {
    "path": "boilerplate/app/i18n/ja.ts",
    "content": "import demoJa from \"./demo-ja\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst ja: Translations = {\n  common: {\n    ok: \"OK\",\n    cancel: \"キャンセル\",\n    back: \"戻る\",\n    logOut: \"ログアウト\", // @demo remove-current-line\n  },\n  welcomeScreen: {\n    postscript:\n      \"注目！ — このアプリはお好みの見た目では無いかもしれません(デザイナーがこのスクリーンを送ってこない限りは。もしそうなら公開しちゃいましょう！)\",\n    readyForLaunch: \"このアプリはもう少しで公開できます！\",\n    exciting: \"(楽しみですね！)\",\n    letsGo: \"レッツゴー！\", // @demo remove-current-line\n  },\n  errorScreen: {\n    title: \"問題が発生しました\",\n    friendlySubtitle:\n      \"本番では、エラーが投げられた時にこのページが表示されます。もし使うならこのメッセージに変更を加えてください(`app/i18n/jp.ts`)レイアウトはこちらで変更できます(`app/screens/ErrorScreen`)。もしこのスクリーンを取り除きたい場合は、`app/app.tsx`にある<ErrorBoundary>コンポーネントをチェックしてください\",\n    reset: \"リセット\",\n    traceTitle: \"エラーのスタック: %{name}\", // @demo remove-current-line\n  },\n  emptyStateComponent: {\n    generic: {\n      heading: \"静かだ...悲しい。\",\n      content:\n        \"データが見つかりません。ボタンを押してアプリをリロード、またはリフレッシュしてください。\",\n      button: \"もう一度やってみよう\",\n    },\n  },\n  // @demo remove-block-start\n  errors: {\n    invalidEmail: \"有効なメールアドレスを入力してください.\",\n  },\n  loginScreen: {\n    logIn: \"ログイン\",\n    enterDetails:\n      \"ここにあなたの情報を入力してトップシークレットをアンロックしましょう。何が待ち構えているか予想もつかないはずです。はたまたそうでも無いかも - ロケットサイエンスほど複雑なものではありません。\",\n    emailFieldLabel: \"メールアドレス\",\n    passwordFieldLabel: \"パスワード\",\n    emailFieldPlaceholder: \"メールアドレスを入力してください\",\n    passwordFieldPlaceholder: \"パスワードを入力してください\",\n    tapToLogIn: \"タップしてログインしよう！\",\n    hint: \"ヒント: お好みのメールアドレスとパスワードを使ってください :)\",\n  },\n  demoNavigator: {\n    componentsTab: \"コンポーネント\",\n    debugTab: \"デバッグ\",\n    communityTab: \"コミュニティ\",\n    podcastListTab: \"ポッドキャスト\",\n  },\n  demoCommunityScreen: {\n    title: \"コミュニティと繋がろう\",\n    tagLine:\n      \"Infinite RedのReact Nativeエンジニアコミュニティに接続して、一緒にあなたのアプリ開発をレベルアップしましょう！\",\n    joinUsOnSlackTitle: \"私たちのSlackに参加しましょう\",\n    joinUsOnSlack:\n      \"世界中のReact Nativeエンジニアと繋がりたいを思いませんか？Infinite RedのコミュニティSlackに参加しましょう！私達のコミュニティは安全に質問ができ、お互いから学び、あなたのネットワークを広げることができます。\",\n    joinSlackLink: \"Slackコミュニティに参加する\",\n    makeIgniteEvenBetterTitle: \"Igniteをより良くする\",\n    makeIgniteEvenBetter:\n      \"Igniteをより良くする為のアイデアはありますか? そうであれば聞きたいです！ 私たちはいつでも最良のReact Nativeのツールを開発する為に助けを求めています。GitHubで私たちと一緒にIgniteの未来を作りましょう。\",\n    contributeToIgniteLink: \"Igniteにコントリビュートする\",\n    theLatestInReactNativeTitle: \"React Nativeの今\",\n    theLatestInReactNative: \"React Nativeの現在をあなたにお届けします。\",\n    reactNativeRadioLink: \"React Native Radio\",\n    reactNativeNewsletterLink: \"React Native Newsletter\",\n    reactNativeLiveLink: \"React Native Live\",\n    chainReactConferenceLink: \"Chain React Conference\",\n    hireUsTitle: \"あなたの次のプロジェクトでInfinite Redと契約する\",\n    hireUs:\n      \"それがプロジェクト全体でも、チームにトレーニングをしてあげたい時でも、Infinite RedはReact Nativeのことであればなんでもお手伝いができます。\",\n    hireUsLink: \"メッセージを送る\",\n  },\n  demoShowroomScreen: {\n    jumpStart: \"あなたのプロジェクトをスタートさせるコンポーネントです！\",\n    lorem2Sentences:\n      \"Nulla cupidatat deserunt amet quis aliquip nostrud do adipisicing. Adipisicing excepteur elit laborum Lorem adipisicing do duis.\",\n    demoHeaderTxExample: \"Yay\",\n    demoViaTxProp: \"`tx`から\",\n    demoViaSpecifiedTxProp: \"`{{prop}}Tx`から\",\n  },\n  demoDebugScreen: {\n    howTo: \"ハウツー\",\n    title: \"デバッグ\",\n    tagLine:\n      \"おめでとうございます、あなたはとてもハイレベルなReact Nativeのテンプレートを使ってます。このボイラープレートを活用してください！\",\n    reactotron: \"Reactotronに送る\",\n    reportBugs: \"バグをレポートする\",\n    demoList: \"デモリスト\",\n    demoPodcastList: \"デモのポッドキャストリスト\",\n    androidReactotronHint:\n      \"もし動かなければ、Reactotronのデスクトップアプリが実行されていることを確認して, このコマンドをターミナルで実行した後、アプリをアプリをリロードしてください。 adb reverse tcp:9090 tcp:9090\",\n    iosReactotronHint:\n      \"もし動かなければ、Reactotronのデスクトップアプリが実行されていることを確認して、アプリをリロードしてください。\",\n    macosReactotronHint:\n      \"もし動かなければ、Reactotronのデスクトップアプリが実行されていることを確認して、アプリをリロードしてください。\",\n    webReactotronHint:\n      \"もし動かなければ、Reactotronのデスクトップアプリが実行されていることを確認して、アプリをリロードしてください。\",\n    windowsReactotronHint:\n      \"もし動かなければ、Reactotronのデスクトップアプリが実行されていることを確認して、アプリをリロードしてください。\",\n  },\n  demoPodcastListScreen: {\n    title: \"React Native Radioのエピソード\",\n    onlyFavorites: \"お気に入り表示\",\n    favoriteButton: \"お気に入り\",\n    unfavoriteButton: \"お気に入りを外す\",\n    accessibility: {\n      cardHint: \"ダブルタップで再生します。 ダブルタップと長押しで {{action}}\",\n      switch: \"スイッチオンでお気に入りを表示する\",\n      favoriteAction: \"お気に入りの切り替え\",\n      favoriteIcon: \"お気に入りのエピソードではありません\",\n      unfavoriteIcon: \"お気に入りのエピソードです\",\n      publishLabel: \"公開日 {{date}}\",\n      durationLabel: \"再生時間: {{hours}} 時間 {{minutes}} 分 {{seconds}} 秒\",\n    },\n    noFavoritesEmptyState: {\n      heading: \"どうやら空っぽのようですね\",\n      content:\n        \"お気に入りのエピソードがまだありません。エピソードにあるハートマークにタップして、お気に入りに追加しましょう！\",\n    },\n  },\n  // @demo remove-block-start\n  ...demoJa,\n  // @demo remove-block-end\n}\n\nexport default ja\n"
  },
  {
    "path": "boilerplate/app/i18n/ko.ts",
    "content": "import demoKo from \"./demo-ko\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst ko: Translations = {\n  common: {\n    ok: \"확인!\",\n    cancel: \"취소\",\n    back: \"뒤로\",\n    logOut: \"로그아웃\", // @demo remove-current-line\n  },\n  welcomeScreen: {\n    postscript:\n      \"잠깐! — 지금 보시는 것은 아마도 당신의 앱의 모양새가 아닐겁니다. (디자이너분이 이렇게 건내주셨다면 모를까요. 만약에 그렇다면, 이대로 가져갑시다!) \",\n    readyForLaunch: \"출시 준비가 거의 끝난 나만의 앱!\",\n    exciting: \"(오, 이거 신나는데요!)\",\n    letsGo: \"가보자구요!\", // @demo remove-current-line\n  },\n  errorScreen: {\n    title: \"뭔가 잘못되었습니다!\",\n    friendlySubtitle:\n      \"이 화면은 오류가 발생할 때 프로덕션에서 사용자에게 표시됩니다. 이 메시지를 커스터마이징 할 수 있고(해당 파일은 `app/i18n/ko.ts` 에 있습니다) 레이아웃도 마찬가지로 수정할 수 있습니다(`app/screens/error`). 만약 이 오류화면을 완전히 없에버리고 싶다면 `app/app.tsx` 파일에서 <ErrorBoundary> 컴포넌트를 확인하기 바랍니다.\",\n    reset: \"초기화\",\n    traceTitle: \"%{name} 스택에서의 오류\", // @demo remove-current-line\n  },\n  emptyStateComponent: {\n    generic: {\n      heading: \"너무 텅 비어서.. 너무 슬퍼요..\",\n      content: \"데이터가 없습니다. 버튼을 눌러서 리프레쉬 하시거나 앱을 리로드하세요.\",\n      button: \"다시 시도해봅시다\",\n    },\n  },\n  // @demo remove-block-start\n  errors: {\n    invalidEmail: \"잘못된 이메일 주소 입니다.\",\n  },\n  loginScreen: {\n    logIn: \"로그인\",\n    enterDetails:\n      \"일급비밀 정보를 해제하기 위해 상세 정보를 입력하세요. 무엇이 기다리고 있는지 절대 모를겁니다. 혹은 알 수 있을지도 모르겠군요. 엄청 복잡한 뭔가는 아닙니다.\",\n    emailFieldLabel: \"이메일\",\n    passwordFieldLabel: \"비밀번호\",\n    emailFieldPlaceholder: \"이메일을 입력하세요\",\n    passwordFieldPlaceholder: \"엄청 비밀스러운 암호를 입력하세요\",\n    tapToLogIn: \"눌러서 로그인 하기!\",\n    hint: \"힌트: 가장 좋아하는 암호와 아무런 아무 이메일 주소나 사용할 수 있어요 :)\",\n  },\n  demoNavigator: {\n    componentsTab: \"컴포넌트\",\n    debugTab: \"디버그\",\n    communityTab: \"커뮤니티\",\n    podcastListTab: \"팟캐스트\",\n  },\n  demoCommunityScreen: {\n    title: \"커뮤니티와 함께해요\",\n    tagLine:\n      \"전문적인 React Native 엔지니어들로 구성된 Infinite Red 커뮤니티에 접속해서 함께 개발 실력을 향상시켜 보세요!\",\n    joinUsOnSlackTitle: \"Slack 에 참여하세요\",\n    joinUsOnSlack:\n      \"전 세계 React Native 엔지니어들과 함께할 수 있는 곳이 있었으면 좋겠죠? Infinite Red Community Slack 에서 대화에 참여하세요! 우리의 성장하는 커뮤니티는 질문을 던지고, 다른 사람들로부터 배우고, 네트워크를 확장할 수 있는 안전한 공간입니다. \",\n    joinSlackLink: \"Slack 에 참여하기\",\n    makeIgniteEvenBetterTitle: \"Ignite 을 향상시켜요\",\n    makeIgniteEvenBetter:\n      \"Ignite 을 더 좋게 만들 아이디어가 있나요? 기쁜 소식이네요. 우리는 항상 최고의 React Native 도구를 구축하는데 도움을 줄 수 있는 분들을 찾고 있습니다. GitHub 에서 Ignite 의 미래를 만들어 가는것에 함께해 주세요.\",\n    contributeToIgniteLink: \"Ignite 에 기여하기\",\n    theLatestInReactNativeTitle: \"React Native 의 최신정보\",\n    theLatestInReactNative: \"React Native 가 제공하는 모든 최신 정보를 알려드립니다.\",\n    reactNativeRadioLink: \"React Native 라디오\",\n    reactNativeNewsletterLink: \"React Native 뉴스레터\",\n    reactNativeLiveLink: \"React Native 라이브 스트리밍\",\n    chainReactConferenceLink: \"Chain React 컨퍼런스\",\n    hireUsTitle: \"다음 프로젝트에 Infinite Red 를 고용하세요\",\n    hireUs:\n      \"프로젝트 전체를 수행하든, 실무 교육을 통해 팀의 개발 속도에 박차를 가하든 상관없이, Infinite Red 는 React Native 프로젝트의 모든 분야의 에서 도움을 드릴 수 있습니다.\",\n    hireUsLink: \"메세지 보내기\",\n  },\n  demoShowroomScreen: {\n    jumpStart: \"프로젝트를 바로 시작할 수 있는 컴포넌트들!\",\n    lorem2Sentences:\n      \"별 하나에 추억과, 별 하나에 사랑과, 별 하나에 쓸쓸함과, 별 하나에 동경(憧憬)과, 별 하나에 시와, 별 하나에 어머니, 어머니\",\n    demoHeaderTxExample: \"야호\",\n    demoViaTxProp: \"`tx` Prop 을 통해\",\n    demoViaSpecifiedTxProp: \"`{{prop}}Tx` Prop 을 통해\",\n  },\n  demoDebugScreen: {\n    howTo: \"사용방법\",\n    title: \"디버그\",\n    tagLine:\n      \"축하합니다. 여기 아주 고급스러운 React Native 앱 템플릿이 있습니다. 이 보일러 플레이트를 사용해보세요!\",\n    reactotron: \"Reactotron 으로 보내기\",\n    reportBugs: \"버그 보고하기\",\n    demoList: \"데모 목록\",\n    demoPodcastList: \"데모 팟캐스트 목록\",\n    androidReactotronHint:\n      \"만약에 동작하지 않는 경우, Reactotron 데스크탑 앱이 실행중인지 확인 후, 터미널에서 adb reverse tcp:9090 tcp:9090 을 실행한 다음 앱을 다시 실행해보세요.\",\n    iosReactotronHint:\n      \"만약에 동작하지 않는 경우, Reactotron 데스크탑 앱이 실행중인지 확인 후 앱을 다시 실행해보세요.\",\n    macosReactotronHint:\n      \"만약에 동작하지 않는 경우, Reactotron 데스크탑 앱이 실행중인지 확인 후 앱을 다시 실행해보세요.\",\n    webReactotronHint:\n      \"만약에 동작하지 않는 경우, Reactotron 데스크탑 앱이 실행중인지 확인 후 앱을 다시 실행해보세요.\",\n    windowsReactotronHint:\n      \"만약에 동작하지 않는 경우, Reactotron 데스크탑 앱이 실행중인지 확인 후 앱을 다시 실행해보세요.\",\n  },\n  demoPodcastListScreen: {\n    title: \"React Native 라디오 에피소드\",\n    onlyFavorites: \"즐겨찾기만 보기\",\n    favoriteButton: \"즐겨찾기\",\n    unfavoriteButton: \"즐겨찾기 해제\",\n    accessibility: {\n      cardHint:\n        \"에피소드를 들으려면 두 번 탭하세요. 이 에피소드를 좋아하거나 싫어하려면 두 번 탭하고 길게 누르세요.\",\n      switch: \"즐겨찾기를 사용하려면 스위치를 사용하세요.\",\n      favoriteAction: \"즐겨찾기 토글\",\n      favoriteIcon: \"좋아하는 에피소드\",\n      unfavoriteIcon: \"즐겨찾기하지 않은 에피소드\",\n      publishLabel: \"{{date}} 에 발행됨\",\n      durationLabel: \"소요시간: {{hours}}시간 {{minutes}}분 {{seconds}}초\",\n    },\n    noFavoritesEmptyState: {\n      heading: \"조금 텅 비어 있네요.\",\n      content: \"즐겨찾기가 없습니다. 에피소드에 있는 하트를 눌러서 즐겨찾기에 추가하세요.\",\n    },\n  },\n  // @demo remove-block-start\n  ...demoKo,\n  // @demo remove-block-end\n}\n\nexport default ko\n"
  },
  {
    "path": "boilerplate/app/i18n/translate.ts",
    "content": "import i18n from \"i18next\"\nimport type { TOptions } from \"i18next\"\n\nimport { TxKeyPath } from \".\"\n\n/**\n * Translates text.\n * @param {TxKeyPath} key - The i18n key.\n * @param {TOptions} options - The i18n options.\n * @returns {string} - The translated text.\n * @example\n * Translations:\n *\n * ```en.ts\n * {\n *  \"hello\": \"Hello, {{name}}!\"\n * }\n * ```\n *\n * Usage:\n * ```ts\n * import { translate } from \"./i18n\"\n *\n * translate(\"hello\", { name: \"world\" })\n * // => \"Hello world!\"\n * ```\n */\nexport function translate(key: TxKeyPath, options?: TOptions): string {\n  if (i18n.isInitialized) {\n    return i18n.t(key, options)\n  }\n  return key\n}\n"
  },
  {
    "path": "boilerplate/app/navigators/AppNavigator.tsx",
    "content": "/**\n * The app navigator (formerly \"AppNavigator\" and \"MainNavigator\") is used for the primary\n * navigation flows of your app.\n * Generally speaking, it will contain an auth flow (registration, login, forgot password)\n * and a \"main\" flow which the user will use once logged in.\n */\nimport { NavigationContainer } from \"@react-navigation/native\"\nimport { createNativeStackNavigator } from \"@react-navigation/native-stack\"\n\nimport Config from \"@/config\"\nimport { useAuth } from \"@/context/AuthContext\" // @demo remove-current-line\nimport { ErrorBoundary } from \"@/screens/ErrorScreen/ErrorBoundary\"\nimport { LoginScreen } from \"@/screens/LoginScreen\" // @demo remove-current-line\nimport { WelcomeScreen } from \"@/screens/WelcomeScreen\"\nimport { useAppTheme } from \"@/theme/context\"\n\nimport { DemoNavigator } from \"./DemoNavigator\" // @demo remove-current-line\nimport { navigationRef, useBackButtonHandler } from \"./navigationUtilities\"\nimport type { AppStackParamList, NavigationProps } from \"./navigationTypes\"\n\n/**\n * This is a list of all the route names that will exit the app if the back button\n * is pressed while in that screen. Only affects Android.\n */\nconst exitRoutes = Config.exitRoutes\n\n// Documentation: https://reactnavigation.org/docs/stack-navigator/\nconst Stack = createNativeStackNavigator<AppStackParamList>()\n\nconst AppStack = () => {\n  // @demo remove-block-start\n  const { isAuthenticated } = useAuth()\n  // @demo remove-block-end\n  const {\n    theme: { colors },\n  } = useAppTheme()\n\n  return (\n    <Stack.Navigator\n      screenOptions={{\n        headerShown: false,\n        navigationBarColor: colors.background,\n        contentStyle: {\n          backgroundColor: colors.background,\n        },\n      }}\n      initialRouteName={isAuthenticated ? \"Welcome\" : \"Login\"} // @demo remove-current-line\n    >\n      {/* @demo remove-block-start */}\n      {isAuthenticated ? (\n        <>\n          {/* @demo remove-block-end */}\n          <Stack.Screen name=\"Welcome\" component={WelcomeScreen} />\n          {/* @demo remove-block-start */}\n          <Stack.Screen name=\"Demo\" component={DemoNavigator} />\n        </>\n      ) : (\n        <>\n          <Stack.Screen name=\"Login\" component={LoginScreen} />\n        </>\n      )}\n      {/* @demo remove-block-end */}\n      {/** 🔥 Your screens go here */}\n      {/* IGNITE_GENERATOR_ANCHOR_APP_STACK_SCREENS */}\n    </Stack.Navigator>\n  )\n}\n\nexport const AppNavigator = (props: NavigationProps) => {\n  const { navigationTheme } = useAppTheme()\n\n  useBackButtonHandler((routeName) => exitRoutes.includes(routeName))\n\n  return (\n    <NavigationContainer ref={navigationRef} theme={navigationTheme} {...props}>\n      <ErrorBoundary catchErrors={Config.catchErrors}>\n        <AppStack />\n      </ErrorBoundary>\n    </NavigationContainer>\n  )\n}\n"
  },
  {
    "path": "boilerplate/app/navigators/DemoNavigator.tsx",
    "content": "import { TextStyle, ViewStyle } from \"react-native\"\nimport { createBottomTabNavigator } from \"@react-navigation/bottom-tabs\"\nimport { useSafeAreaInsets } from \"react-native-safe-area-context\"\n\nimport { Icon } from \"@/components/Icon\"\nimport { EpisodeProvider } from \"@/context/EpisodeContext\"\nimport { translate } from \"@/i18n/translate\"\nimport { DemoCommunityScreen } from \"@/screens/DemoCommunityScreen\"\nimport { DemoDebugScreen } from \"@/screens/DemoDebugScreen\"\nimport { DemoPodcastListScreen } from \"@/screens/DemoPodcastListScreen\"\nimport { DemoShowroomScreen } from \"@/screens/DemoShowroomScreen/DemoShowroomScreen\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\n\nimport type { DemoTabParamList } from \"./navigationTypes\"\n\nconst Tab = createBottomTabNavigator<DemoTabParamList>()\n\n/**\n * This is the main navigator for the demo screens with a bottom tab bar.\n * Each tab is a stack navigator with its own set of screens.\n *\n * More info: https://reactnavigation.org/docs/bottom-tab-navigator/\n * @returns {JSX.Element} The rendered `DemoNavigator`.\n */\nexport function DemoNavigator() {\n  const { bottom } = useSafeAreaInsets()\n  const {\n    themed,\n    theme: { colors },\n  } = useAppTheme()\n\n  return (\n    <EpisodeProvider>\n      <Tab.Navigator\n        screenOptions={{\n          headerShown: false,\n          tabBarHideOnKeyboard: true,\n          tabBarStyle: themed([$tabBar, { height: bottom + 70 }]),\n          tabBarActiveTintColor: colors.text,\n          tabBarInactiveTintColor: colors.text,\n          tabBarLabelStyle: themed($tabBarLabel),\n          tabBarItemStyle: themed($tabBarItem),\n        }}\n      >\n        <Tab.Screen\n          name=\"DemoShowroom\"\n          component={DemoShowroomScreen}\n          options={{\n            tabBarLabel: translate(\"demoNavigator:componentsTab\"),\n            tabBarIcon: ({ focused }) => (\n              <Icon\n                icon=\"components\"\n                color={focused ? colors.tint : colors.tintInactive}\n                size={30}\n              />\n            ),\n          }}\n        />\n\n        <Tab.Screen\n          name=\"DemoCommunity\"\n          component={DemoCommunityScreen}\n          options={{\n            tabBarLabel: translate(\"demoNavigator:communityTab\"),\n            tabBarIcon: ({ focused }) => (\n              <Icon\n                icon=\"community\"\n                color={focused ? colors.tint : colors.tintInactive}\n                size={30}\n              />\n            ),\n          }}\n        />\n\n        <Tab.Screen\n          name=\"DemoPodcastList\"\n          component={DemoPodcastListScreen}\n          options={{\n            tabBarAccessibilityLabel: translate(\"demoNavigator:podcastListTab\"),\n            tabBarLabel: translate(\"demoNavigator:podcastListTab\"),\n            tabBarIcon: ({ focused }) => (\n              <Icon icon=\"podcast\" color={focused ? colors.tint : colors.tintInactive} size={30} />\n            ),\n          }}\n        />\n\n        <Tab.Screen\n          name=\"DemoDebug\"\n          component={DemoDebugScreen}\n          options={{\n            tabBarLabel: translate(\"demoNavigator:debugTab\"),\n            tabBarIcon: ({ focused }) => (\n              <Icon icon=\"debug\" color={focused ? colors.tint : colors.tintInactive} size={30} />\n            ),\n          }}\n        />\n      </Tab.Navigator>\n    </EpisodeProvider>\n  )\n}\n\nconst $tabBar: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.background,\n  borderTopColor: colors.transparent,\n})\n\nconst $tabBarItem: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  paddingTop: spacing.md,\n})\n\nconst $tabBarLabel: ThemedStyle<TextStyle> = ({ colors, typography }) => ({\n  fontSize: 12,\n  fontFamily: typography.primary.medium,\n  lineHeight: 16,\n  color: colors.text,\n})\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/navigators/navigationTypes.ts",
    "content": "import { ComponentProps } from \"react\"\nimport { BottomTabScreenProps } from \"@react-navigation/bottom-tabs\"\nimport {\n  CompositeScreenProps,\n  NavigationContainer,\n  NavigatorScreenParams,\n} from \"@react-navigation/native\"\nimport { NativeStackScreenProps } from \"@react-navigation/native-stack\"\n\n// Demo Tab Navigator types\nexport type DemoTabParamList = {\n  DemoCommunity: undefined\n  DemoShowroom: { queryIndex?: string; itemIndex?: string }\n  DemoDebug: undefined\n  DemoPodcastList: undefined\n}\n\n// App Stack Navigator types\nexport type AppStackParamList = {\n  Welcome: undefined\n  Login: undefined\n  Demo: NavigatorScreenParams<DemoTabParamList>\n  // 🔥 Your screens go here\n  // IGNITE_GENERATOR_ANCHOR_APP_STACK_PARAM_LIST\n}\n\nexport type AppStackScreenProps<T extends keyof AppStackParamList> = NativeStackScreenProps<\n  AppStackParamList,\n  T\n>\n\nexport type DemoTabScreenProps<T extends keyof DemoTabParamList> = CompositeScreenProps<\n  BottomTabScreenProps<DemoTabParamList, T>,\n  AppStackScreenProps<keyof AppStackParamList>\n>\n\nexport interface NavigationProps extends Partial<\n  ComponentProps<typeof NavigationContainer<AppStackParamList>>\n> {}\n"
  },
  {
    "path": "boilerplate/app/navigators/navigationUtilities.ts",
    "content": "import { useState, useEffect, useRef } from \"react\"\nimport { BackHandler, Linking, Platform } from \"react-native\"\nimport {\n  NavigationState,\n  PartialState,\n  createNavigationContainerRef,\n} from \"@react-navigation/native\"\n\nimport Config from \"@/config\"\nimport type { PersistNavigationConfig } from \"@/config/config.base\"\nimport * as storage from \"@/utils/storage\"\nimport { useIsMounted } from \"@/utils/useIsMounted\"\n\nimport type { AppStackParamList, NavigationProps } from \"./navigationTypes\"\n\ntype Storage = typeof storage\n\n/**\n * Reference to the root App Navigator.\n *\n * If needed, you can use this to access the navigation object outside of a\n * `NavigationContainer` context. However, it's recommended to use the `useNavigation` hook whenever possible.\n * @see [Navigating Without Navigation Prop]{@link https://reactnavigation.org/docs/navigating-without-navigation-prop/}\n *\n * The types on this reference will only let you reference top level navigators. If you have\n * nested navigators, you'll need to use the `useNavigation` with the stack navigator's ParamList type.\n */\nexport const navigationRef = createNavigationContainerRef<AppStackParamList>()\n\n/**\n * Gets the current screen from any navigation state.\n * @param {NavigationState | PartialState<NavigationState>} state - The navigation state to traverse.\n * @returns {string} - The name of the current screen.\n */\nexport function getActiveRouteName(state: NavigationState | PartialState<NavigationState>): string {\n  const route = state.routes[state.index ?? 0]\n\n  // Found the active route -- return the name\n  if (!route.state) return route.name as keyof AppStackParamList\n\n  // Recursive call to deal with nested routers\n  return getActiveRouteName(route.state as NavigationState<AppStackParamList>)\n}\n\nconst iosExit = () => false\n\n/**\n * Hook that handles Android back button presses and forwards those on to\n * the navigation or allows exiting the app.\n * @see [BackHandler]{@link https://reactnative.dev/docs/backhandler}\n * @param {(routeName: string) => boolean} canExit - Function that returns whether we can exit the app.\n * @returns {void}\n */\nexport function useBackButtonHandler(canExit: (routeName: string) => boolean) {\n  // The reason we're using a ref here is because we need to be able\n  // to update the canExit function without re-setting up all the listeners\n  const canExitRef = useRef(Platform.OS !== \"android\" ? iosExit : canExit)\n\n  useEffect(() => {\n    canExitRef.current = canExit\n  }, [canExit])\n\n  useEffect(() => {\n    // We'll fire this when the back button is pressed on Android.\n    const onBackPress = () => {\n      if (!navigationRef.isReady()) {\n        return false\n      }\n\n      // grab the current route\n      const routeName = getActiveRouteName(navigationRef.getRootState())\n\n      // are we allowed to exit?\n      if (canExitRef.current(routeName)) {\n        // exit and let the system know we've handled the event\n        BackHandler.exitApp()\n        return true\n      }\n\n      // we can't exit, so let's turn this into a back action\n      if (navigationRef.canGoBack()) {\n        navigationRef.goBack()\n        return true\n      }\n\n      return false\n    }\n\n    // Subscribe when we come to life\n    const subscription = BackHandler.addEventListener(\"hardwareBackPress\", onBackPress)\n\n    // Unsubscribe when we're done\n    return () => subscription.remove()\n  }, [])\n}\n\n/**\n * This helper function will determine whether we should enable navigation persistence\n * based on a config setting and the __DEV__ environment (dev or prod).\n * @param {PersistNavigationConfig} persistNavigation - The config setting for navigation persistence.\n * @returns {boolean} - Whether to restore navigation state by default.\n */\nfunction navigationRestoredDefaultState(persistNavigation: PersistNavigationConfig) {\n  if (persistNavigation === \"always\") return false\n  if (persistNavigation === \"dev\" && __DEV__) return false\n  if (persistNavigation === \"prod\" && !__DEV__) return false\n\n  // all other cases, disable restoration by returning true\n  return true\n}\n\n/**\n * Custom hook for persisting navigation state.\n * @param {Storage} storage - The storage utility to use.\n * @param {string} persistenceKey - The key to use for storing the navigation state.\n * @returns {object} - The navigation state and persistence functions.\n */\nexport function useNavigationPersistence(storage: Storage, persistenceKey: string) {\n  const [initialNavigationState, setInitialNavigationState] =\n    useState<NavigationProps[\"initialState\"]>()\n  const isMounted = useIsMounted()\n\n  const initNavState = navigationRestoredDefaultState(Config.persistNavigation)\n  const [isRestored, setIsRestored] = useState(initNavState)\n\n  const routeNameRef = useRef<keyof AppStackParamList | undefined>(undefined)\n\n  const onNavigationStateChange = (state: NavigationState | undefined) => {\n    const previousRouteName = routeNameRef.current\n    if (state !== undefined) {\n      const currentRouteName = getActiveRouteName(state)\n\n      if (previousRouteName !== currentRouteName) {\n        // track screens.\n        if (__DEV__) {\n          console.log(currentRouteName)\n        }\n      }\n\n      // Save the current route name for later comparison\n      routeNameRef.current = currentRouteName as keyof AppStackParamList\n\n      // Persist state to storage\n      storage.save(persistenceKey, state)\n    }\n  }\n\n  const restoreState = async () => {\n    try {\n      const initialUrl = await Linking.getInitialURL()\n\n      // Only restore the state if app has not started from a deep link\n      if (!initialUrl) {\n        const state = (await storage.load(persistenceKey)) as NavigationProps[\"initialState\"] | null\n        if (state) setInitialNavigationState(state)\n      }\n    } finally {\n      if (isMounted()) setIsRestored(true)\n    }\n  }\n\n  useEffect(() => {\n    if (!isRestored) restoreState()\n    // runs once on mount\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [])\n\n  return { onNavigationStateChange, restoreState, isRestored, initialNavigationState }\n}\n\n/**\n * use this to navigate without the navigation\n * prop. If you have access to the navigation prop, do not use this.\n * @see {@link https://reactnavigation.org/docs/navigating-without-navigation-prop/}\n * @param {unknown} name - The name of the route to navigate to.\n * @param {unknown} params - The params to pass to the route.\n */\nexport function navigate(name: unknown, params?: unknown) {\n  if (navigationRef.isReady()) {\n    // @ts-expect-error\n    navigationRef.navigate(name as never, params as never)\n  }\n}\n\n/**\n * This function is used to go back in a navigation stack, if it's possible to go back.\n * If the navigation stack can't go back, nothing happens.\n * The navigationRef variable is a React ref that references a navigation object.\n * The navigationRef variable is set in the App component.\n */\nexport function goBack() {\n  if (navigationRef.isReady() && navigationRef.canGoBack()) {\n    navigationRef.goBack()\n  }\n}\n\n/**\n * resetRoot will reset the root navigation state to the given params.\n * @param {Parameters<typeof navigationRef.resetRoot>[0]} state - The state to reset the root to.\n * @returns {void}\n */\nexport function resetRoot(\n  state: Parameters<typeof navigationRef.resetRoot>[0] = { index: 0, routes: [] },\n) {\n  if (navigationRef.isReady()) {\n    navigationRef.resetRoot(state)\n  }\n}\n"
  },
  {
    "path": "boilerplate/app/screens/DemoCommunityScreen.tsx",
    "content": "import { FC } from \"react\"\nimport { Image, ImageStyle, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { ListItem } from \"@/components/ListItem\"\nimport { Screen } from \"@/components/Screen\"\nimport { Text } from \"@/components/Text\"\nimport { isRTL } from \"@/i18n\"\nimport { DemoTabScreenProps } from \"@/navigators/navigationTypes\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\nimport { openLinkInBrowser } from \"@/utils/openLinkInBrowser\"\n\nconst chainReactLogo = require(\"@assets/images/demo/cr-logo.png\")\nconst reactNativeLiveLogo = require(\"@assets/images/demo/rnl-logo.png\")\nconst reactNativeNewsletterLogo = require(\"@assets/images/demo/rnn-logo.png\")\nconst reactNativeRadioLogo = require(\"@assets/images/demo/rnr-logo.png\")\n\nexport const DemoCommunityScreen: FC<DemoTabScreenProps<\"DemoCommunity\">> =\n  function DemoCommunityScreen(_props) {\n    const { themed } = useAppTheme()\n    return (\n      <Screen preset=\"scroll\" contentContainerStyle={$styles.container} safeAreaEdges={[\"top\"]}>\n        <Text preset=\"heading\" tx=\"demoCommunityScreen:title\" style={themed($title)} />\n        <Text tx=\"demoCommunityScreen:tagLine\" style={themed($tagline)} />\n\n        <Text preset=\"subheading\" tx=\"demoCommunityScreen:joinUsOnSlackTitle\" />\n        <Text tx=\"demoCommunityScreen:joinUsOnSlack\" style={themed($description)} />\n        <ListItem\n          tx=\"demoCommunityScreen:joinSlackLink\"\n          leftIcon=\"slack\"\n          rightIcon={isRTL ? \"caretLeft\" : \"caretRight\"}\n          onPress={() => openLinkInBrowser(\"https://community.infinite.red/\")}\n        />\n        <Text\n          preset=\"subheading\"\n          tx=\"demoCommunityScreen:makeIgniteEvenBetterTitle\"\n          style={themed($sectionTitle)}\n        />\n        <Text tx=\"demoCommunityScreen:makeIgniteEvenBetter\" style={themed($description)} />\n        <ListItem\n          tx=\"demoCommunityScreen:contributeToIgniteLink\"\n          leftIcon=\"github\"\n          rightIcon={isRTL ? \"caretLeft\" : \"caretRight\"}\n          onPress={() => openLinkInBrowser(\"https://github.com/infinitered/ignite\")}\n        />\n\n        <Text\n          preset=\"subheading\"\n          tx=\"demoCommunityScreen:theLatestInReactNativeTitle\"\n          style={themed($sectionTitle)}\n        />\n        <Text tx=\"demoCommunityScreen:theLatestInReactNative\" style={themed($description)} />\n        <ListItem\n          tx=\"demoCommunityScreen:reactNativeRadioLink\"\n          bottomSeparator\n          rightIcon={isRTL ? \"caretLeft\" : \"caretRight\"}\n          LeftComponent={\n            <View style={[$styles.row, themed($logoContainer)]}>\n              <Image source={reactNativeRadioLogo} style={$logo} />\n            </View>\n          }\n          onPress={() => openLinkInBrowser(\"https://reactnativeradio.com/\")}\n        />\n        <ListItem\n          tx=\"demoCommunityScreen:reactNativeNewsletterLink\"\n          bottomSeparator\n          rightIcon={isRTL ? \"caretLeft\" : \"caretRight\"}\n          LeftComponent={\n            <View style={[$styles.row, themed($logoContainer)]}>\n              <Image source={reactNativeNewsletterLogo} style={$logo} />\n            </View>\n          }\n          onPress={() => openLinkInBrowser(\"https://reactnativenewsletter.com/\")}\n        />\n        <ListItem\n          tx=\"demoCommunityScreen:reactNativeLiveLink\"\n          bottomSeparator\n          rightIcon={isRTL ? \"caretLeft\" : \"caretRight\"}\n          LeftComponent={\n            <View style={[$styles.row, themed($logoContainer)]}>\n              <Image source={reactNativeLiveLogo} style={$logo} />\n            </View>\n          }\n          onPress={() => openLinkInBrowser(\"https://rn.live/\")}\n        />\n        <ListItem\n          tx=\"demoCommunityScreen:chainReactConferenceLink\"\n          rightIcon={isRTL ? \"caretLeft\" : \"caretRight\"}\n          LeftComponent={\n            <View style={[$styles.row, themed($logoContainer)]}>\n              <Image source={chainReactLogo} style={$logo} />\n            </View>\n          }\n          onPress={() => openLinkInBrowser(\"https://cr.infinite.red/\")}\n        />\n        <Text\n          preset=\"subheading\"\n          tx=\"demoCommunityScreen:hireUsTitle\"\n          style={themed($sectionTitle)}\n        />\n        <Text tx=\"demoCommunityScreen:hireUs\" style={themed($description)} />\n        <ListItem\n          tx=\"demoCommunityScreen:hireUsLink\"\n          leftIcon=\"clap\"\n          rightIcon={isRTL ? \"caretLeft\" : \"caretRight\"}\n          onPress={() => openLinkInBrowser(\"https://infinite.red/contact\")}\n        />\n      </Screen>\n    )\n  }\n\nconst $title: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginBottom: spacing.sm,\n})\n\nconst $tagline: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginBottom: spacing.xxl,\n})\n\nconst $description: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginBottom: spacing.lg,\n})\n\nconst $sectionTitle: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginTop: spacing.xxl,\n})\n\nconst $logoContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginEnd: spacing.md,\n  flexWrap: \"wrap\",\n  alignContent: \"center\",\n  alignSelf: \"stretch\",\n})\n\nconst $logo: ImageStyle = {\n  height: 38,\n  width: 38,\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoDebugScreen.tsx",
    "content": "import { FC, useCallback, useMemo } from \"react\"\nimport {\n  LayoutAnimation,\n  Linking,\n  Platform,\n  TextStyle,\n  useColorScheme,\n  View,\n  ViewStyle,\n} from \"react-native\"\nimport * as Application from \"expo-application\"\n\nimport { Button } from \"@/components/Button\"\nimport { ListItem } from \"@/components/ListItem\"\nimport { Screen } from \"@/components/Screen\"\nimport { Text } from \"@/components/Text\"\nimport { useAuth } from \"@/context/AuthContext\"\nimport { isRTL } from \"@/i18n\"\nimport { DemoTabScreenProps } from \"@/navigators/navigationTypes\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\n\n/**\n * @param {string} url - The URL to open in the browser.\n * @returns {void} - No return value.\n */\nfunction openLinkInBrowser(url: string) {\n  Linking.canOpenURL(url).then((canOpen) => canOpen && Linking.openURL(url))\n}\n\nconst usingHermes = typeof HermesInternal === \"object\" && HermesInternal !== null\n\nexport const DemoDebugScreen: FC<DemoTabScreenProps<\"DemoDebug\">> = function DemoDebugScreen(\n  _props,\n) {\n  const { setThemeContextOverride, themeContext, themed } = useAppTheme()\n  const { logout } = useAuth()\n\n  // @ts-expect-error\n  const usingFabric = global.nativeFabricUIManager != null\n\n  const demoReactotron = useMemo(\n    () => async () => {\n      if (__DEV__) {\n        console.tron.display({\n          name: \"DISPLAY\",\n          value: {\n            appId: Application.applicationId,\n            appName: Application.applicationName,\n            appVersion: Application.nativeApplicationVersion,\n            appBuildVersion: Application.nativeBuildVersion,\n            hermesEnabled: usingHermes,\n          },\n          important: true,\n        })\n      }\n    },\n    [],\n  )\n\n  const toggleTheme = useCallback(() => {\n    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) // Animate the transition\n    setThemeContextOverride(themeContext === \"dark\" ? \"light\" : \"dark\")\n  }, [themeContext, setThemeContextOverride])\n\n  // Resets the theme to the system theme\n  const colorScheme = useColorScheme()\n  const resetTheme = useCallback(() => {\n    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)\n    setThemeContextOverride(undefined)\n  }, [setThemeContextOverride])\n\n  return (\n    <Screen\n      preset=\"scroll\"\n      safeAreaEdges={[\"top\"]}\n      contentContainerStyle={[$styles.container, themed($container)]}\n    >\n      <Text\n        style={themed($reportBugsLink)}\n        tx=\"demoDebugScreen:reportBugs\"\n        onPress={() => openLinkInBrowser(\"https://github.com/infinitered/ignite/issues\")}\n      />\n\n      <Text style={themed($title)} preset=\"heading\" tx=\"demoDebugScreen:title\" />\n      <Text preset=\"bold\">Current system theme: {colorScheme}</Text>\n      <Text preset=\"bold\">Current app theme: {themeContext}</Text>\n      <Button onPress={resetTheme} text={`Reset`} />\n\n      <View style={themed($itemsContainer)}>\n        <Button onPress={toggleTheme} text={`Toggle Theme: ${themeContext}`} />\n      </View>\n      <View style={themed($itemsContainer)}>\n        <ListItem\n          LeftComponent={\n            <View style={themed($item)}>\n              <Text preset=\"bold\">App Id</Text>\n              <Text>{Application.applicationId}</Text>\n            </View>\n          }\n        />\n        <ListItem\n          LeftComponent={\n            <View style={themed($item)}>\n              <Text preset=\"bold\">App Name</Text>\n              <Text>{Application.applicationName}</Text>\n            </View>\n          }\n        />\n        <ListItem\n          LeftComponent={\n            <View style={themed($item)}>\n              <Text preset=\"bold\">App Version</Text>\n              <Text>{Application.nativeApplicationVersion}</Text>\n            </View>\n          }\n        />\n        <ListItem\n          LeftComponent={\n            <View style={themed($item)}>\n              <Text preset=\"bold\">App Build Version</Text>\n              <Text>{Application.nativeBuildVersion}</Text>\n            </View>\n          }\n        />\n        <ListItem\n          LeftComponent={\n            <View style={themed($item)}>\n              <Text preset=\"bold\">Hermes Enabled</Text>\n              <Text>{String(usingHermes)}</Text>\n            </View>\n          }\n        />\n        <ListItem\n          LeftComponent={\n            <View style={themed($item)}>\n              <Text preset=\"bold\">Fabric Enabled</Text>\n              <Text>{String(usingFabric)}</Text>\n            </View>\n          }\n        />\n      </View>\n      <View style={themed($buttonContainer)}>\n        <Button style={themed($button)} tx=\"demoDebugScreen:reactotron\" onPress={demoReactotron} />\n        <Text style={themed($hint)} tx={`demoDebugScreen:${Platform.OS}ReactotronHint` as const} />\n      </View>\n      <View style={themed($buttonContainer)}>\n        <Button style={themed($button)} tx=\"common:logOut\" onPress={logout} />\n      </View>\n    </Screen>\n  )\n}\n\nconst $container: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  paddingBottom: spacing.xxl,\n})\n\nconst $title: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginBottom: spacing.xxl,\n})\n\nconst $reportBugsLink: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({\n  color: colors.tint,\n  marginBottom: spacing.lg,\n  alignSelf: isRTL ? \"flex-start\" : \"flex-end\",\n})\n\nconst $item: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginBottom: spacing.md,\n})\n\nconst $itemsContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginVertical: spacing.xl,\n})\n\nconst $button: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginBottom: spacing.xs,\n})\n\nconst $buttonContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginBottom: spacing.md,\n})\n\nconst $hint: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({\n  color: colors.palette.neutral600,\n  fontSize: 12,\n  lineHeight: 15,\n  paddingBottom: spacing.lg,\n})\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoPodcastListScreen.tsx",
    "content": "import { ComponentType, FC, useCallback, useEffect, useMemo, useState } from \"react\"\nimport {\n  AccessibilityProps,\n  ActivityIndicator,\n  FlatList,\n  Image,\n  ImageSourcePropType,\n  ImageStyle,\n  Platform,\n  StyleSheet,\n  TextStyle,\n  View,\n  ViewStyle,\n} from \"react-native\"\nimport Animated, {\n  Extrapolation,\n  interpolate,\n  useAnimatedStyle,\n  useSharedValue,\n  withSpring,\n} from \"react-native-reanimated\"\n\nimport { Button, type ButtonAccessoryProps } from \"@/components/Button\"\nimport { Card } from \"@/components/Card\"\nimport { EmptyState } from \"@/components/EmptyState\"\nimport { Icon } from \"@/components/Icon\"\nimport { Screen } from \"@/components/Screen\"\nimport { Text } from \"@/components/Text\"\nimport { Switch } from \"@/components/Toggle/Switch\"\nimport { useEpisodes, useEpisode } from \"@/context/EpisodeContext\"\nimport { isRTL } from \"@/i18n\"\nimport { translate } from \"@/i18n/translate\"\nimport { DemoTabScreenProps } from \"@/navigators/navigationTypes\"\nimport type { EpisodeItem } from \"@/services/api/types\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\nimport { delay } from \"@/utils/delay\"\nimport { openLinkInBrowser } from \"@/utils/openLinkInBrowser\"\n\nconst ICON_SIZE = 14\n\nconst rnrImage1 = require(\"@assets/images/demo/rnr-image-1.png\")\nconst rnrImage2 = require(\"@assets/images/demo/rnr-image-2.png\")\nconst rnrImage3 = require(\"@assets/images/demo/rnr-image-3.png\")\n\nconst rnrImages = [rnrImage1, rnrImage2, rnrImage3]\n\nexport const DemoPodcastListScreen: FC<DemoTabScreenProps<\"DemoPodcastList\">> = (_props) => {\n  const { themed } = useAppTheme()\n  const {\n    totalEpisodes,\n    totalFavorites,\n\n    episodesForList,\n    fetchEpisodes,\n    favoritesOnly,\n    toggleFavoritesOnly,\n    toggleFavorite,\n  } = useEpisodes()\n\n  const [refreshing, setRefreshing] = useState(false)\n  const [isLoading, setIsLoading] = useState(false)\n\n  // initially, kick off a background refresh without the refreshing UI\n  useEffect(() => {\n    ;(async function load() {\n      setIsLoading(true)\n      await fetchEpisodes()\n      setIsLoading(false)\n    })()\n  }, [fetchEpisodes])\n\n  // simulate a longer refresh, if the refresh is too fast for UX\n  async function manualRefresh() {\n    setRefreshing(true)\n    await Promise.allSettled([fetchEpisodes(), delay(750)])\n    setRefreshing(false)\n  }\n\n  return (\n    <Screen preset=\"fixed\" safeAreaEdges={[\"top\"]} contentContainerStyle={$styles.flex1}>\n      <FlatList<EpisodeItem>\n        contentContainerStyle={themed([$styles.container, $listContentContainer])}\n        data={episodesForList}\n        extraData={totalEpisodes + totalFavorites}\n        refreshing={refreshing}\n        onRefresh={manualRefresh}\n        keyExtractor={(item) => item.guid}\n        ListEmptyComponent={\n          isLoading ? (\n            <ActivityIndicator />\n          ) : (\n            <EmptyState\n              preset=\"generic\"\n              style={themed($emptyState)}\n              headingTx={\n                favoritesOnly ? \"demoPodcastListScreen:noFavoritesEmptyState.heading\" : undefined\n              }\n              contentTx={\n                favoritesOnly ? \"demoPodcastListScreen:noFavoritesEmptyState.content\" : undefined\n              }\n              button={favoritesOnly ? \"\" : undefined}\n              buttonOnPress={manualRefresh}\n              imageStyle={$emptyStateImage}\n              ImageProps={{ resizeMode: \"contain\" }}\n            />\n          )\n        }\n        ListHeaderComponent={\n          <View style={themed($heading)}>\n            <Text preset=\"heading\" tx=\"demoPodcastListScreen:title\" />\n            {(favoritesOnly || episodesForList.length > 0) && (\n              <View style={themed($toggle)}>\n                <Switch\n                  value={favoritesOnly}\n                  onValueChange={() => toggleFavoritesOnly()}\n                  labelTx=\"demoPodcastListScreen:onlyFavorites\"\n                  labelPosition=\"left\"\n                  labelStyle={$labelStyle}\n                  accessibilityLabel={translate(\"demoPodcastListScreen:accessibility.switch\")}\n                />\n              </View>\n            )}\n          </View>\n        }\n        renderItem={({ item }) => (\n          <EpisodeCard episode={item} onPressFavorite={() => toggleFavorite(item)} />\n        )}\n      />\n    </Screen>\n  )\n}\n\nconst EpisodeCard = ({\n  episode,\n  onPressFavorite,\n}: {\n  episode: EpisodeItem\n  onPressFavorite: () => void\n}) => {\n  const {\n    theme: { colors },\n    themed,\n  } = useAppTheme()\n  const { isFavorite, datePublished, duration, parsedTitleAndSubtitle } = useEpisode(episode)\n\n  const liked = useSharedValue(isFavorite ? 1 : 0)\n  const imageUri = useMemo<ImageSourcePropType>(() => {\n    return rnrImages[Math.floor(Math.random() * rnrImages.length)]\n  }, [])\n\n  // Grey heart\n  const animatedLikeButtonStyles = useAnimatedStyle(() => {\n    return {\n      transform: [\n        {\n          scale: interpolate(liked.value, [0, 1], [1, 0], Extrapolation.EXTEND),\n        },\n      ],\n      opacity: interpolate(liked.value, [0, 1], [1, 0], Extrapolation.CLAMP),\n    }\n  })\n\n  // Pink heart\n  const animatedUnlikeButtonStyles = useAnimatedStyle(() => {\n    return {\n      transform: [\n        {\n          scale: liked.value,\n        },\n      ],\n      opacity: liked.value,\n    }\n  })\n\n  const handlePressFavorite = useCallback(() => {\n    onPressFavorite()\n    liked.value = withSpring(liked.value ? 0 : 1)\n  }, [liked, onPressFavorite])\n\n  /**\n   * Android has a \"longpress\" accessibility action. iOS does not, so we just have to use a hint.\n   * @see https://reactnative.dev/docs/accessibility#accessibilityactions\n   */\n  const accessibilityHintProps = useMemo(\n    () =>\n      Platform.select<AccessibilityProps>({\n        ios: {\n          accessibilityLabel: episode.title,\n          accessibilityHint: translate(\"demoPodcastListScreen:accessibility.cardHint\", {\n            action: isFavorite ? \"unfavorite\" : \"favorite\",\n          }),\n        },\n        android: {\n          accessibilityLabel: episode.title,\n          accessibilityActions: [\n            {\n              name: \"longpress\",\n              label: translate(\"demoPodcastListScreen:accessibility.favoriteAction\"),\n            },\n          ],\n          onAccessibilityAction: ({ nativeEvent }) => {\n            if (nativeEvent.actionName === \"longpress\") {\n              handlePressFavorite()\n            }\n          },\n        },\n      }),\n    [episode.title, handlePressFavorite, isFavorite],\n  )\n\n  const handlePressCard = () => {\n    openLinkInBrowser(episode.enclosure.link)\n  }\n\n  const ButtonLeftAccessory: ComponentType<ButtonAccessoryProps> = useMemo(\n    () =>\n      function ButtonLeftAccessory() {\n        return (\n          <View>\n            <Animated.View\n              style={[\n                $styles.row,\n                themed($iconContainer),\n                StyleSheet.absoluteFill,\n                animatedLikeButtonStyles,\n              ]}\n            >\n              <Icon\n                icon=\"heart\"\n                size={ICON_SIZE}\n                color={colors.palette.neutral800} // dark grey\n              />\n            </Animated.View>\n            <Animated.View\n              style={[$styles.row, themed($iconContainer), animatedUnlikeButtonStyles]}\n            >\n              <Icon\n                icon=\"heart\"\n                size={ICON_SIZE}\n                color={colors.palette.primary400} // pink\n              />\n            </Animated.View>\n          </View>\n        )\n      },\n    [animatedLikeButtonStyles, animatedUnlikeButtonStyles, colors, themed],\n  )\n\n  return (\n    <Card\n      style={themed($item)}\n      verticalAlignment=\"force-footer-bottom\"\n      onPress={handlePressCard}\n      onLongPress={handlePressFavorite}\n      HeadingComponent={\n        <View style={[$styles.row, themed($metadata)]}>\n          <Text\n            style={themed($metadataText)}\n            size=\"xxs\"\n            accessibilityLabel={datePublished.accessibilityLabel}\n          >\n            {datePublished.textLabel}\n          </Text>\n          <Text\n            style={themed($metadataText)}\n            size=\"xxs\"\n            accessibilityLabel={duration.accessibilityLabel}\n          >\n            {duration.textLabel}\n          </Text>\n        </View>\n      }\n      content={\n        parsedTitleAndSubtitle.subtitle\n          ? `${parsedTitleAndSubtitle.title} - ${parsedTitleAndSubtitle.subtitle}`\n          : parsedTitleAndSubtitle.title\n      }\n      {...accessibilityHintProps}\n      RightComponent={<Image source={imageUri} style={themed($itemThumbnail)} />}\n      FooterComponent={\n        <Button\n          onPress={handlePressFavorite}\n          onLongPress={handlePressFavorite}\n          style={themed([$favoriteButton, isFavorite && $unFavoriteButton])}\n          accessibilityLabel={\n            isFavorite\n              ? translate(\"demoPodcastListScreen:accessibility.unfavoriteIcon\")\n              : translate(\"demoPodcastListScreen:accessibility.favoriteIcon\")\n          }\n          LeftAccessory={ButtonLeftAccessory}\n        >\n          <Text\n            size=\"xxs\"\n            accessibilityLabel={duration.accessibilityLabel}\n            weight=\"medium\"\n            text={\n              isFavorite\n                ? translate(\"demoPodcastListScreen:unfavoriteButton\")\n                : translate(\"demoPodcastListScreen:favoriteButton\")\n            }\n          />\n        </Button>\n      }\n    />\n  )\n}\n\n// #region Styles\nconst $listContentContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  paddingHorizontal: spacing.lg,\n  paddingTop: spacing.lg + spacing.xl,\n  paddingBottom: spacing.lg,\n})\n\nconst $heading: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginBottom: spacing.md,\n})\n\nconst $item: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({\n  padding: spacing.md,\n  marginTop: spacing.md,\n  minHeight: 120,\n  backgroundColor: colors.palette.neutral100,\n})\n\nconst $itemThumbnail: ThemedStyle<ImageStyle> = ({ spacing }) => ({\n  marginTop: spacing.sm,\n  borderRadius: 50,\n  alignSelf: \"flex-start\",\n})\n\nconst $toggle: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginTop: spacing.md,\n})\n\nconst $labelStyle: TextStyle = {\n  textAlign: \"left\",\n}\n\nconst $iconContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  height: ICON_SIZE,\n  width: ICON_SIZE,\n  marginEnd: spacing.sm,\n})\n\nconst $metadata: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({\n  color: colors.textDim,\n  marginTop: spacing.xs,\n})\n\nconst $metadataText: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({\n  color: colors.textDim,\n  marginEnd: spacing.md,\n  marginBottom: spacing.xs,\n})\n\nconst $favoriteButton: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({\n  borderRadius: 17,\n  marginTop: spacing.md,\n  justifyContent: \"flex-start\",\n  backgroundColor: colors.palette.neutral300,\n  borderColor: colors.palette.neutral300,\n  paddingHorizontal: spacing.md,\n  paddingTop: spacing.xxxs,\n  paddingBottom: 0,\n  minHeight: 32,\n  alignSelf: \"flex-start\",\n})\n\nconst $unFavoriteButton: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  borderColor: colors.palette.primary100,\n  backgroundColor: colors.palette.primary100,\n})\n\nconst $emptyState: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginTop: spacing.xxl,\n})\n\nconst $emptyStateImage: ImageStyle = {\n  transform: [{ scaleX: isRTL ? -1 : 1 }],\n}\n// #endregion\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/DemoDivider.tsx",
    "content": "/* eslint-disable  react-native/no-inline-styles */\nimport { StyleProp, View, ViewStyle } from \"react-native\"\n\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\n\ninterface DemoDividerProps {\n  type?: \"vertical\" | \"horizontal\"\n  size?: number\n  style?: StyleProp<ViewStyle>\n  line?: boolean\n}\n\n/**\n * @param {DemoDividerProps} props - The props for the `DemoDivider` component.\n * @returns {JSX.Element} The rendered `DemoDivider` component.\n */\nexport function DemoDivider(props: DemoDividerProps) {\n  const { type = \"horizontal\", size = 10, line = false, style: $styleOverride } = props\n  const { themed } = useAppTheme()\n\n  return (\n    <View\n      style={[\n        $divider,\n        type === \"horizontal\" && { height: size },\n        type === \"vertical\" && { width: size },\n        $styleOverride,\n      ]}\n    >\n      {line && (\n        <View\n          style={[\n            themed($line),\n            type === \"horizontal\" && {\n              width: 150,\n              height: 1,\n              marginStart: -75,\n              marginTop: -1,\n            },\n            type === \"vertical\" && {\n              height: 50,\n              width: 1,\n              marginTop: -25,\n              marginStart: -1,\n            },\n          ]}\n        />\n      )}\n    </View>\n  )\n}\n\nconst $divider: ViewStyle = {\n  flexGrow: 0,\n  flexShrink: 0,\n}\n\nconst $line: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.border,\n  position: \"absolute\",\n  left: \"50%\",\n  top: \"50%\",\n})\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx",
    "content": "import { FC, ReactElement, useCallback, useEffect, useRef, useState } from \"react\"\nimport {\n  FlatList,\n  Image,\n  ImageStyle,\n  Platform,\n  SectionList,\n  TextStyle,\n  View,\n  ViewStyle,\n} from \"react-native\"\nimport { Link, RouteProp, useRoute } from \"@react-navigation/native\"\nimport { Drawer } from \"react-native-drawer-layout\"\n\nimport { ListItem } from \"@/components/ListItem\"\nimport { Screen } from \"@/components/Screen\"\nimport { Text } from \"@/components/Text\"\nimport { TxKeyPath, isRTL } from \"@/i18n\"\nimport { translate } from \"@/i18n/translate\"\nimport { DemoTabParamList, DemoTabScreenProps } from \"@/navigators/navigationTypes\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\nimport { useSafeAreaInsetsStyle } from \"@/utils/useSafeAreaInsetsStyle\"\n\nimport * as Demos from \"./demos\"\nimport { DrawerIconButton } from \"./DrawerIconButton\"\nimport SectionListWithKeyboardAwareScrollView from \"./SectionListWithKeyboardAwareScrollView\"\n\nconst logo = require(\"@assets/images/logo.png\")\n\ninterface DemoListItem {\n  item: { name: string; useCases: string[] }\n  sectionIndex: number\n  handleScroll?: (sectionIndex: number, itemIndex?: number) => void\n}\n\nconst slugify = (str: string) =>\n  str\n    .toLowerCase()\n    .trim()\n    .replace(/[^\\w\\s-]/g, \"\")\n    .replace(/[\\s_-]+/g, \"-\")\n    .replace(/^-+|-+$/g, \"\")\n\n/**\n * Type-safe utility to check if an unknown object has a valid string property.\n * This is particularly useful in React 19 where props are typed as unknown by default.\n * The function safely narrows down the type by checking both property existence and type.\n * @param props - The unknown props to check.\n * @param propName - The name of the property to check.\n * @returns Whether the property is a valid string.\n */\nfunction hasValidStringProp(props: unknown, propName: string): boolean {\n  return (\n    props !== null &&\n    typeof props === \"object\" &&\n    propName in props &&\n    typeof (props as Record<string, unknown>)[propName] === \"string\"\n  )\n}\n\nconst WebListItem: FC<DemoListItem> = ({ item, sectionIndex }) => {\n  const sectionSlug = item.name.toLowerCase()\n  const { themed } = useAppTheme()\n  return (\n    <View>\n      <Link\n        screen=\"DemoShowroom\"\n        params={{ queryIndex: sectionSlug }}\n        style={themed($menuContainer)}\n      >\n        <Text preset=\"bold\">{item.name}</Text>\n      </Link>\n      {item.useCases.map((u) => {\n        const itemSlug = slugify(u)\n\n        return (\n          <Link\n            key={`section${sectionIndex}-${u}`}\n            screen=\"DemoShowroom\"\n            params={{ queryIndex: sectionSlug, itemIndex: itemSlug }}\n          >\n            <Text>{u}</Text>\n          </Link>\n        )\n      })}\n    </View>\n  )\n}\n\nconst NativeListItem: FC<DemoListItem> = ({ item, sectionIndex, handleScroll }) => {\n  const { themed } = useAppTheme()\n  return (\n    <View>\n      <Text\n        onPress={() => handleScroll?.(sectionIndex)}\n        preset=\"bold\"\n        style={themed($menuContainer)}\n      >\n        {item.name}\n      </Text>\n      {item.useCases.map((u, index) => (\n        <ListItem\n          key={`section${sectionIndex}-${u}`}\n          onPress={() => handleScroll?.(sectionIndex, index)}\n          text={u}\n          rightIcon={isRTL ? \"caretLeft\" : \"caretRight\"}\n        />\n      ))}\n    </View>\n  )\n}\n\nconst ShowroomListItem = Platform.select({ web: WebListItem, default: NativeListItem })\nconst isAndroid = Platform.OS === \"android\"\n\nexport const DemoShowroomScreen: FC<DemoTabScreenProps<\"DemoShowroom\">> =\n  function DemoShowroomScreen(_props) {\n    const [open, setOpen] = useState(false)\n    const timeout = useRef<ReturnType<typeof setTimeout>>(null)\n    const listRef = useRef<SectionList>(null)\n    const menuRef = useRef<FlatList<DemoListItem[\"item\"]>>(null)\n    const route = useRoute<RouteProp<DemoTabParamList, \"DemoShowroom\">>()\n    const params = route.params\n\n    const { themed, theme } = useAppTheme()\n\n    const toggleDrawer = useCallback(() => {\n      if (!open) {\n        setOpen(true)\n      } else {\n        setOpen(false)\n      }\n    }, [open])\n\n    const handleScroll = useCallback((sectionIndex: number, itemIndex = 0) => {\n      try {\n        listRef.current?.scrollToLocation({\n          animated: true,\n          itemIndex,\n          sectionIndex,\n          viewPosition: 0.25,\n        })\n      } catch (e) {\n        console.error(e)\n      }\n    }, [])\n\n    // handle Web links\n    useEffect(() => {\n      if (params !== undefined && Object.keys(params).length > 0) {\n        const demoValues = Object.values(Demos)\n        const findSectionIndex = demoValues.findIndex(\n          (x) => x.name.toLowerCase() === params.queryIndex,\n        )\n        let findItemIndex = 0\n        if (params.itemIndex) {\n          try {\n            findItemIndex = demoValues[findSectionIndex].data({ themed, theme }).findIndex((u) => {\n              if (hasValidStringProp(u.props, \"name\")) {\n                return (\n                  slugify(translate((u.props as { name: TxKeyPath }).name)) === params.itemIndex\n                )\n              }\n              return false\n            })\n          } catch (err) {\n            console.error(err)\n          }\n        }\n        handleScroll(findSectionIndex, findItemIndex)\n      }\n    }, [handleScroll, params, theme, themed])\n\n    const scrollToIndexFailed = (info: {\n      index: number\n      highestMeasuredFrameIndex: number\n      averageItemLength: number\n    }) => {\n      listRef.current?.getScrollResponder()?.scrollToEnd()\n      timeout.current = setTimeout(\n        () =>\n          listRef.current?.scrollToLocation({\n            animated: true,\n            itemIndex: info.index,\n            sectionIndex: 0,\n          }),\n        50,\n      )\n    }\n\n    useEffect(() => {\n      return () => {\n        if (timeout.current) {\n          clearTimeout(timeout.current)\n        }\n      }\n    }, [])\n\n    const $drawerInsets = useSafeAreaInsetsStyle([\"top\"])\n\n    return (\n      <Drawer\n        open={open}\n        onOpen={() => setOpen(true)}\n        onClose={() => setOpen(false)}\n        drawerType=\"back\"\n        drawerPosition={isRTL ? \"right\" : \"left\"}\n        renderDrawerContent={() => (\n          <View style={themed([$drawer, $drawerInsets])}>\n            <View style={themed($logoContainer)}>\n              <Image source={logo} style={$logoImage} />\n            </View>\n            <FlatList<DemoListItem[\"item\"]>\n              ref={menuRef}\n              contentContainerStyle={themed($listContentContainer)}\n              data={Object.values(Demos).map((d) => ({\n                name: d.name,\n                useCases: d.data({ theme, themed }).map((u) => {\n                  if (hasValidStringProp(u.props, \"name\")) {\n                    return translate((u.props as { name: TxKeyPath }).name)\n                  }\n                  return \"\"\n                }),\n              }))}\n              keyExtractor={(item) => item.name}\n              renderItem={({ item, index: sectionIndex }) => (\n                <ShowroomListItem {...{ item, sectionIndex, handleScroll }} />\n              )}\n            />\n          </View>\n        )}\n      >\n        <Screen\n          preset=\"fixed\"\n          safeAreaEdges={[\"top\"]}\n          contentContainerStyle={$styles.flex1}\n          {...(isAndroid ? { KeyboardAvoidingViewProps: { behavior: undefined } } : {})}\n        >\n          <DrawerIconButton onPress={toggleDrawer} />\n\n          <SectionListWithKeyboardAwareScrollView\n            ref={listRef}\n            contentContainerStyle={themed($sectionListContentContainer)}\n            stickySectionHeadersEnabled={false}\n            sections={Object.values(Demos).map((d) => ({\n              name: d.name,\n              description: d.description,\n              data: [d.data({ theme, themed })],\n            }))}\n            renderItem={({ item, index: sectionIndex }) => (\n              <View>\n                {item.map((demo: ReactElement, demoIndex: number) => (\n                  <View key={`${sectionIndex}-${demoIndex}`}>{demo}</View>\n                ))}\n              </View>\n            )}\n            renderSectionFooter={() => <View style={themed($demoUseCasesSpacer)} />}\n            ListHeaderComponent={\n              <View style={themed($heading)}>\n                <Text preset=\"heading\" tx=\"demoShowroomScreen:jumpStart\" />\n              </View>\n            }\n            onScrollToIndexFailed={scrollToIndexFailed}\n            renderSectionHeader={({ section }) => {\n              return (\n                <View>\n                  <Text preset=\"heading\" style={themed($demoItemName)}>\n                    {section.name}\n                  </Text>\n                  <Text style={themed($demoItemDescription)}>{translate(section.description)}</Text>\n                </View>\n              )\n            }}\n          />\n        </Screen>\n      </Drawer>\n    )\n  }\n\nconst $drawer: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.background,\n  flex: 1,\n})\n\nconst $listContentContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  paddingHorizontal: spacing.lg,\n})\n\nconst $sectionListContentContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  paddingHorizontal: spacing.lg,\n})\n\nconst $heading: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginBottom: spacing.xxxl,\n})\n\nconst $logoImage: ImageStyle = {\n  height: 42,\n  width: 77,\n}\n\nconst $logoContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  alignSelf: \"flex-start\",\n  justifyContent: \"center\",\n  height: 56,\n  paddingHorizontal: spacing.lg,\n})\n\nconst $menuContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  paddingBottom: spacing.xs,\n  paddingTop: spacing.lg,\n})\n\nconst $demoItemName: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  fontSize: 24,\n  marginBottom: spacing.md,\n})\n\nconst $demoItemDescription: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginBottom: spacing.xxl,\n})\n\nconst $demoUseCasesSpacer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  paddingBottom: spacing.xxl,\n})\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/DemoUseCase.tsx",
    "content": "import { ReactNode } from \"react\"\nimport { TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Text } from \"@/components/Text\"\nimport type { TxKeyPath } from \"@/i18n\"\nimport { translate } from \"@/i18n/translate\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\n\ninterface DemoUseCaseProps {\n  name: TxKeyPath\n  description?: TxKeyPath\n  layout?: \"column\" | \"row\"\n  itemStyle?: ViewStyle\n  children: ReactNode\n}\n\n/**\n * @param {DemoUseCaseProps} props - The props for the `DemoUseCase` component.\n * @returns {JSX.Element} The rendered `DemoUseCase` component.\n */\nexport function DemoUseCase(props: DemoUseCaseProps) {\n  const { name, description, children, layout = \"column\", itemStyle = {} } = props\n  const { themed } = useAppTheme()\n\n  return (\n    <View>\n      <Text style={themed($name)}>{translate(name)}</Text>\n      {description && <Text style={themed($description)}>{translate(description)}</Text>}\n\n      <View style={[itemStyle, layout === \"row\" && $styles.row, themed($item)]}>{children}</View>\n    </View>\n  )\n}\n\nconst $description: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginTop: spacing.md,\n})\n\nconst $item: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({\n  backgroundColor: colors.palette.neutral100,\n  borderRadius: 8,\n  padding: spacing.lg,\n  marginVertical: spacing.md,\n})\n\nconst $name: ThemedStyle<TextStyle> = ({ typography }) => ({\n  fontFamily: typography.primary.bold,\n})\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/DrawerIconButton.tsx",
    "content": "import { Pressable, PressableProps, ViewStyle, Platform } from \"react-native\"\nimport { useDrawerProgress } from \"react-native-drawer-layout\"\nimport Animated, { interpolate, interpolateColor, useAnimatedStyle } from \"react-native-reanimated\"\n\nimport { isRTL } from \"@/i18n\"\nimport { useAppTheme } from \"@/theme/context\"\n\ninterface DrawerIconButtonProps extends PressableProps {}\n\nconst AnimatedPressable = Animated.createAnimatedComponent(Pressable)\n\n/**\n * @param {DrawerIconButtonProps} props - The props for the `DrawerIconButton` component.\n * @returns {JSX.Element} The rendered `DrawerIconButton` component.\n */\nexport function DrawerIconButton(props: DrawerIconButtonProps) {\n  const { ...PressableProps } = props\n  const progress = useDrawerProgress()\n  const isWeb = Platform.OS === \"web\"\n  const {\n    theme: { colors },\n    themed,\n  } = useAppTheme()\n\n  const animatedContainerStyles = useAnimatedStyle(() => {\n    const translateX = interpolate(progress.value, [0, 1], [0, isRTL ? 60 : -60])\n\n    return {\n      transform: [{ translateX }],\n    }\n  })\n\n  const animatedTopBarStyles = useAnimatedStyle(() => {\n    const backgroundColor = interpolateColor(progress.value, [0, 1], [colors.text, colors.tint])\n    const marginStart = interpolate(progress.value, [0, 1], [0, -11.5])\n    const rotate = interpolate(progress.value, [0, 1], [0, isRTL ? 45 : -45])\n    const marginBottom = interpolate(progress.value, [0, 1], [0, -2])\n    const width = interpolate(progress.value, [0, 1], [18, 12])\n    const marginHorizontal =\n      isWeb && isRTL\n        ? { marginRight: marginStart }\n        : {\n            marginLeft: marginStart,\n          }\n\n    return {\n      ...marginHorizontal,\n      backgroundColor,\n      marginBottom,\n      width,\n      transform: [{ rotate: `${rotate}deg` }],\n    }\n  })\n\n  const animatedMiddleBarStyles = useAnimatedStyle(() => {\n    const backgroundColor = interpolateColor(progress.value, [0, 1], [colors.text, colors.tint])\n    const width = interpolate(progress.value, [0, 1], [18, 16])\n\n    return {\n      backgroundColor,\n      width,\n    }\n  })\n\n  const animatedBottomBarStyles = useAnimatedStyle(() => {\n    const marginTop = interpolate(progress.value, [0, 1], [4, 2])\n    const backgroundColor = interpolateColor(progress.value, [0, 1], [colors.text, colors.tint])\n    const marginStart = interpolate(progress.value, [0, 1], [0, -11.5])\n    const rotate = interpolate(progress.value, [0, 1], [0, isRTL ? -45 : 45])\n    const width = interpolate(progress.value, [0, 1], [18, 12])\n    const marginHorizontal =\n      isWeb && isRTL\n        ? { marginRight: marginStart }\n        : {\n            marginLeft: marginStart,\n          }\n\n    return {\n      ...marginHorizontal,\n      backgroundColor,\n      width,\n      marginTop,\n      transform: [{ rotate: `${rotate}deg` }],\n    }\n  })\n\n  return (\n    <AnimatedPressable {...PressableProps} style={[$container, animatedContainerStyles]}>\n      <Animated.View style={[$topBar, animatedTopBarStyles]} />\n\n      <Animated.View style={[themed($middleBar), animatedMiddleBarStyles]} />\n\n      <Animated.View style={[$bottomBar, animatedBottomBarStyles]} />\n    </AnimatedPressable>\n  )\n}\n\nconst barHeight = 2\n\nconst $container: ViewStyle = {\n  alignItems: \"center\",\n  height: 56,\n  justifyContent: \"center\",\n  width: 56,\n}\n\nconst $topBar: ViewStyle = {\n  height: barHeight,\n}\n\nconst $middleBar: ViewStyle = {\n  height: barHeight,\n  marginTop: 4,\n}\n\nconst $bottomBar: ViewStyle = {\n  height: barHeight,\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/SectionListWithKeyboardAwareScrollView.tsx",
    "content": "import { forwardRef, ReactElement, ReactNode, useCallback } from \"react\"\nimport { ScrollViewProps, SectionList, SectionListProps } from \"react-native\"\nimport { KeyboardAwareScrollView } from \"react-native-keyboard-controller\"\n\nimport { DEFAULT_BOTTOM_OFFSET } from \"@/components/Screen\"\n\ntype SectionType<ItemType> = {\n  name: string\n  description: string\n  data: ItemType[]\n}\n\ntype SectionListWithKeyboardAwareScrollViewProps<ItemType> = SectionListProps<ItemType> & {\n  /* Optional function to pass a custom scroll component */\n  renderScrollComponent?: (props: ScrollViewProps) => ReactNode\n  /* Optional additional offset between TextInput bottom edge and keyboard top edge. See https://kirillzyusko.github.io/react-native-keyboard-controller/docs/api/components/keyboard-aware-scroll-view#bottomoffset */\n  bottomOffset?: number\n  /* The sections to be rendered in the list */\n  sections: SectionType<ItemType>[]\n  /* Function to render the header for each section */\n  renderSectionHeader: ({ section }: { section: SectionType<ItemType> }) => React.ReactNode\n}\n\nfunction SectionListWithKeyboardAwareScrollView<ItemType = any>(\n  {\n    renderScrollComponent,\n    bottomOffset = DEFAULT_BOTTOM_OFFSET,\n    contentContainerStyle,\n    ...props\n  }: SectionListWithKeyboardAwareScrollViewProps<ItemType>,\n  ref: React.Ref<SectionList<ItemType>>,\n): ReactElement {\n  const defaultRenderScrollComponent = useCallback(\n    (props: ScrollViewProps) => (\n      <KeyboardAwareScrollView\n        contentContainerStyle={contentContainerStyle}\n        bottomOffset={bottomOffset}\n        {...props}\n      />\n    ),\n    [contentContainerStyle, bottomOffset],\n  )\n\n  return (\n    <SectionList\n      {...props}\n      ref={ref}\n      renderScrollComponent={renderScrollComponent ?? defaultRenderScrollComponent}\n    />\n  )\n}\n\nexport default forwardRef(SectionListWithKeyboardAwareScrollView) as <ItemType = any>(\n  props: SectionListWithKeyboardAwareScrollViewProps<ItemType> & {\n    ref?: React.Ref<SectionList<ItemType>>\n  },\n) => ReactElement\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoAutoImage.tsx",
    "content": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { Image, ImageStyle, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { AutoImage } from \"@/components/AutoImage\"\nimport { Text } from \"@/components/Text\"\nimport { translate } from \"@/i18n/translate\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { DemoDivider } from \"../DemoDivider\"\nimport { Demo } from \"./types\"\nimport { DemoUseCase } from \"../DemoUseCase\"\n\nconst $imageContainer: ViewStyle = {\n  alignItems: \"center\",\n}\n\nconst $aspectRatioDescription: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  textAlign: \"center\",\n  width: \"100%\",\n  marginTop: spacing.xs,\n})\n\nconst $aspectRatioWidthExampleContainer: ViewStyle = {\n  justifyContent: \"space-between\",\n}\n\nconst $aspectRatioHeightExampleContainer: ViewStyle = {\n  alignItems: \"stretch\",\n  justifyContent: \"space-between\",\n  height: 130,\n}\n\nconst $aspectRatioBox: ThemedStyle<ViewStyle & ImageStyle> = (theme) => ({\n  borderRadius: 4,\n  borderWidth: 3,\n  borderColor: theme.colors.palette.secondary300,\n  backgroundColor: theme.colors.palette.neutral800,\n})\n\nexport const DemoAutoImage: Demo = {\n  name: \"AutoImage\",\n  description: \"demoAutoImage:description\",\n  data: ({ theme, themed }) => [\n    <DemoUseCase name=\"demoAutoImage:useCase.remoteUri.name\">\n      <View style={$imageContainer}>\n        <AutoImage\n          source={{\n            uri: \"https://user-images.githubusercontent.com/1775841/184508739-f90d0ce5-7219-42fd-a91f-3382d016eae0.png\",\n          }}\n        />\n      </View>\n    </DemoUseCase>,\n\n    <DemoUseCase name=\"demoAutoImage:useCase.base64Uri.name\">\n      <View style={$imageContainer}>\n        <AutoImage\n          source={{\n            uri: `data:image/png;base64,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`,\n          }}\n        />\n      </View>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoAutoImage:useCase.scaledToFitDimensions.name\"\n      description=\"demoAutoImage:useCase.scaledToFitDimensions.description\"\n    >\n      <View style={[$styles.row, $aspectRatioWidthExampleContainer]}>\n        <Text\n          text=\"<View />\"\n          size=\"xxs\"\n          weight=\"bold\"\n          style={{ flexBasis: \"33.3333%\", color: theme.colors.palette.secondary400 }}\n        />\n        <Text\n          text=\"<Image />\"\n          size=\"xxs\"\n          weight=\"bold\"\n          style={{\n            flexBasis: \"33.3333%\",\n            textAlign: \"center\",\n            color: theme.colors.palette.secondary400,\n          }}\n        />\n        <Text\n          text=\"<AutoImage />\"\n          size=\"xxs\"\n          weight=\"bold\"\n          style={{\n            flexBasis: \"33.3333%\",\n            textAlign: \"right\",\n            color: theme.colors.palette.secondary400,\n          }}\n        />\n      </View>\n\n      <DemoDivider size={5} />\n\n      <View style={[$styles.row, $aspectRatioWidthExampleContainer, { height: 80 }]}>\n        <View style={themed([$aspectRatioBox, { width: 60 }])} />\n        <Image\n          source={{\n            uri: \"https://user-images.githubusercontent.com/1775841/188244137-a35ab1b9-658d-4701-b1dd-7caa51173fa9.png\",\n          }}\n          style={themed([$aspectRatioBox, { width: 60 }])}\n          resizeMode=\"contain\"\n        />\n        <AutoImage\n          maxWidth={60}\n          style={themed($aspectRatioBox)}\n          source={{\n            uri: \"https://user-images.githubusercontent.com/1775841/188244137-a35ab1b9-658d-4701-b1dd-7caa51173fa9.png\",\n          }}\n        />\n      </View>\n\n      <Text weight=\"bold\" size=\"xs\" style={themed($aspectRatioDescription)}>\n        {translate(\"demoAutoImage:useCase.scaledToFitDimensions.heightAuto\")}\n      </Text>\n\n      <DemoDivider size={40} />\n\n      <View style={$styles.row}>\n        <View style={$aspectRatioHeightExampleContainer}>\n          <Text\n            text=\"<View />\"\n            size=\"xxs\"\n            weight=\"bold\"\n            style={{ color: theme.colors.palette.secondary400 }}\n          />\n          <Text\n            text=\"<Image />\"\n            size=\"xxs\"\n            weight=\"bold\"\n            style={{ color: theme.colors.palette.secondary400 }}\n          />\n          <Text\n            text=\"<AutoImage />\"\n            size=\"xxs\"\n            weight=\"bold\"\n            style={{ color: theme.colors.palette.secondary400 }}\n          />\n        </View>\n\n        <View\n          style={[$aspectRatioHeightExampleContainer, { flex: 1, marginStart: theme.spacing.sm }]}\n        >\n          <View style={themed([$aspectRatioBox, { height: 32 }])} />\n          <Image\n            source={{\n              uri: \"https://user-images.githubusercontent.com/1775841/188244137-a35ab1b9-658d-4701-b1dd-7caa51173fa9.png\",\n            }}\n            style={themed([$aspectRatioBox, { height: 32 }])}\n            resizeMode=\"contain\"\n          />\n          <AutoImage\n            maxHeight={32}\n            style={themed([$aspectRatioBox, { alignSelf: \"center\" }])}\n            source={{\n              uri: \"https://user-images.githubusercontent.com/1775841/188244137-a35ab1b9-658d-4701-b1dd-7caa51173fa9.png\",\n            }}\n          />\n        </View>\n      </View>\n\n      <Text weight=\"bold\" size=\"xs\" style={themed($aspectRatioDescription)}>\n        {translate(\"demoAutoImage:useCase.scaledToFitDimensions.widthAuto\")}\n      </Text>\n\n      <DemoDivider size={40} />\n\n      <View style={[$styles.row, $aspectRatioWidthExampleContainer]}>\n        <Text\n          text=\"<View />\"\n          size=\"xxs\"\n          weight=\"bold\"\n          style={{ flexBasis: \"33.3333%\", color: theme.colors.palette.secondary400 }}\n        />\n        <Text\n          text=\"<Image />\"\n          size=\"xxs\"\n          weight=\"bold\"\n          style={{\n            flexBasis: \"33.3333%\",\n            textAlign: \"center\",\n            color: theme.colors.palette.secondary400,\n          }}\n        />\n        <Text\n          text=\"<AutoImage />\"\n          size=\"xxs\"\n          weight=\"bold\"\n          style={{\n            flexBasis: \"33.3333%\",\n            textAlign: \"right\",\n            color: theme.colors.palette.secondary400,\n          }}\n        />\n      </View>\n\n      <DemoDivider size={5} />\n\n      <View style={[$styles.row, $aspectRatioWidthExampleContainer]}>\n        <View style={themed([$aspectRatioBox, { width: 60, height: 60 }])} />\n        <Image\n          source={{\n            uri: \"https://user-images.githubusercontent.com/1775841/188244137-a35ab1b9-658d-4701-b1dd-7caa51173fa9.png\",\n          }}\n          style={themed([$aspectRatioBox, { width: 60, height: 60 }])}\n          resizeMode=\"contain\"\n        />\n        <AutoImage\n          maxWidth={60}\n          maxHeight={60}\n          style={themed($aspectRatioBox)}\n          source={{\n            uri: \"https://user-images.githubusercontent.com/1775841/188244137-a35ab1b9-658d-4701-b1dd-7caa51173fa9.png\",\n          }}\n        />\n      </View>\n\n      <Text weight=\"bold\" size=\"xs\" style={themed($aspectRatioDescription)}>\n        {translate(\"demoAutoImage:useCase.scaledToFitDimensions.bothManual\")}\n      </Text>\n    </DemoUseCase>,\n  ],\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoButton.tsx",
    "content": "/* eslint-disable react/jsx-key */\nimport { ImageStyle, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Button } from \"@/components/Button\"\nimport { Icon } from \"@/components/Icon\"\nimport { Text } from \"@/components/Text\"\nimport { translate } from \"@/i18n/translate\"\nimport type { ThemedStyle } from \"@/theme/types\"\n\nimport { DemoDivider } from \"../DemoDivider\"\nimport { Demo } from \"./types\"\nimport { DemoUseCase } from \"../DemoUseCase\"\n\nconst $iconStyle: ImageStyle = { width: 30, height: 30 }\nconst $customButtonStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.error,\n  height: 100,\n})\nconst $customButtonPressedStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.error,\n})\nconst $customButtonTextStyle: ThemedStyle<TextStyle> = ({ colors, typography }) => ({\n  color: colors.error,\n  fontFamily: typography.primary.bold,\n  textDecorationLine: \"underline\",\n  textDecorationColor: colors.error,\n})\nconst $customButtonPressedTextStyle: ThemedStyle<TextStyle> = ({ colors }) => ({\n  color: colors.palette.neutral100,\n})\nconst $customButtonRightAccessoryStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  width: \"53%\",\n  height: \"200%\",\n  backgroundColor: colors.error,\n  position: \"absolute\",\n  top: 0,\n  right: 0,\n})\nconst $customButtonPressedRightAccessoryStyle: ThemedStyle<ImageStyle> = ({ colors }) => ({\n  tintColor: colors.palette.neutral100,\n})\n\nconst $disabledOpacity: ViewStyle = { opacity: 0.5 }\nconst $disabledButtonTextStyle: ThemedStyle<TextStyle> = ({ colors }) => ({\n  color: colors.palette.neutral100,\n  textDecorationColor: colors.palette.neutral100,\n})\n\nexport const DemoButton: Demo = {\n  name: \"Button\",\n  description: \"demoButton:description\",\n  data: ({ themed }) => [\n    <DemoUseCase\n      name=\"demoButton:useCase.presets.name\"\n      description=\"demoButton:useCase.presets.description\"\n    >\n      <Button>Default - Laboris In Labore</Button>\n      <DemoDivider />\n\n      <Button preset=\"filled\">Filled - Laboris Ex</Button>\n      <DemoDivider />\n\n      <Button preset=\"reversed\">Reversed - Ad Ipsum</Button>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoButton:useCase.passingContent.name\"\n      description=\"demoButton:useCase.passingContent.description\"\n    >\n      <Button text={translate(\"demoButton:useCase.passingContent.viaTextProps\")} />\n      <DemoDivider />\n\n      <Button tx=\"demoShowroomScreen:demoViaTxProp\" />\n      <DemoDivider />\n\n      <Button>{translate(\"demoButton:useCase.passingContent.children\")}</Button>\n      <DemoDivider />\n\n      <Button\n        preset=\"filled\"\n        RightAccessory={(props) => (\n          <Icon containerStyle={props.style} style={$iconStyle} icon=\"ladybug\" />\n        )}\n      >\n        {translate(\"demoButton:useCase.passingContent.rightAccessory\")}\n      </Button>\n      <DemoDivider />\n\n      <Button\n        preset=\"filled\"\n        LeftAccessory={(props) => (\n          <Icon containerStyle={props.style} style={$iconStyle} icon=\"ladybug\" />\n        )}\n      >\n        {translate(\"demoButton:useCase.passingContent.leftAccessory\")}\n      </Button>\n      <DemoDivider />\n\n      <Button>\n        <Text>\n          <Text preset=\"bold\">{translate(\"demoButton:useCase.passingContent.nestedChildren\")}</Text>\n          {` `}\n          <Text preset=\"default\">\n            {translate(\"demoButton:useCase.passingContent.nestedChildren2\")}\n          </Text>\n          {` `}\n          <Text preset=\"bold\">\n            {translate(\"demoButton:useCase.passingContent.nestedChildren3\")}\n          </Text>\n        </Text>\n      </Button>\n      <DemoDivider />\n\n      <Button\n        preset=\"reversed\"\n        RightAccessory={(props) => (\n          <Icon containerStyle={props.style} style={$iconStyle} icon=\"ladybug\" />\n        )}\n        LeftAccessory={(props) => (\n          <Icon containerStyle={props.style} style={$iconStyle} icon=\"ladybug\" />\n        )}\n      >\n        {translate(\"demoButton:useCase.passingContent.multiLine\")}\n      </Button>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoButton:useCase.styling.name\"\n      description=\"demoButton:useCase.styling.description\"\n    >\n      <Button style={themed($customButtonStyle)}>\n        {translate(\"demoButton:useCase.styling.styleContainer\")}\n      </Button>\n      <DemoDivider />\n\n      <Button preset=\"filled\" textStyle={themed($customButtonTextStyle)}>\n        {translate(\"demoButton:useCase.styling.styleText\")}\n      </Button>\n      <DemoDivider />\n\n      <Button\n        preset=\"reversed\"\n        RightAccessory={() => <View style={themed($customButtonRightAccessoryStyle)} />}\n      >\n        {translate(\"demoButton:useCase.styling.styleAccessories\")}\n      </Button>\n      <DemoDivider />\n\n      <Button\n        pressedStyle={themed($customButtonPressedStyle)}\n        pressedTextStyle={themed($customButtonPressedTextStyle)}\n        RightAccessory={(props) => (\n          <Icon\n            containerStyle={props.style}\n            style={[\n              $iconStyle,\n              props.pressableState.pressed && themed($customButtonPressedRightAccessoryStyle),\n            ]}\n            icon=\"ladybug\"\n          />\n        )}\n      >\n        {translate(\"demoButton:useCase.styling.pressedState\")}\n      </Button>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoButton:useCase.disabling.name\"\n      description=\"demoButton:useCase.disabling.description\"\n    >\n      <Button\n        disabled\n        disabledStyle={$disabledOpacity}\n        pressedStyle={themed($customButtonPressedStyle)}\n        pressedTextStyle={themed($customButtonPressedTextStyle)}\n      >\n        {translate(\"demoButton:useCase.disabling.standard\")}\n      </Button>\n      <DemoDivider />\n\n      <Button\n        disabled\n        preset=\"filled\"\n        disabledStyle={$disabledOpacity}\n        pressedStyle={themed($customButtonPressedStyle)}\n        pressedTextStyle={themed($customButtonPressedTextStyle)}\n      >\n        {translate(\"demoButton:useCase.disabling.filled\")}\n      </Button>\n      <DemoDivider />\n\n      <Button\n        disabled\n        preset=\"reversed\"\n        disabledStyle={$disabledOpacity}\n        pressedStyle={themed($customButtonPressedStyle)}\n        pressedTextStyle={themed($customButtonPressedTextStyle)}\n      >\n        {translate(\"demoButton:useCase.disabling.reversed\")}\n      </Button>\n      <DemoDivider />\n\n      <Button\n        disabled\n        pressedStyle={themed($customButtonPressedStyle)}\n        pressedTextStyle={themed($customButtonPressedTextStyle)}\n        RightAccessory={(props) => (\n          <View\n            style={\n              props.disabled\n                ? [$customButtonRightAccessoryStyle, $disabledOpacity]\n                : themed($customButtonPressedRightAccessoryStyle)\n            }\n          />\n        )}\n      >\n        {translate(\"demoButton:useCase.disabling.accessory\")}\n      </Button>\n      <DemoDivider />\n\n      <Button\n        disabled\n        preset=\"filled\"\n        disabledTextStyle={themed([$customButtonTextStyle, $disabledButtonTextStyle])}\n        pressedStyle={themed($customButtonPressedStyle)}\n        pressedTextStyle={themed($customButtonPressedTextStyle)}\n      >\n        {translate(\"demoButton:useCase.disabling.textStyle\")}\n      </Button>\n    </DemoUseCase>,\n  ],\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoCard.tsx",
    "content": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { AutoImage } from \"@/components/AutoImage\"\nimport { Button } from \"@/components/Button\"\nimport { Card } from \"@/components/Card\"\nimport { Icon } from \"@/components/Icon\"\n\nimport { DemoDivider } from \"../DemoDivider\"\nimport { Demo } from \"./types\"\nimport { DemoUseCase } from \"../DemoUseCase\"\n\nexport const DemoCard: Demo = {\n  name: \"Card\",\n  description: \"demoCard:description\",\n  data: ({ theme }) => [\n    <DemoUseCase\n      name=\"demoCard:useCase.presets.name\"\n      description=\"demoCard:useCase.presets.description\"\n    >\n      <Card\n        headingTx=\"demoCard:useCase.presets.default.heading\"\n        contentTx=\"demoCard:useCase.presets.default.content\"\n        footerTx=\"demoCard:useCase.presets.default.footer\"\n      />\n      <DemoDivider />\n      <Card\n        headingTx=\"demoCard:useCase.presets.reversed.heading\"\n        contentTx=\"demoCard:useCase.presets.reversed.content\"\n        footerTx=\"demoCard:useCase.presets.reversed.footer\"\n        preset=\"reversed\"\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoCard:useCase.verticalAlignment.name\"\n      description=\"demoCard:useCase.verticalAlignment.description\"\n    >\n      <Card\n        headingTx=\"demoCard:useCase.verticalAlignment.top.heading\"\n        contentTx=\"demoCard:useCase.verticalAlignment.top.content\"\n        footerTx=\"demoCard:useCase.verticalAlignment.top.footer\"\n        style={{ minHeight: 160 }}\n      />\n      <DemoDivider />\n      <Card\n        headingTx=\"demoCard:useCase.verticalAlignment.center.heading\"\n        verticalAlignment=\"center\"\n        preset=\"reversed\"\n        contentTx=\"demoCard:useCase.verticalAlignment.center.content\"\n        footerTx=\"demoCard:useCase.verticalAlignment.center.footer\"\n        style={{ minHeight: 160 }}\n      />\n      <DemoDivider />\n      <Card\n        headingTx=\"demoCard:useCase.verticalAlignment.spaceBetween.heading\"\n        verticalAlignment=\"space-between\"\n        contentTx=\"demoCard:useCase.verticalAlignment.spaceBetween.content\"\n        footerTx=\"demoCard:useCase.verticalAlignment.spaceBetween.footer\"\n        style={{ minHeight: 160 }}\n      />\n      <DemoDivider />\n      <Card\n        preset=\"reversed\"\n        headingTx=\"demoCard:useCase.verticalAlignment.reversed.heading\"\n        verticalAlignment=\"force-footer-bottom\"\n        contentTx=\"demoCard:useCase.verticalAlignment.reversed.content\"\n        footerTx=\"demoCard:useCase.verticalAlignment.reversed.footer\"\n        style={{ minHeight: 160 }}\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoCard:useCase.passingContent.name\"\n      description=\"demoCard:useCase.passingContent.description\"\n    >\n      <Card\n        headingTx=\"demoCard:useCase.passingContent.heading\"\n        contentTx=\"demoCard:useCase.passingContent.content\"\n        footerTx=\"demoCard:useCase.passingContent.footer\"\n      />\n      <DemoDivider />\n      <Card\n        preset=\"reversed\"\n        headingTx=\"demoShowroomScreen:demoViaSpecifiedTxProp\"\n        headingTxOptions={{ prop: \"heading\" }}\n        contentTx=\"demoShowroomScreen:demoViaSpecifiedTxProp\"\n        contentTxOptions={{ prop: \"content\" }}\n        footerTx=\"demoShowroomScreen:demoViaSpecifiedTxProp\"\n        footerTxOptions={{ prop: \"footer\" }}\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoCard:useCase.customComponent.name\"\n      description=\"demoCard:useCase.customComponent.description\"\n    >\n      <Card\n        HeadingComponent={\n          <Button\n            preset=\"reversed\"\n            text=\"HeadingComponent\"\n            LeftAccessory={(props) => <Icon style={props.style} icon=\"ladybug\" />}\n          />\n        }\n        ContentComponent={\n          <Button\n            style={{ marginVertical: theme.spacing.sm }}\n            text=\"ContentComponent\"\n            LeftAccessory={(props) => <Icon style={props.style} icon=\"ladybug\" />}\n          />\n        }\n        FooterComponent={\n          <Button\n            preset=\"reversed\"\n            text=\"FooterComponent\"\n            LeftAccessory={(props) => <Icon style={props.style} icon=\"ladybug\" />}\n          />\n        }\n      />\n      <DemoDivider />\n      <Card\n        headingTx=\"demoCard:useCase.customComponent.rightComponent\"\n        verticalAlignment=\"center\"\n        RightComponent={\n          <AutoImage\n            maxWidth={80}\n            maxHeight={60}\n            style={{ alignSelf: \"center\" }}\n            source={{\n              uri: \"https://user-images.githubusercontent.com/1775841/184508739-f90d0ce5-7219-42fd-a91f-3382d016eae0.png\",\n            }}\n          />\n        }\n      />\n      <DemoDivider />\n      <Card\n        preset=\"reversed\"\n        headingTx=\"demoCard:useCase.customComponent.leftComponent\"\n        verticalAlignment=\"center\"\n        LeftComponent={\n          <AutoImage\n            maxWidth={80}\n            maxHeight={60}\n            style={{ alignSelf: \"center\" }}\n            source={{\n              uri: \"https://user-images.githubusercontent.com/1775841/184508739-f90d0ce5-7219-42fd-a91f-3382d016eae0.png\",\n            }}\n          />\n        }\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoCard:useCase.style.name\"\n      description=\"demoCard:useCase.style.description\"\n    >\n      <Card\n        headingTx=\"demoCard:useCase.style.heading\"\n        headingStyle={{ color: theme.colors.error }}\n        contentTx=\"demoCard:useCase.style.content\"\n        contentStyle={{\n          backgroundColor: theme.colors.error,\n          color: theme.colors.palette.neutral100,\n        }}\n        footerTx=\"demoCard:useCase.style.footer\"\n        footerStyle={{\n          textDecorationLine: \"underline line-through\",\n          textDecorationStyle: \"dashed\",\n          color: theme.colors.error,\n          textDecorationColor: theme.colors.error,\n        }}\n        style={{\n          shadowRadius: 5,\n          shadowColor: theme.colors.error,\n          shadowOpacity: 0.5,\n        }}\n      />\n    </DemoUseCase>,\n  ],\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoEmptyState.tsx",
    "content": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { EmptyState } from \"@/components/EmptyState\"\n\nimport { DemoDivider } from \"../DemoDivider\"\nimport { Demo } from \"./types\"\nimport { DemoUseCase } from \"../DemoUseCase\"\n\nexport const DemoEmptyState: Demo = {\n  name: \"EmptyState\",\n  description: \"demoEmptyState:description\",\n  data: ({ theme }) => [\n    <DemoUseCase\n      name=\"demoEmptyState:useCase.presets.name\"\n      description=\"demoEmptyState:useCase.presets.description\"\n    >\n      <EmptyState preset=\"generic\" />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoEmptyState:useCase.passingContent.name\"\n      description=\"demoEmptyState:useCase.passingContent.description\"\n    >\n      <EmptyState\n        imageSource={require(\"@assets/images/logo.png\")}\n        headingTx=\"demoEmptyState:useCase.passingContent.customizeImageHeading\"\n        contentTx=\"demoEmptyState:useCase.passingContent.customizeImageContent\"\n      />\n\n      <DemoDivider size={30} line />\n\n      <EmptyState\n        headingTx=\"demoEmptyState:useCase.passingContent.viaHeadingProp\"\n        contentTx=\"demoEmptyState:useCase.passingContent.viaContentProp\"\n        buttonTx=\"demoEmptyState:useCase.passingContent.viaButtonProp\"\n      />\n\n      <DemoDivider size={30} line />\n\n      <EmptyState\n        headingTx=\"demoShowroomScreen:demoViaSpecifiedTxProp\"\n        headingTxOptions={{ prop: \"heading\" }}\n        contentTx=\"demoShowroomScreen:demoViaSpecifiedTxProp\"\n        contentTxOptions={{ prop: \"content\" }}\n        buttonTx=\"demoShowroomScreen:demoViaSpecifiedTxProp\"\n        buttonTxOptions={{ prop: \"button\" }}\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoEmptyState:useCase.styling.name\"\n      description=\"demoEmptyState:useCase.styling.description\"\n    >\n      <EmptyState\n        preset=\"generic\"\n        style={{ backgroundColor: theme.colors.error, paddingVertical: 20 }}\n        imageStyle={{ height: 75, tintColor: theme.colors.palette.neutral100 }}\n        ImageProps={{ resizeMode: \"contain\" }}\n        headingStyle={{\n          color: theme.colors.palette.neutral100,\n          textDecorationLine: \"underline\",\n          textDecorationColor: theme.colors.palette.neutral100,\n        }}\n        contentStyle={{\n          color: theme.colors.palette.neutral100,\n          textDecorationLine: \"underline\",\n          textDecorationColor: theme.colors.palette.neutral100,\n        }}\n        buttonStyle={{ alignSelf: \"center\", backgroundColor: theme.colors.palette.neutral100 }}\n        buttonTextStyle={{ color: theme.colors.error }}\n        ButtonProps={{\n          preset: \"reversed\",\n        }}\n      />\n    </DemoUseCase>,\n  ],\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoHeader.tsx",
    "content": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Header } from \"@/components/Header\"\nimport { Icon } from \"@/components/Icon\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { DemoDivider } from \"../DemoDivider\"\nimport { Demo } from \"./types\"\nimport { DemoUseCase } from \"../DemoUseCase\"\n\nconst $rightAlignTitle: TextStyle = {\n  textAlign: \"right\",\n}\n\nconst $customLeftAction: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.error,\n  flexGrow: 0,\n  flexBasis: 100,\n  height: \"100%\",\n  flexWrap: \"wrap\",\n  overflow: \"hidden\",\n})\n\nconst $customTitle: ThemedStyle<TextStyle> = ({ colors }) => ({\n  textDecorationLine: \"underline line-through\",\n  textDecorationStyle: \"dashed\",\n  color: colors.error,\n  textDecorationColor: colors.error,\n})\n\nconst $customWhiteTitle: ThemedStyle<TextStyle> = ({ colors }) => ({\n  color: colors.palette.neutral100,\n})\n\nexport const DemoHeader: Demo = {\n  name: \"Header\",\n  description: \"demoHeader:description\",\n  data: ({ theme, themed }) => [\n    <DemoUseCase\n      name=\"demoHeader:useCase.actionIcons.name\"\n      description=\"demoHeader:useCase.actionIcons.description\"\n    >\n      <Header\n        titleTx=\"demoHeader:useCase.actionIcons.leftIconTitle\"\n        leftIcon=\"ladybug\"\n        safeAreaEdges={[]}\n      />\n      <DemoDivider size={24} />\n      <Header\n        titleTx=\"demoHeader:useCase.actionIcons.rightIconTitle\"\n        rightIcon=\"ladybug\"\n        safeAreaEdges={[]}\n      />\n      <DemoDivider size={24} />\n      <Header\n        titleTx=\"demoHeader:useCase.actionIcons.bothIconsTitle\"\n        leftIcon=\"ladybug\"\n        rightIcon=\"ladybug\"\n        safeAreaEdges={[]}\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoHeader:useCase.actionText.name\"\n      description=\"demoHeader:useCase.actionText.description\"\n    >\n      <Header\n        titleTx=\"demoHeader:useCase.actionText.leftTxTitle\"\n        leftTx=\"demoShowroomScreen:demoHeaderTxExample\"\n        safeAreaEdges={[]}\n      />\n      <DemoDivider size={24} />\n      <Header\n        titleTx=\"demoHeader:useCase.actionText.rightTextTitle\"\n        rightText=\"Yay\"\n        safeAreaEdges={[]}\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoHeader:useCase.customActionComponents.name\"\n      description=\"demoHeader:useCase.customActionComponents.description\"\n    >\n      <Header\n        titleTx=\"demoHeader:useCase.customActionComponents.customLeftActionTitle\"\n        titleMode=\"flex\"\n        titleStyle={$rightAlignTitle}\n        LeftActionComponent={\n          <View style={themed([$styles.row, $customLeftAction])}>\n            {Array.from({ length: 20 }, (x, i) => i).map((i) => (\n              <Icon key={i} icon=\"ladybug\" color={theme.colors.palette.neutral100} size={20} />\n            ))}\n          </View>\n        }\n        safeAreaEdges={[]}\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoHeader:useCase.titleModes.name\"\n      description=\"demoHeader:useCase.titleModes.description\"\n    >\n      <Header\n        titleTx=\"demoHeader:useCase.titleModes.centeredTitle\"\n        leftIcon=\"ladybug\"\n        rightText=\"Hooray\"\n        safeAreaEdges={[]}\n      />\n      <DemoDivider size={24} />\n      <Header\n        titleTx=\"demoHeader:useCase.titleModes.flexTitle\"\n        titleMode=\"flex\"\n        leftIcon=\"ladybug\"\n        rightText=\"Hooray\"\n        safeAreaEdges={[]}\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoHeader:useCase.styling.name\"\n      description=\"demoHeader:useCase.styling.description\"\n    >\n      <Header\n        titleTx=\"demoHeader:useCase.styling.styledTitle\"\n        titleStyle={themed($customTitle)}\n        safeAreaEdges={[]}\n      />\n      <DemoDivider size={24} />\n      <Header\n        titleTx=\"demoHeader:useCase.styling.styledWrapperTitle\"\n        titleStyle={themed($customWhiteTitle)}\n        backgroundColor={theme.colors.error}\n        style={{ height: 35 }}\n        safeAreaEdges={[]}\n      />\n      <DemoDivider size={24} />\n      <Header\n        titleTx=\"demoHeader:useCase.styling.tintedIconsTitle\"\n        titleStyle={themed($customWhiteTitle)}\n        backgroundColor={theme.colors.error}\n        leftIcon=\"ladybug\"\n        leftIconColor={theme.colors.palette.neutral100}\n        safeAreaEdges={[]}\n      />\n    </DemoUseCase>,\n  ],\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoIcon.tsx",
    "content": "/* eslint-disable react/jsx-key */\nimport { ImageStyle, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Icon, iconRegistry, type IconTypes } from \"@/components/Icon\"\nimport { Text } from \"@/components/Text\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { Demo } from \"./types\"\nimport { DemoUseCase } from \"../DemoUseCase\"\n\nconst $demoIconContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  padding: spacing.xs,\n})\n\nconst $iconTile: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  width: \"33.333%\",\n  alignItems: \"center\",\n  paddingVertical: spacing.xs,\n})\n\nconst $iconTileLabel: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({\n  marginTop: spacing.xxs,\n  color: colors.textDim,\n})\n\nconst $customIconContainer: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({\n  padding: spacing.md,\n  backgroundColor: colors.palette.angry500,\n})\n\nconst $customIcon: ThemedStyle<ImageStyle> = ({ colors }) => ({\n  tintColor: colors.palette.neutral100,\n})\n\nexport const DemoIcon: Demo = {\n  name: \"Icon\",\n  description: \"demoIcon:description\",\n  data: ({ theme, themed }) => [\n    <DemoUseCase\n      name=\"demoIcon:useCase.icons.name\"\n      description=\"demoIcon:useCase.icons.description\"\n      layout=\"row\"\n      itemStyle={$styles.flexWrap}\n    >\n      {Object.keys(iconRegistry).map((icon) => (\n        <View key={icon} style={themed($iconTile)}>\n          <Icon icon={icon as IconTypes} color={theme.colors.tint} size={35} />\n\n          <Text size=\"xs\" style={themed($iconTileLabel)}>\n            {icon}\n          </Text>\n        </View>\n      ))}\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoIcon:useCase.size.name\"\n      description=\"demoIcon:useCase.size.description\"\n      layout=\"row\"\n    >\n      <Icon icon=\"ladybug\" containerStyle={themed($demoIconContainer)} />\n      <Icon icon=\"ladybug\" size={35} containerStyle={themed($demoIconContainer)} />\n      <Icon icon=\"ladybug\" size={50} containerStyle={themed($demoIconContainer)} />\n      <Icon icon=\"ladybug\" size={75} containerStyle={themed($demoIconContainer)} />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoIcon:useCase.color.name\"\n      description=\"demoIcon:useCase.color.description\"\n      layout=\"row\"\n    >\n      <Icon\n        icon=\"ladybug\"\n        color={theme.colors.palette.accent500}\n        containerStyle={themed($demoIconContainer)}\n      />\n      <Icon\n        icon=\"ladybug\"\n        color={theme.colors.palette.primary500}\n        containerStyle={themed($demoIconContainer)}\n      />\n      <Icon\n        icon=\"ladybug\"\n        color={theme.colors.palette.secondary500}\n        containerStyle={themed($demoIconContainer)}\n      />\n      <Icon\n        icon=\"ladybug\"\n        color={theme.colors.palette.neutral700}\n        containerStyle={themed($demoIconContainer)}\n      />\n      <Icon\n        icon=\"ladybug\"\n        color={theme.colors.palette.angry500}\n        containerStyle={themed($demoIconContainer)}\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoIcon:useCase.styling.name\"\n      description=\"demoIcon:useCase.styling.description\"\n      layout=\"row\"\n    >\n      <Icon\n        icon=\"ladybug\"\n        style={themed($customIcon)}\n        size={40}\n        containerStyle={themed($customIconContainer)}\n      />\n    </DemoUseCase>,\n  ],\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx",
    "content": "/* eslint-disable react/jsx-key */\nimport { TextStyle, View, ViewStyle } from \"react-native\"\nimport { FlatList } from \"react-native-gesture-handler\"\n\nimport { Icon } from \"@/components/Icon\"\nimport { ListItem } from \"@/components/ListItem\"\nimport { Text } from \"@/components/Text\"\nimport { translate } from \"@/i18n/translate\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { DemoDivider } from \"../DemoDivider\"\nimport { Demo } from \"./types\"\nimport { DemoUseCase } from \"../DemoUseCase\"\n\nconst listData =\n  `Tempor Id Ea Aliqua Pariatur Aliquip. Irure Minim Voluptate Consectetur Consequat Sint Esse Proident Irure. Nostrud Elit Veniam Nostrud Excepteur Minim Deserunt Quis Dolore Velit Nulla Irure Voluptate Tempor. Occaecat Amet Laboris Nostrud Qui Do Quis Lorem Ex Elit Fugiat Deserunt. In Pariatur Excepteur Exercitation Ex Incididunt Qui Mollit Dolor Sit Non. Culpa Officia Minim Cillum Exercitation Voluptate Proident Laboris Et Est Reprehenderit Quis Pariatur Nisi`\n    .split(\".\")\n    .map((item) => item.trim())\n\nconst $customLeft: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.error,\n  flexGrow: 0,\n  flexBasis: 60,\n  height: \"100%\",\n  flexWrap: \"wrap\",\n  overflow: \"hidden\",\n})\n\nconst $customTextStyle: ThemedStyle<TextStyle> = ({ colors }) => ({\n  color: colors.error,\n})\n\nconst $customTouchableStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.error,\n})\n\nconst $customContainerStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  borderTopWidth: 5,\n  borderTopColor: colors.palette.neutral100,\n})\n\nconst $listStyle: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({\n  height: 148,\n  paddingHorizontal: spacing.xs,\n  backgroundColor: colors.palette.neutral200,\n})\n\nexport const DemoListItem: Demo = {\n  name: \"ListItem\",\n  description: \"demoListItem:description\",\n  data: ({ theme, themed }) => [\n    <DemoUseCase\n      name=\"demoListItem:useCase.height.name\"\n      description=\"demoListItem:useCase.height.description\"\n    >\n      <ListItem topSeparator>{translate(\"demoListItem:useCase.height.defaultHeight\")}</ListItem>\n\n      <ListItem topSeparator height={100}>\n        {translate(\"demoListItem:useCase.height.customHeight\")}\n      </ListItem>\n\n      <ListItem topSeparator>{translate(\"demoListItem:useCase.height.textHeight\")}</ListItem>\n\n      <ListItem topSeparator bottomSeparator TextProps={{ numberOfLines: 1 }}>\n        {translate(\"demoListItem:useCase.height.longText\")}\n      </ListItem>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoListItem:useCase.separators.name\"\n      description=\"demoListItem:useCase.separators.description\"\n    >\n      <ListItem topSeparator>{translate(\"demoListItem:useCase.separators.topSeparator\")}</ListItem>\n\n      <DemoDivider size={40} />\n\n      <ListItem topSeparator bottomSeparator>\n        {translate(\"demoListItem:useCase.separators.topAndBottomSeparator\")}\n      </ListItem>\n\n      <DemoDivider size={40} />\n\n      <ListItem bottomSeparator>\n        {translate(\"demoListItem:useCase.separators.bottomSeparator\")}\n      </ListItem>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoListItem:useCase.icons.name\"\n      description=\"demoListItem:useCase.icons.description\"\n    >\n      <ListItem topSeparator leftIcon=\"ladybug\">\n        {translate(\"demoListItem:useCase.icons.leftIcon\")}\n      </ListItem>\n\n      <ListItem topSeparator rightIcon=\"ladybug\">\n        {translate(\"demoListItem:useCase.icons.rightIcon\")}\n      </ListItem>\n\n      <ListItem topSeparator bottomSeparator rightIcon=\"ladybug\" leftIcon=\"ladybug\">\n        {translate(\"demoListItem:useCase.icons.leftRightIcons\")}\n      </ListItem>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoListItem:useCase.customLeftRight.name\"\n      description=\"demoListItem:useCase.customLeftRight.description\"\n    >\n      <ListItem\n        topSeparator\n        LeftComponent={\n          <View style={themed([$styles.row, $customLeft, { marginEnd: theme.spacing.md }])}>\n            {Array.from({ length: 9 }, (x, i) => i).map((i) => (\n              <Icon key={i} icon=\"ladybug\" color={theme.colors.palette.neutral100} size={20} />\n            ))}\n          </View>\n        }\n      >\n        {translate(\"demoListItem:useCase.customLeftRight.customLeft\")}\n      </ListItem>\n\n      <ListItem\n        topSeparator\n        bottomSeparator\n        RightComponent={\n          <View style={themed([$styles.row, $customLeft, { marginStart: theme.spacing.md }])}>\n            {Array.from({ length: 9 }, (x, i) => i).map((i) => (\n              <Icon key={i} icon=\"ladybug\" color={theme.colors.palette.neutral100} size={20} />\n            ))}\n          </View>\n        }\n      >\n        {translate(\"demoListItem:useCase.customLeftRight.customRight\")}\n      </ListItem>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoListItem:useCase.passingContent.name\"\n      description=\"demoListItem:useCase.passingContent.description\"\n    >\n      <ListItem topSeparator text={translate(\"demoListItem:useCase.passingContent.children\")} />\n      <ListItem topSeparator tx=\"demoShowroomScreen:demoViaTxProp\" />\n      <ListItem topSeparator>{translate(\"demoListItem:useCase.passingContent.children\")}</ListItem>\n      <ListItem topSeparator bottomSeparator>\n        <Text>\n          <Text preset=\"bold\">\n            {translate(\"demoListItem:useCase.passingContent.nestedChildren1\")}\n          </Text>\n          {` `}\n          <Text preset=\"default\">\n            {translate(\"demoListItem:useCase.passingContent.nestedChildren2\")}\n          </Text>\n        </Text>\n      </ListItem>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoListItem:useCase.listIntegration.name\"\n      description=\"demoListItem:useCase.listIntegration.description\"\n    >\n      <View style={themed($listStyle)}>\n        <FlatList<string>\n          data={listData}\n          keyExtractor={(item, index) => `${item}-${index}`}\n          renderItem={({ item, index }) => (\n            <ListItem\n              text={item}\n              rightIcon=\"caretRight\"\n              TextProps={{ numberOfLines: 1 }}\n              topSeparator={index !== 0}\n            />\n          )}\n        />\n      </View>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoListItem:useCase.styling.name\"\n      description=\"demoListItem:useCase.styling.description\"\n    >\n      <ListItem topSeparator textStyle={themed($customTextStyle)}>\n        {translate(\"demoListItem:useCase.styling.styledText\")}\n      </ListItem>\n\n      <ListItem\n        topSeparator\n        textStyle={{ color: theme.colors.palette.neutral100 }}\n        style={themed($customTouchableStyle)}\n      >\n        {translate(\"demoListItem:useCase.styling.styledText\")}\n      </ListItem>\n\n      <ListItem\n        topSeparator\n        textStyle={{ color: theme.colors.palette.neutral100 }}\n        style={themed($customTouchableStyle)}\n        containerStyle={themed($customContainerStyle)}\n      >\n        {translate(\"demoListItem:useCase.styling.styledContainer\")}\n      </ListItem>\n      <ListItem\n        topSeparator\n        textStyle={{ color: theme.colors.palette.neutral100 }}\n        style={themed($customTouchableStyle)}\n        containerStyle={themed($customContainerStyle)}\n        rightIcon=\"ladybug\"\n        leftIcon=\"ladybug\"\n        rightIconColor={theme.colors.palette.neutral100}\n        leftIconColor={theme.colors.palette.neutral100}\n      >\n        {translate(\"demoListItem:useCase.styling.tintedIcons\")}\n      </ListItem>\n    </DemoUseCase>,\n  ],\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoText.tsx",
    "content": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { Text } from \"@/components/Text\"\nimport { translate } from \"@/i18n/translate\"\n\nimport { DemoDivider } from \"../DemoDivider\"\nimport { Demo } from \"./types\"\nimport { DemoUseCase } from \"../DemoUseCase\"\n\nexport const DemoText: Demo = {\n  name: \"Text\",\n  description: \"demoText:description\",\n  data: ({ theme }) => [\n    <DemoUseCase\n      name=\"demoText:useCase.presets.name\"\n      description=\"demoText:useCase.presets.description\"\n    >\n      <Text>{translate(\"demoText:useCase.presets.default\")}</Text>\n\n      <DemoDivider />\n\n      <Text preset=\"bold\">{translate(\"demoText:useCase.presets.bold\")}</Text>\n\n      <DemoDivider />\n\n      <Text preset=\"subheading\">{translate(\"demoText:useCase.presets.subheading\")}</Text>\n\n      <DemoDivider />\n\n      <Text preset=\"heading\">{translate(\"demoText:useCase.presets.heading\")}</Text>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoText:useCase.sizes.name\"\n      description=\"demoText:useCase.sizes.description\"\n    >\n      <Text size=\"xs\">{translate(\"demoText:useCase.sizes.xs\")}</Text>\n\n      <DemoDivider />\n\n      <Text size=\"sm\">{translate(\"demoText:useCase.sizes.sm\")}</Text>\n\n      <DemoDivider />\n\n      <Text size=\"md\">{translate(\"demoText:useCase.sizes.md\")}</Text>\n\n      <DemoDivider />\n\n      <Text size=\"lg\">{translate(\"demoText:useCase.sizes.lg\")}</Text>\n\n      <DemoDivider />\n\n      <Text size=\"xl\">{translate(\"demoText:useCase.sizes.xl\")}</Text>\n\n      <DemoDivider />\n\n      <Text size=\"xxl\">{translate(\"demoText:useCase.sizes.xxl\")}</Text>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoText:useCase.weights.name\"\n      description=\"demoText:useCase.weights.description\"\n    >\n      <Text weight=\"light\">{translate(\"demoText:useCase.weights.light\")}</Text>\n\n      <DemoDivider />\n\n      <Text weight=\"normal\">{translate(\"demoText:useCase.weights.normal\")}</Text>\n\n      <DemoDivider />\n\n      <Text weight=\"medium\">{translate(\"demoText:useCase.weights.medium\")}</Text>\n\n      <DemoDivider />\n\n      <Text weight=\"semiBold\">{translate(\"demoText:useCase.weights.semibold\")}</Text>\n\n      <DemoDivider />\n\n      <Text weight=\"bold\">{translate(\"demoText:useCase.weights.bold\")}</Text>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoText:useCase.passingContent.name\"\n      description=\"demoText:useCase.passingContent.description\"\n    >\n      <Text text={translate(\"demoText:useCase.passingContent.viaText\")} />\n\n      <DemoDivider />\n\n      <Text>\n        <Text tx=\"demoText:useCase.passingContent.viaTx\" />\n        <Text tx=\"demoShowroomScreen:lorem2Sentences\" />\n      </Text>\n\n      <DemoDivider />\n\n      <Text>{translate(\"demoText:useCase.passingContent.children\")}</Text>\n\n      <DemoDivider />\n\n      <Text>\n        <Text>{translate(\"demoText:useCase.passingContent.nestedChildren\")}</Text>\n        <Text preset=\"bold\">{translate(\"demoText:useCase.passingContent.nestedChildren2\")}</Text>\n        {` `}\n        <Text preset=\"default\">{translate(\"demoText:useCase.passingContent.nestedChildren3\")}</Text>\n        {` `}\n        <Text preset=\"bold\"> {translate(\"demoText:useCase.passingContent.nestedChildren4\")}</Text>\n      </Text>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoText:useCase.styling.name\"\n      description=\"demoText:useCase.styling.description\"\n    >\n      <Text>\n        <Text style={{ color: theme.colors.error }}>\n          {translate(\"demoText:useCase.styling.text\")}\n        </Text>\n        {` `}\n        <Text\n          style={{\n            color: theme.colors.palette.neutral100,\n            backgroundColor: theme.colors.error,\n          }}\n        >\n          {translate(\"demoText:useCase.styling.text2\")}\n        </Text>\n        {` `}\n        <Text\n          style={{\n            textDecorationLine: \"underline line-through\",\n            textDecorationStyle: \"dashed\",\n            color: theme.colors.error,\n            textDecorationColor: theme.colors.error,\n          }}\n        >\n          {translate(\"demoText:useCase.styling.text3\")}\n        </Text>\n      </Text>\n    </DemoUseCase>,\n  ],\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoTextField.tsx",
    "content": "/* eslint-disable react/jsx-key */\nimport { TextStyle, ViewStyle } from \"react-native\"\n\nimport { Icon } from \"@/components/Icon\"\nimport { TextField } from \"@/components/TextField\"\nimport type { ThemedStyle } from \"@/theme/types\"\n\nimport { DemoDivider } from \"../DemoDivider\"\nimport { Demo } from \"./types\"\nimport { DemoUseCase } from \"../DemoUseCase\"\n\nconst $customInputStyle: ThemedStyle<TextStyle> = ({ colors }) => ({\n  backgroundColor: colors.error,\n  color: colors.palette.neutral100,\n})\n\nconst $customInputWrapperStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.error,\n  borderColor: colors.palette.neutral800,\n})\n\nconst $customContainerStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.error,\n})\n\nconst $customLabelAndHelperStyle: ThemedStyle<TextStyle> = ({ colors }) => ({\n  color: colors.palette.neutral100,\n})\n\nconst $customInputWithAbsoluteAccessoriesStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginHorizontal: spacing.xxl,\n})\n\nconst $customLeftAccessoryStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.error,\n  position: \"absolute\",\n  left: 0,\n})\n\nconst $customRightAccessoryStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  backgroundColor: colors.error,\n  position: \"absolute\",\n  right: 0,\n})\n\nexport const DemoTextField: Demo = {\n  name: \"TextField\",\n  description: \"demoTextField:description\",\n  data: ({ themed }) => [\n    <DemoUseCase\n      name=\"demoTextField:useCase.statuses.name\"\n      description=\"demoTextField:useCase.statuses.description\"\n    >\n      <TextField\n        value=\"Labore occaecat in id eu commodo aliquip occaecat veniam officia pariatur.\"\n        labelTx=\"demoTextField:useCase.statuses.noStatus.label\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.statuses.noStatus.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        placeholderTx=\"demoTextField:useCase.statuses.noStatus.placeholder\"\n        placeholderTxOptions={{ prop: \"placeholder\" }}\n      />\n\n      <DemoDivider size={24} />\n\n      <TextField\n        status=\"error\"\n        value=\"Est Lorem duis sunt sunt duis proident minim elit dolore incididunt pariatur eiusmod anim cillum.\"\n        labelTx=\"demoTextField:useCase.statuses.error.label\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.statuses.error.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        placeholderTx=\"demoTextField:useCase.statuses.error.placeholder\"\n        placeholderTxOptions={{ prop: \"placeholder\" }}\n      />\n\n      <DemoDivider size={24} />\n\n      <TextField\n        status=\"disabled\"\n        value=\"Eu ipsum mollit non minim voluptate nulla fugiat aliqua ullamco aute consectetur nulla nulla amet.\"\n        labelTx=\"demoTextField:useCase.statuses.disabled.label\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.statuses.disabled.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        placeholderTx=\"demoTextField:useCase.statuses.disabled.placeholder\"\n        placeholderTxOptions={{ prop: \"placeholder\" }}\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoTextField:useCase.passingContent.name\"\n      description=\"demoTextField:useCase.passingContent.description\"\n    >\n      <TextField\n        labelTx=\"demoTextField:useCase.passingContent.viaLabel.labelTx\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.passingContent.viaLabel.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        placeholderTx=\"demoTextField:useCase.passingContent.viaLabel.placeholder\"\n        placeholderTxOptions={{ prop: \"placeholder\" }}\n      />\n\n      <DemoDivider size={24} />\n\n      <TextField\n        labelTx=\"demoShowroomScreen:demoViaSpecifiedTxProp\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoShowroomScreen:demoViaSpecifiedTxProp\"\n        helperTxOptions={{ prop: \"helper\" }}\n        placeholderTx=\"demoShowroomScreen:demoViaSpecifiedTxProp\"\n        placeholderTxOptions={{ prop: \"placeholder\" }}\n      />\n\n      <DemoDivider size={24} />\n\n      <TextField\n        value=\"Reprehenderit Lorem magna non consequat ullamco cupidatat.\"\n        labelTx=\"demoTextField:useCase.passingContent.rightAccessory.label\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.passingContent.rightAccessory.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        RightAccessory={(props) => <Icon icon=\"ladybug\" containerStyle={props.style} size={21} />}\n      />\n\n      <DemoDivider size={24} />\n\n      <TextField\n        labelTx=\"demoTextField:useCase.passingContent.leftAccessory.label\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.passingContent.leftAccessory.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        value=\"Eiusmod exercitation mollit elit magna occaecat eiusmod Lorem minim veniam.\"\n        LeftAccessory={(props) => <Icon icon=\"ladybug\" containerStyle={props.style} size={21} />}\n      />\n\n      <DemoDivider size={24} />\n\n      <TextField\n        labelTx=\"demoTextField:useCase.passingContent.supportsMultiline.label\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.passingContent.supportsMultiline.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        value=\"Eiusmod exercitation mollit elit magna occaecat eiusmod Lorem minim veniam. Laborum Lorem velit velit minim irure ad in ut adipisicing consectetur.\"\n        multiline\n        RightAccessory={(props) => <Icon icon=\"ladybug\" containerStyle={props.style} size={21} />}\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoTextField:useCase.styling.name\"\n      description=\"demoTextField:useCase.styling.description\"\n    >\n      <TextField\n        labelTx=\"demoTextField:useCase.styling.styleInput.label\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.styling.styleInput.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        value=\"Laborum cupidatat aliquip sunt sunt voluptate sint sit proident sunt mollit exercitation ullamco ea elit.\"\n        style={themed($customInputStyle)}\n      />\n\n      <DemoDivider size={24} />\n\n      <TextField\n        labelTx=\"demoTextField:useCase.styling.styleInputWrapper.label\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.styling.styleInputWrapper.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        value=\"Aute velit esse dolore pariatur exercitation irure nulla do sunt in duis mollit duis et.\"\n        inputWrapperStyle={themed($customInputWrapperStyle)}\n        style={themed($customInputStyle)}\n      />\n\n      <DemoDivider size={24} />\n\n      <TextField\n        labelTx=\"demoTextField:useCase.styling.styleContainer.label\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.styling.styleContainer.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        value=\"Aliquip proident commodo adipisicing non adipisicing Lorem excepteur ullamco voluptate laborum.\"\n        style={themed($customInputStyle)}\n        containerStyle={themed($customContainerStyle)}\n        inputWrapperStyle={themed($customInputWrapperStyle)}\n      />\n\n      <DemoDivider size={24} />\n\n      <TextField\n        labelTx=\"demoTextField:useCase.styling.styleLabel.label\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.styling.styleLabel.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        value=\"Ex culpa in consectetur dolor irure velit.\"\n        style={themed($customInputStyle)}\n        containerStyle={themed($customContainerStyle)}\n        inputWrapperStyle={themed($customInputWrapperStyle)}\n        HelperTextProps={{ style: themed($customLabelAndHelperStyle) }}\n        LabelTextProps={{ style: themed($customLabelAndHelperStyle) }}\n      />\n\n      <DemoDivider size={24} />\n\n      <TextField\n        labelTx=\"demoTextField:useCase.styling.styleAccessories.label\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoTextField:useCase.styling.styleAccessories.helper\"\n        helperTxOptions={{ prop: \"helper\" }}\n        value=\"Aute nisi dolore fugiat anim mollit nulla ex minim ipsum ex elit.\"\n        style={themed($customInputWithAbsoluteAccessoriesStyle)}\n        LeftAccessory={() => (\n          <Icon\n            icon=\"ladybug\"\n            containerStyle={themed($customLeftAccessoryStyle)}\n            color=\"white\"\n            size={41}\n          />\n        )}\n        RightAccessory={() => (\n          <Icon\n            icon=\"ladybug\"\n            containerStyle={themed($customRightAccessoryStyle)}\n            color=\"white\"\n            size={41}\n          />\n        )}\n      />\n    </DemoUseCase>,\n  ],\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoToggle.tsx",
    "content": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { useState } from \"react\"\nimport { TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Text } from \"@/components/Text\"\nimport { Checkbox, CheckboxToggleProps } from \"@/components/Toggle/Checkbox\"\nimport { Radio, RadioToggleProps } from \"@/components/Toggle/Radio\"\nimport { Switch, SwitchToggleProps } from \"@/components/Toggle/Switch\"\nimport { translate } from \"@/i18n/translate\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { $styles } from \"@/theme/styles\"\n\nimport { DemoDivider } from \"../DemoDivider\"\nimport { Demo } from \"./types\"\nimport { DemoUseCase } from \"../DemoUseCase\"\n\nfunction ControlledCheckbox(props: CheckboxToggleProps) {\n  const [value, setValue] = useState(props.value || false)\n  return <Checkbox {...props} value={value} onPress={() => setValue(!value)} />\n}\n\nfunction ControlledRadio(props: RadioToggleProps) {\n  const [value, setValue] = useState(props.value || false)\n  return <Radio {...props} value={value} onPress={() => setValue(!value)} />\n}\n\nfunction ControlledSwitch(props: SwitchToggleProps) {\n  const [value, setValue] = useState(props.value || false)\n  return <Switch {...props} value={value} onPress={() => setValue(!value)} />\n}\n\nconst $centeredOneThirdCol: ViewStyle = {\n  width: \"33.33333%\",\n  alignItems: \"center\",\n  justifyContent: \"center\",\n}\nconst $centeredText: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  textAlign: \"center\",\n  width: \"100%\",\n  marginTop: spacing.xs,\n})\n\nexport const DemoToggle: Demo = {\n  name: \"Toggle\",\n  description: \"demoToggle:description\",\n  data: ({ theme, themed }) => [\n    <DemoUseCase\n      name=\"demoToggle:useCase.variants.name\"\n      description=\"demoToggle:useCase.variants.description\"\n    >\n      <ControlledCheckbox\n        labelTx=\"demoToggle:useCase.variants.checkbox.label\"\n        helperTx=\"demoToggle:useCase.variants.checkbox.helper\"\n      />\n      <DemoDivider size={24} />\n      <ControlledRadio\n        labelTx=\"demoToggle:useCase.variants.radio.label\"\n        helperTx=\"demoToggle:useCase.variants.radio.helper\"\n      />\n      <DemoDivider size={24} />\n      <ControlledSwitch\n        labelTx=\"demoToggle:useCase.variants.switch.label\"\n        helperTx=\"demoToggle:useCase.variants.switch.helper\"\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoToggle:useCase.statuses.name\"\n      description=\"demoToggle:useCase.statuses.description\"\n      layout=\"row\"\n      itemStyle={$styles.flexWrap}\n    >\n      <ControlledCheckbox containerStyle={$centeredOneThirdCol} />\n      <ControlledRadio containerStyle={$centeredOneThirdCol} />\n      <ControlledSwitch containerStyle={$centeredOneThirdCol} />\n      <DemoDivider style={{ width: \"100%\" }} />\n      <ControlledCheckbox value containerStyle={$centeredOneThirdCol} />\n      <ControlledRadio value containerStyle={$centeredOneThirdCol} />\n      <ControlledSwitch value containerStyle={$centeredOneThirdCol} />\n      <Text preset=\"formHelper\" style={themed($centeredText)}>\n        {translate(\"demoToggle:useCase.statuses.noStatus\")}\n      </Text>\n\n      <DemoDivider size={24} style={{ width: \"100%\" }} />\n\n      <ControlledCheckbox status=\"error\" containerStyle={$centeredOneThirdCol} />\n      <ControlledRadio status=\"error\" containerStyle={$centeredOneThirdCol} />\n      <ControlledSwitch status=\"error\" containerStyle={$centeredOneThirdCol} />\n      <DemoDivider style={{ width: \"100%\" }} />\n      <ControlledCheckbox value status=\"error\" containerStyle={$centeredOneThirdCol} />\n      <ControlledRadio value status=\"error\" containerStyle={$centeredOneThirdCol} />\n      <ControlledSwitch value status=\"error\" containerStyle={$centeredOneThirdCol} />\n      <Text preset=\"formHelper\" style={themed($centeredText)}>\n        {translate(\"demoToggle:useCase.statuses.errorStatus\")}\n      </Text>\n\n      <DemoDivider size={24} style={{ width: \"100%\" }} />\n\n      <ControlledCheckbox status=\"disabled\" containerStyle={$centeredOneThirdCol} />\n      <ControlledRadio status=\"disabled\" containerStyle={$centeredOneThirdCol} />\n      <ControlledSwitch status=\"disabled\" containerStyle={$centeredOneThirdCol} />\n      <DemoDivider style={{ width: \"100%\" }} />\n      <ControlledCheckbox value status=\"disabled\" containerStyle={$centeredOneThirdCol} />\n      <ControlledRadio value status=\"disabled\" containerStyle={$centeredOneThirdCol} />\n      <ControlledSwitch value status=\"disabled\" containerStyle={$centeredOneThirdCol} />\n      <Text preset=\"formHelper\" style={themed($centeredText)}>\n        {translate(\"demoToggle:useCase.statuses.disabledStatus\")}\n      </Text>\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoToggle:useCase.passingContent.name\"\n      description=\"demoToggle:useCase.passingContent.description\"\n    >\n      <ControlledCheckbox\n        value\n        labelTx=\"demoToggle:useCase.passingContent.useCase.checkBox.label\"\n        helperTx=\"demoToggle:useCase.passingContent.useCase.checkBox.helper\"\n      />\n      <DemoDivider size={24} />\n      <ControlledRadio\n        value\n        labelTx=\"demoShowroomScreen:demoViaSpecifiedTxProp\"\n        labelTxOptions={{ prop: \"label\" }}\n        helperTx=\"demoShowroomScreen:demoViaSpecifiedTxProp\"\n        helperTxOptions={{ prop: \"helper\" }}\n      />\n      <DemoDivider size={24} />\n      <ControlledCheckbox\n        value\n        labelTx=\"demoToggle:useCase.passingContent.useCase.checkBoxMultiLine.helper\"\n        editable={false}\n      />\n      <DemoDivider size={24} />\n      <ControlledRadio\n        value\n        labelTx=\"demoToggle:useCase.passingContent.useCase.radioChangeSides.helper\"\n        labelPosition=\"left\"\n      />\n      <DemoDivider size={24} />\n      <ControlledCheckbox\n        value\n        status=\"error\"\n        icon=\"ladybug\"\n        labelTx=\"demoToggle:useCase.passingContent.useCase.customCheckBox.label\"\n      />\n      <DemoDivider size={24} />\n      <ControlledSwitch\n        value\n        accessibilityMode=\"text\"\n        labelTx=\"demoToggle:useCase.passingContent.useCase.switch.label\"\n        status=\"error\"\n        helperTx=\"demoToggle:useCase.passingContent.useCase.switch.helper\"\n      />\n      <DemoDivider size={24} />\n      <ControlledSwitch\n        value\n        labelPosition=\"left\"\n        accessibilityMode=\"icon\"\n        labelTx=\"demoToggle:useCase.passingContent.useCase.switchAid.label\"\n      />\n    </DemoUseCase>,\n\n    <DemoUseCase\n      name=\"demoToggle:useCase.styling.name\"\n      description=\"demoToggle:useCase.styling.description\"\n      layout=\"row\"\n      itemStyle={$styles.flexWrap}\n    >\n      <ControlledCheckbox\n        containerStyle={$centeredOneThirdCol}\n        inputOuterStyle={{\n          width: 50,\n          height: 50,\n          backgroundColor: theme.colors.palette.accent300,\n          borderColor: theme.colors.palette.accent500,\n        }}\n      />\n      <ControlledRadio\n        containerStyle={$centeredOneThirdCol}\n        inputOuterStyle={{\n          width: 50,\n          height: 50,\n          borderRadius: 25,\n          backgroundColor: theme.colors.palette.accent300,\n          borderColor: theme.colors.palette.accent500,\n        }}\n      />\n      <ControlledSwitch\n        containerStyle={$centeredOneThirdCol}\n        inputOuterStyle={{\n          width: 70,\n          height: 50,\n          borderRadius: 25,\n          backgroundColor: theme.colors.palette.accent300,\n          borderColor: theme.colors.palette.accent500,\n        }}\n      />\n      <Text preset=\"formHelper\" style={themed($centeredText)}>\n        {translate(\"demoToggle:useCase.styling.outerWrapper\")}\n      </Text>\n\n      <DemoDivider style={{ width: \"100%\" }} />\n\n      <ControlledCheckbox\n        value\n        containerStyle={$centeredOneThirdCol}\n        inputOuterStyle={{\n          width: 50,\n          height: 50,\n          backgroundColor: theme.colors.palette.accent300,\n          borderColor: theme.colors.palette.accent500,\n        }}\n        inputInnerStyle={{\n          backgroundColor: theme.colors.palette.accent500,\n        }}\n      />\n      <ControlledRadio\n        value\n        containerStyle={$centeredOneThirdCol}\n        inputOuterStyle={{\n          width: 50,\n          height: 50,\n          borderRadius: 25,\n          backgroundColor: theme.colors.palette.accent300,\n          borderColor: theme.colors.palette.accent500,\n        }}\n        inputInnerStyle={{\n          backgroundColor: theme.colors.palette.accent500,\n        }}\n      />\n      <ControlledSwitch\n        value\n        containerStyle={$centeredOneThirdCol}\n        inputOuterStyle={{\n          width: 70,\n          height: 50,\n          borderRadius: 25,\n          backgroundColor: theme.colors.palette.accent300,\n          borderColor: theme.colors.palette.accent500,\n        }}\n        inputInnerStyle={{\n          backgroundColor: theme.colors.palette.accent500,\n          paddingLeft: 10,\n          paddingRight: 10,\n        }}\n      />\n      <Text preset=\"formHelper\" style={themed($centeredText)}>\n        {translate(\"demoToggle:useCase.styling.innerWrapper\")}\n      </Text>\n\n      <DemoDivider style={{ width: \"100%\" }} />\n\n      <ControlledCheckbox\n        value\n        icon=\"ladybug\"\n        containerStyle={$centeredOneThirdCol}\n        inputOuterStyle={{\n          width: 50,\n          height: 50,\n          backgroundColor: theme.colors.palette.accent300,\n          borderColor: theme.colors.palette.accent500,\n        }}\n        inputInnerStyle={{\n          backgroundColor: theme.colors.palette.accent500,\n        }}\n        inputDetailStyle={{\n          tintColor: theme.colors.tint,\n          height: 35,\n          width: 35,\n        }}\n      />\n      <ControlledRadio\n        value\n        containerStyle={$centeredOneThirdCol}\n        inputOuterStyle={{\n          width: 50,\n          height: 50,\n          borderRadius: 25,\n          backgroundColor: theme.colors.palette.accent300,\n          borderColor: theme.colors.palette.accent500,\n        }}\n        inputInnerStyle={{\n          backgroundColor: theme.colors.palette.accent500,\n        }}\n        inputDetailStyle={{\n          backgroundColor: theme.colors.tint,\n          height: 36,\n          width: 36,\n          borderRadius: 18,\n        }}\n      />\n\n      <ControlledSwitch\n        value\n        containerStyle={$centeredOneThirdCol}\n        inputOuterStyle={{\n          width: 70,\n          height: 50,\n          borderRadius: 25,\n          backgroundColor: theme.colors.palette.accent300,\n          borderColor: theme.colors.palette.accent500,\n        }}\n        inputInnerStyle={{\n          backgroundColor: theme.colors.tint,\n          paddingLeft: 10,\n          paddingRight: 10,\n        }}\n        inputDetailStyle={{\n          backgroundColor: theme.colors.palette.accent300,\n          height: 36,\n          width: 18,\n          borderRadius: 36,\n        }}\n        accessibilityMode=\"icon\"\n      />\n\n      <Text preset=\"formHelper\" style={themed($centeredText)}>\n        {translate(\"demoToggle:useCase.styling.inputDetail\")}\n      </Text>\n\n      <DemoDivider size={32} style={{ width: \"100%\" }} />\n\n      <View style={{ width: \"100%\" }}>\n        <ControlledRadio\n          value\n          labelTx=\"demoToggle:useCase.styling.labelTx\"\n          LabelTextProps={{ size: \"xs\", weight: \"bold\" }}\n          status=\"error\"\n          labelStyle={{\n            backgroundColor: theme.colors.error,\n            color: theme.colors.palette.neutral100,\n            paddingHorizontal: 5,\n          }}\n        />\n      </View>\n\n      <DemoDivider size={24} style={{ width: \"100%\" }} />\n\n      <View style={{ width: \"100%\" }}>\n        <ControlledRadio\n          value\n          labelPosition=\"left\"\n          containerStyle={{ padding: 10, backgroundColor: theme.colors.error }}\n          labelTx=\"demoToggle:useCase.styling.styleContainer\"\n          status=\"error\"\n          labelStyle={{ color: theme.colors.palette.neutral100 }}\n        />\n      </View>\n    </DemoUseCase>,\n  ],\n}\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/index.ts",
    "content": "export * from \"./DemoIcon\"\nexport * from \"./DemoTextField\"\nexport * from \"./DemoToggle\"\nexport * from \"./DemoButton\"\nexport * from \"./DemoListItem\"\nexport * from \"./DemoCard\"\nexport * from \"./DemoAutoImage\"\nexport * from \"./DemoText\"\nexport * from \"./DemoHeader\"\nexport * from \"./DemoEmptyState\"\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/DemoShowroomScreen/demos/types.ts",
    "content": "import { ReactElement } from \"react\"\nimport { TxKeyPath } from \"@/i18n\"\nimport type { Theme } from \"@/theme/types\"\n\nexport interface Demo {\n  name: string\n  description: TxKeyPath\n  data: ({ themed, theme }: { themed: any; theme: Theme }) => ReactElement[]\n}\n"
  },
  {
    "path": "boilerplate/app/screens/ErrorScreen/ErrorBoundary.tsx",
    "content": "import { Component, ErrorInfo, ReactNode } from \"react\"\n\nimport { ErrorDetails } from \"./ErrorDetails\"\n\ninterface Props {\n  children: ReactNode\n  catchErrors: \"always\" | \"dev\" | \"prod\" | \"never\"\n}\n\ninterface State {\n  error: Error | null\n  errorInfo: ErrorInfo | null\n}\n\n/**\n * This component handles whenever the user encounters a JS error in the\n * app. It follows the \"error boundary\" pattern in React. We're using a\n * class component because according to the documentation, only class\n * components can be error boundaries.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/concept/Error-Boundary/}\n * @see [React Error Boundaries]{@link https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary}\n * @param {Props} props - The props for the `ErrorBoundary` component.\n * @returns {JSX.Element} The rendered `ErrorBoundary` component.\n */\nexport class ErrorBoundary extends Component<Props, State> {\n  state = { error: null, errorInfo: null }\n\n  // If an error in a child is encountered, this will run\n  componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n    // Only set errors if enabled\n    if (!this.isEnabled()) {\n      return\n    }\n    // Catch errors in any components below and re-render with error message\n    this.setState({\n      error,\n      errorInfo,\n    })\n\n    // You can also log error messages to an error reporting service here\n    // This is a great place to put BugSnag, Sentry, crashlytics, etc:\n    // reportCrash(error)\n  }\n\n  // Reset the error back to null\n  resetError = () => {\n    this.setState({ error: null, errorInfo: null })\n  }\n\n  // To avoid unnecessary re-renders\n  shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>): boolean {\n    return nextState.error !== this.state.error\n  }\n\n  // Only enable if we're catching errors in the right environment\n  isEnabled(): boolean {\n    return (\n      this.props.catchErrors === \"always\" ||\n      (this.props.catchErrors === \"dev\" && __DEV__) ||\n      (this.props.catchErrors === \"prod\" && !__DEV__)\n    )\n  }\n\n  // Render an error UI if there's an error; otherwise, render children\n  render() {\n    return this.isEnabled() && this.state.error ? (\n      <ErrorDetails\n        onReset={this.resetError}\n        error={this.state.error}\n        errorInfo={this.state.errorInfo}\n      />\n    ) : (\n      this.props.children\n    )\n  }\n}\n"
  },
  {
    "path": "boilerplate/app/screens/ErrorScreen/ErrorDetails.tsx",
    "content": "import { ErrorInfo } from \"react\"\nimport { ScrollView, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Button } from \"@/components/Button\"\nimport { Icon } from \"@/components/Icon\"\nimport { Screen } from \"@/components/Screen\"\nimport { Text } from \"@/components/Text\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\n\nexport interface ErrorDetailsProps {\n  error: Error\n  errorInfo: ErrorInfo | null\n  onReset(): void\n}\n\n/**\n * Renders the error details screen.\n * @param {ErrorDetailsProps} props - The props for the `ErrorDetails` component.\n * @returns {JSX.Element} The rendered `ErrorDetails` component.\n */\nexport function ErrorDetails(props: ErrorDetailsProps) {\n  const { themed } = useAppTheme()\n  return (\n    <Screen\n      preset=\"fixed\"\n      safeAreaEdges={[\"top\", \"bottom\"]}\n      contentContainerStyle={themed($contentContainer)}\n    >\n      <View style={$topSection}>\n        <Icon icon=\"ladybug\" size={64} />\n        <Text style={themed($heading)} preset=\"subheading\" tx=\"errorScreen:title\" />\n        <Text tx=\"errorScreen:friendlySubtitle\" />\n      </View>\n\n      <ScrollView\n        style={themed($errorSection)}\n        contentContainerStyle={themed($errorSectionContentContainer)}\n      >\n        <Text style={themed($errorContent)} weight=\"bold\" text={`${props.error}`.trim()} />\n        <Text\n          selectable\n          style={themed($errorBacktrace)}\n          text={`${props.errorInfo?.componentStack ?? \"\"}`.trim()}\n        />\n      </ScrollView>\n\n      <Button\n        preset=\"reversed\"\n        style={themed($resetButton)}\n        onPress={props.onReset}\n        tx=\"errorScreen:reset\"\n      />\n    </Screen>\n  )\n}\n\nconst $contentContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  alignItems: \"center\",\n  paddingHorizontal: spacing.lg,\n  paddingTop: spacing.xl,\n  flex: 1,\n})\n\nconst $topSection: ViewStyle = {\n  flex: 1,\n  alignItems: \"center\",\n}\n\nconst $heading: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({\n  color: colors.error,\n  marginBottom: spacing.md,\n})\n\nconst $errorSection: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({\n  flex: 2,\n  backgroundColor: colors.separator,\n  marginVertical: spacing.md,\n  borderRadius: 6,\n})\n\nconst $errorSectionContentContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  padding: spacing.md,\n})\n\nconst $errorContent: ThemedStyle<TextStyle> = ({ colors }) => ({\n  color: colors.error,\n})\n\nconst $errorBacktrace: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({\n  marginTop: spacing.md,\n  color: colors.textDim,\n})\n\nconst $resetButton: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({\n  backgroundColor: colors.error,\n  paddingHorizontal: spacing.xxl,\n})\n"
  },
  {
    "path": "boilerplate/app/screens/LoginScreen.tsx",
    "content": "import { ComponentType, FC, useEffect, useMemo, useRef, useState } from \"react\"\n// eslint-disable-next-line no-restricted-imports\nimport { TextInput, TextStyle, ViewStyle } from \"react-native\"\n\nimport { Button } from \"@/components/Button\"\nimport { PressableIcon } from \"@/components/Icon\"\nimport { Screen } from \"@/components/Screen\"\nimport { Text } from \"@/components/Text\"\nimport { TextField, type TextFieldAccessoryProps } from \"@/components/TextField\"\nimport { useAuth } from \"@/context/AuthContext\"\nimport type { AppStackScreenProps } from \"@/navigators/navigationTypes\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\n\ninterface LoginScreenProps extends AppStackScreenProps<\"Login\"> {}\n\nexport const LoginScreen: FC<LoginScreenProps> = () => {\n  const authPasswordInput = useRef<TextInput>(null)\n\n  const [authPassword, setAuthPassword] = useState(\"\")\n  const [isAuthPasswordHidden, setIsAuthPasswordHidden] = useState(true)\n  const [isSubmitted, setIsSubmitted] = useState(false)\n  const [attemptsCount, setAttemptsCount] = useState(0)\n  const { authEmail, setAuthEmail, setAuthToken, validationError } = useAuth()\n\n  const {\n    themed,\n    theme: { colors },\n  } = useAppTheme()\n\n  useEffect(() => {\n    // Here is where you could fetch credentials from keychain or storage\n    // and pre-fill the form fields.\n    setAuthEmail(\"ignite@infinite.red\")\n    setAuthPassword(\"ign1teIsAwes0m3\")\n  }, [setAuthEmail])\n\n  const error = isSubmitted ? validationError : \"\"\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    setAuthToken(String(Date.now()))\n  }\n\n  const PasswordRightAccessory: ComponentType<TextFieldAccessoryProps> = useMemo(\n    () =>\n      function PasswordRightAccessory(props: TextFieldAccessoryProps) {\n        return (\n          <PressableIcon\n            icon={isAuthPasswordHidden ? \"view\" : \"hidden\"}\n            color={colors.palette.neutral800}\n            containerStyle={props.style}\n            size={20}\n            onPress={() => setIsAuthPasswordHidden(!isAuthPasswordHidden)}\n          />\n        )\n      },\n    [isAuthPasswordHidden, colors.palette.neutral800],\n  )\n\n  return (\n    <Screen\n      preset=\"auto\"\n      contentContainerStyle={themed($screenContentContainer)}\n      safeAreaEdges={[\"top\", \"bottom\"]}\n    >\n      <Text testID=\"login-heading\" tx=\"loginScreen:logIn\" preset=\"heading\" style={themed($logIn)} />\n      <Text tx=\"loginScreen:enterDetails\" preset=\"subheading\" style={themed($enterDetails)} />\n      {attemptsCount > 2 && (\n        <Text tx=\"loginScreen:hint\" size=\"sm\" weight=\"light\" style={themed($hint)} />\n      )}\n\n      <TextField\n        value={authEmail}\n        onChangeText={setAuthEmail}\n        containerStyle={themed($textField)}\n        autoCapitalize=\"none\"\n        autoComplete=\"email\"\n        autoCorrect={false}\n        keyboardType=\"email-address\"\n        labelTx=\"loginScreen:emailFieldLabel\"\n        placeholderTx=\"loginScreen:emailFieldPlaceholder\"\n        helper={error}\n        status={error ? \"error\" : undefined}\n        onSubmitEditing={() => authPasswordInput.current?.focus()}\n      />\n\n      <TextField\n        ref={authPasswordInput}\n        value={authPassword}\n        onChangeText={setAuthPassword}\n        containerStyle={themed($textField)}\n        autoCapitalize=\"none\"\n        autoComplete=\"password\"\n        autoCorrect={false}\n        secureTextEntry={isAuthPasswordHidden}\n        labelTx=\"loginScreen:passwordFieldLabel\"\n        placeholderTx=\"loginScreen:passwordFieldPlaceholder\"\n        onSubmitEditing={login}\n        RightAccessory={PasswordRightAccessory}\n      />\n\n      <Button\n        testID=\"login-button\"\n        tx=\"loginScreen:tapToLogIn\"\n        style={themed($tapButton)}\n        preset=\"reversed\"\n        onPress={login}\n      />\n    </Screen>\n  )\n}\n\nconst $screenContentContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  paddingVertical: spacing.xxl,\n  paddingHorizontal: spacing.lg,\n})\n\nconst $logIn: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginBottom: spacing.sm,\n})\n\nconst $enterDetails: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginBottom: spacing.lg,\n})\n\nconst $hint: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({\n  color: colors.tint,\n  marginBottom: spacing.md,\n})\n\nconst $textField: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginBottom: spacing.lg,\n})\n\nconst $tapButton: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  marginTop: spacing.xs,\n})\n\n// @demo remove-file\n"
  },
  {
    "path": "boilerplate/app/screens/WelcomeScreen.tsx",
    "content": "import { FC } from \"react\"\nimport { Image, ImageStyle, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Button } from \"@/components/Button\" // @demo remove-current-line\nimport { Screen } from \"@/components/Screen\"\nimport { Text } from \"@/components/Text\"\nimport { useAuth } from \"@/context/AuthContext\" // @demo remove-current-line\nimport { isRTL } from \"@/i18n\"\nimport type { AppStackScreenProps } from \"@/navigators/navigationTypes\" // @demo remove-current-line\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\nimport { useHeader } from \"@/utils/useHeader\" // @demo remove-current-line\nimport { useSafeAreaInsetsStyle } from \"@/utils/useSafeAreaInsetsStyle\"\n\nconst welcomeLogo = require(\"@assets/images/logo.png\")\nconst welcomeFace = require(\"@assets/images/welcome-face.png\")\n\ninterface WelcomeScreenProps extends AppStackScreenProps<\"Welcome\"> {} // @demo remove-current-line\n\n// @demo replace-next-line export const WelcomeScreen: FC = function WelcomeScreen(\nexport const WelcomeScreen: FC<WelcomeScreenProps> = function WelcomeScreen(\n  _props, // @demo remove-current-line\n) {\n  const { themed, theme } = useAppTheme()\n  // @demo remove-block-start\n  const { navigation } = _props\n  const { logout } = useAuth()\n\n  function goNext() {\n    navigation.navigate(\"Demo\", { screen: \"DemoShowroom\", params: {} })\n  }\n\n  useHeader(\n    {\n      rightTx: \"common:logOut\",\n      onRightPress: logout,\n    },\n    [logout],\n  )\n  // @demo remove-block-end\n\n  const $bottomContainerInsets = useSafeAreaInsetsStyle([\"bottom\"])\n\n  return (\n    <Screen preset=\"fixed\" contentContainerStyle={$styles.flex1}>\n      <View style={themed($topContainer)}>\n        <Image style={themed($welcomeLogo)} source={welcomeLogo} resizeMode=\"contain\" />\n        <Text\n          testID=\"welcome-heading\"\n          style={themed($welcomeHeading)}\n          tx=\"welcomeScreen:readyForLaunch\"\n          preset=\"heading\"\n        />\n        <Text tx=\"welcomeScreen:exciting\" preset=\"subheading\" />\n        <Image\n          style={$welcomeFace}\n          source={welcomeFace}\n          resizeMode=\"contain\"\n          tintColor={theme.colors.palette.neutral900}\n        />\n      </View>\n\n      <View style={themed([$bottomContainer, $bottomContainerInsets])}>\n        <Text tx=\"welcomeScreen:postscript\" size=\"md\" />\n        {/* @demo remove-block-start */}\n        <Button\n          testID=\"next-screen-button\"\n          preset=\"reversed\"\n          tx=\"welcomeScreen:letsGo\"\n          onPress={goNext}\n        />\n        {/* @demo remove-block-end */}\n      </View>\n    </Screen>\n  )\n}\n\nconst $topContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  flexShrink: 1,\n  flexGrow: 1,\n  flexBasis: \"57%\",\n  justifyContent: \"center\",\n  paddingHorizontal: spacing.lg,\n})\n\nconst $bottomContainer: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({\n  flexShrink: 1,\n  flexGrow: 0,\n  flexBasis: \"43%\",\n  backgroundColor: colors.palette.neutral100,\n  borderTopLeftRadius: 16,\n  borderTopRightRadius: 16,\n  paddingHorizontal: spacing.lg,\n  justifyContent: \"space-around\",\n})\n\nconst $welcomeLogo: ThemedStyle<ImageStyle> = ({ spacing }) => ({\n  height: 88,\n  width: \"100%\",\n  marginBottom: spacing.xxl,\n})\n\nconst $welcomeFace: ImageStyle = {\n  height: 169,\n  width: 269,\n  position: \"absolute\",\n  bottom: -47,\n  right: -80,\n  transform: [{ scaleX: isRTL ? -1 : 1 }],\n}\n\nconst $welcomeHeading: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginBottom: spacing.md,\n})\n"
  },
  {
    "path": "boilerplate/app/services/api/apiProblem.test.ts",
    "content": "import { ApiErrorResponse } from \"apisauce\"\n\nimport { getGeneralApiProblem } from \"./apiProblem\"\n\ntest(\"handles connection errors\", () => {\n  expect(getGeneralApiProblem({ problem: \"CONNECTION_ERROR\" } as ApiErrorResponse<null>)).toEqual({\n    kind: \"cannot-connect\",\n    temporary: true,\n  })\n})\n\ntest(\"handles network errors\", () => {\n  expect(getGeneralApiProblem({ problem: \"NETWORK_ERROR\" } as ApiErrorResponse<null>)).toEqual({\n    kind: \"cannot-connect\",\n    temporary: true,\n  })\n})\n\ntest(\"handles timeouts\", () => {\n  expect(getGeneralApiProblem({ problem: \"TIMEOUT_ERROR\" } as ApiErrorResponse<null>)).toEqual({\n    kind: \"timeout\",\n    temporary: true,\n  })\n})\n\ntest(\"handles server errors\", () => {\n  expect(getGeneralApiProblem({ problem: \"SERVER_ERROR\" } as ApiErrorResponse<null>)).toEqual({\n    kind: \"server\",\n  })\n})\n\ntest(\"handles unknown errors\", () => {\n  expect(getGeneralApiProblem({ problem: \"UNKNOWN_ERROR\" } as ApiErrorResponse<null>)).toEqual({\n    kind: \"unknown\",\n    temporary: true,\n  })\n})\n\ntest(\"handles unauthorized errors\", () => {\n  expect(\n    getGeneralApiProblem({ problem: \"CLIENT_ERROR\", status: 401 } as ApiErrorResponse<null>),\n  ).toEqual({\n    kind: \"unauthorized\",\n  })\n})\n\ntest(\"handles forbidden errors\", () => {\n  expect(\n    getGeneralApiProblem({ problem: \"CLIENT_ERROR\", status: 403 } as ApiErrorResponse<null>),\n  ).toEqual({\n    kind: \"forbidden\",\n  })\n})\n\ntest(\"handles not-found errors\", () => {\n  expect(\n    getGeneralApiProblem({ problem: \"CLIENT_ERROR\", status: 404 } as ApiErrorResponse<null>),\n  ).toEqual({\n    kind: \"not-found\",\n  })\n})\n\ntest(\"handles other client errors\", () => {\n  expect(\n    getGeneralApiProblem({ problem: \"CLIENT_ERROR\", status: 418 } as ApiErrorResponse<null>),\n  ).toEqual({\n    kind: \"rejected\",\n  })\n})\n\ntest(\"handles cancellation errors\", () => {\n  expect(getGeneralApiProblem({ problem: \"CANCEL_ERROR\" } as ApiErrorResponse<null>)).toBeNull()\n})\n"
  },
  {
    "path": "boilerplate/app/services/api/apiProblem.ts",
    "content": "import { ApiResponse } from \"apisauce\"\n\nexport type GeneralApiProblem =\n  /**\n   * Times up.\n   */\n  | { kind: \"timeout\"; temporary: true }\n  /**\n   * Cannot connect to the server for some reason.\n   */\n  | { kind: \"cannot-connect\"; temporary: true }\n  /**\n   * The server experienced a problem. Any 5xx error.\n   */\n  | { kind: \"server\" }\n  /**\n   * We're not allowed because we haven't identified ourself. This is 401.\n   */\n  | { kind: \"unauthorized\" }\n  /**\n   * We don't have access to perform that request. This is 403.\n   */\n  | { kind: \"forbidden\" }\n  /**\n   * Unable to find that resource.  This is a 404.\n   */\n  | { kind: \"not-found\" }\n  /**\n   * All other 4xx series errors.\n   */\n  | { kind: \"rejected\" }\n  /**\n   * Something truly unexpected happened. Most likely can try again. This is a catch all.\n   */\n  | { kind: \"unknown\"; temporary: true }\n  /**\n   * The data we received is not in the expected format.\n   */\n  | { kind: \"bad-data\" }\n\n/**\n * Attempts to get a common cause of problems from an api response.\n *\n * @param response The api response.\n */\nexport function getGeneralApiProblem(response: ApiResponse<any>): GeneralApiProblem | null {\n  switch (response.problem) {\n    case \"CONNECTION_ERROR\":\n      return { kind: \"cannot-connect\", temporary: true }\n    case \"NETWORK_ERROR\":\n      return { kind: \"cannot-connect\", temporary: true }\n    case \"TIMEOUT_ERROR\":\n      return { kind: \"timeout\", temporary: true }\n    case \"SERVER_ERROR\":\n      return { kind: \"server\" }\n    case \"UNKNOWN_ERROR\":\n      return { kind: \"unknown\", temporary: true }\n    case \"CLIENT_ERROR\":\n      switch (response.status) {\n        case 401:\n          return { kind: \"unauthorized\" }\n        case 403:\n          return { kind: \"forbidden\" }\n        case 404:\n          return { kind: \"not-found\" }\n        default:\n          return { kind: \"rejected\" }\n      }\n    case \"CANCEL_ERROR\":\n      return null\n  }\n\n  return null\n}\n"
  },
  {
    "path": "boilerplate/app/services/api/index.ts",
    "content": "/**\n * This Api class lets you define an API endpoint and methods to request\n * data and process it.\n *\n * See the [Backend API Integration](https://docs.infinite.red/ignite-cli/boilerplate/app/services/#backend-api-integration)\n * documentation for more details.\n */\nimport {\n  ApiResponse, // @demo remove-current-line\n  ApisauceInstance,\n  create,\n} from \"apisauce\"\n\nimport Config from \"@/config\"\nimport type { EpisodeItem } from \"@/services/api/types\" // @demo remove-current-line\n\nimport { GeneralApiProblem, getGeneralApiProblem } from \"./apiProblem\" // @demo remove-current-line\nimport type {\n  ApiConfig,\n  ApiFeedResponse, // @demo remove-current-line\n} from \"./types\"\n\n/**\n * Configuring the apisauce instance.\n */\nexport const DEFAULT_API_CONFIG: ApiConfig = {\n  url: Config.API_URL,\n  timeout: 10000,\n}\n\n/**\n * Manages all requests to the API. You can use this class to build out\n * various requests that you need to call from your backend API.\n */\nexport class Api {\n  apisauce: ApisauceInstance\n  config: ApiConfig\n\n  /**\n   * Set up our API instance. Keep this lightweight!\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  // @demo remove-block-start\n  /**\n   * Gets a list of recent React Native Radio episodes.\n   */\n  async getEpisodes(): Promise<{ kind: \"ok\"; episodes: EpisodeItem[] } | GeneralApiProblem> {\n    // make the api call\n    const response: ApiResponse<ApiFeedResponse> = await this.apisauce.get(\n      `api.json?rss_url=https%3A%2F%2Ffeeds.simplecast.com%2FhEI_f9Dx`,\n    )\n\n    // the typical ways to die when calling an api\n    if (!response.ok) {\n      const problem = getGeneralApiProblem(response)\n      if (problem) return problem\n    }\n\n    // transform the data into the format we are expecting\n    try {\n      const rawData = response.data\n\n      // This is where we transform the data into the shape we expect for our model.\n      const episodes: EpisodeItem[] =\n        rawData?.items.map((raw) => ({\n          ...raw,\n        })) ?? []\n\n      return { kind: \"ok\", episodes }\n    } catch (e) {\n      if (__DEV__ && e instanceof Error) {\n        console.error(`Bad data: ${e.message}\\n${response.data}`, e.stack)\n      }\n      return { kind: \"bad-data\" }\n    }\n  }\n  // @demo remove-block-end\n}\n\n// Singleton instance of the API for convenience\nexport const api = new Api()\n"
  },
  {
    "path": "boilerplate/app/services/api/types.ts",
    "content": "/**\n * These types indicate the shape of the data you expect to receive from your\n * API endpoint, assuming it's a JSON object like we have.\n */\nexport interface EpisodeItem {\n  title: string\n  pubDate: string\n  link: string\n  guid: string\n  author: string\n  thumbnail: string\n  description: string\n  content: string\n  enclosure: {\n    link: string\n    type: string\n    length: number\n    duration: number\n    rating: { scheme: string; value: string }\n  }\n  categories: string[]\n}\n\nexport interface ApiFeedResponse {\n  status: string\n  feed: {\n    url: string\n    title: string\n    link: string\n    author: string\n    description: string\n    image: string\n  }\n  items: EpisodeItem[]\n}\n\n/**\n * The options used to configure apisauce.\n */\nexport interface ApiConfig {\n  /**\n   * The URL of the api.\n   */\n  url: string\n\n  /**\n   * Milliseconds before we timeout the request.\n   */\n  timeout: number\n}\n"
  },
  {
    "path": "boilerplate/app/theme/colors.ts",
    "content": "const palette = {\n  neutral100: \"#FFFFFF\",\n  neutral200: \"#F4F2F1\",\n  neutral300: \"#D7CEC9\",\n  neutral400: \"#B6ACA6\",\n  neutral500: \"#978F8A\",\n  neutral600: \"#564E4A\",\n  neutral700: \"#3C3836\",\n  neutral800: \"#191015\",\n  neutral900: \"#000000\",\n\n  primary100: \"#F4E0D9\",\n  primary200: \"#E8C1B4\",\n  primary300: \"#DDA28E\",\n  primary400: \"#D28468\",\n  primary500: \"#C76542\",\n  primary600: \"#A54F31\",\n\n  secondary100: \"#DCDDE9\",\n  secondary200: \"#BCC0D6\",\n  secondary300: \"#9196B9\",\n  secondary400: \"#626894\",\n  secondary500: \"#41476E\",\n\n  accent100: \"#FFEED4\",\n  accent200: \"#FFE1B2\",\n  accent300: \"#FDD495\",\n  accent400: \"#FBC878\",\n  accent500: \"#FFBB50\",\n\n  angry100: \"#F2D6CD\",\n  angry500: \"#C03403\",\n\n  overlay20: \"rgba(25, 16, 21, 0.2)\",\n  overlay50: \"rgba(25, 16, 21, 0.5)\",\n} as const\n\nexport const colors = {\n  /**\n   * The palette is available to use, but prefer using the name.\n   * This is only included for rare, one-off cases. Try to use\n   * semantic names as much as possible.\n   */\n  palette,\n  /**\n   * A helper for making something see-thru.\n   */\n  transparent: \"rgba(0, 0, 0, 0)\",\n  /**\n   * The default text color in many components.\n   */\n  text: palette.neutral800,\n  /**\n   * Secondary text information.\n   */\n  textDim: palette.neutral600,\n  /**\n   * The default color of the screen background.\n   */\n  background: palette.neutral200,\n  /**\n   * The default border color.\n   */\n  border: palette.neutral400,\n  /**\n   * The main tinting color.\n   */\n  tint: palette.primary500,\n  /**\n   * The inactive tinting color.\n   */\n  tintInactive: palette.neutral300,\n  /**\n   * A subtle color used for lines.\n   */\n  separator: palette.neutral300,\n  /**\n   * Error messages.\n   */\n  error: palette.angry500,\n  /**\n   * Error Background.\n   */\n  errorBackground: palette.angry100,\n} as const\n"
  },
  {
    "path": "boilerplate/app/theme/colorsDark.ts",
    "content": "const palette = {\n  neutral900: \"#FFFFFF\",\n  neutral800: \"#F4F2F1\",\n  neutral700: \"#D7CEC9\",\n  neutral600: \"#B6ACA6\",\n  neutral500: \"#978F8A\",\n  neutral400: \"#564E4A\",\n  neutral300: \"#3C3836\",\n  neutral200: \"#191015\",\n  neutral100: \"#000000\",\n\n  primary600: \"#F4E0D9\",\n  primary500: \"#E8C1B4\",\n  primary400: \"#DDA28E\",\n  primary300: \"#D28468\",\n  primary200: \"#C76542\",\n  primary100: \"#A54F31\",\n\n  secondary500: \"#DCDDE9\",\n  secondary400: \"#BCC0D6\",\n  secondary300: \"#9196B9\",\n  secondary200: \"#626894\",\n  secondary100: \"#41476E\",\n\n  accent500: \"#FFEED4\",\n  accent400: \"#FFE1B2\",\n  accent300: \"#FDD495\",\n  accent200: \"#FBC878\",\n  accent100: \"#FFBB50\",\n\n  angry100: \"#F2D6CD\",\n  angry500: \"#C03403\",\n\n  overlay20: \"rgba(25, 16, 21, 0.2)\",\n  overlay50: \"rgba(25, 16, 21, 0.5)\",\n} as const\n\nexport const colors = {\n  palette,\n  transparent: \"rgba(0, 0, 0, 0)\",\n  text: palette.neutral800,\n  textDim: palette.neutral600,\n  background: palette.neutral200,\n  border: palette.neutral400,\n  tint: palette.primary500,\n  tintInactive: palette.neutral300,\n  separator: palette.neutral300,\n  error: palette.angry500,\n  errorBackground: palette.angry100,\n} as const\n"
  },
  {
    "path": "boilerplate/app/theme/context.tsx",
    "content": "import {\n  createContext,\n  FC,\n  PropsWithChildren,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n} from \"react\"\nimport { StyleProp, useColorScheme } from \"react-native\"\nimport {\n  DarkTheme as NavDarkTheme,\n  DefaultTheme as NavDefaultTheme,\n  Theme as NavTheme,\n} from \"@react-navigation/native\"\nimport { useMMKVString } from \"react-native-mmkv\"\n\nimport { storage } from \"@/utils/storage\"\n\nimport { setImperativeTheming } from \"./context.utils\"\nimport { darkTheme, lightTheme } from \"./theme\"\nimport type {\n  AllowedStylesT,\n  ImmutableThemeContextModeT,\n  Theme,\n  ThemeContextModeT,\n  ThemedFnT,\n  ThemedStyle,\n} from \"./types\"\n\nexport type ThemeContextType = {\n  navigationTheme: NavTheme\n  setThemeContextOverride: (newTheme: ThemeContextModeT) => void\n  theme: Theme\n  themeContext: ImmutableThemeContextModeT\n  themed: ThemedFnT\n}\n\nexport const ThemeContext = createContext<ThemeContextType | null>(null)\n\nexport interface ThemeProviderProps {\n  initialContext?: ThemeContextModeT\n}\n\n/**\n * The ThemeProvider is the heart and soul of the design token system. It provides a context wrapper\n * for your entire app to consume the design tokens as well as global functionality like the app's theme.\n *\n * To get started, you want to wrap your entire app's JSX hierarchy in `ThemeProvider`\n * and then use the `useAppTheme()` hook to access the theme context.\n *\n * Documentation: https://docs.infinite.red/ignite-cli/boilerplate/app/theme/Theming/\n */\nexport const ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({\n  children,\n  initialContext,\n}) => {\n  // The operating system theme:\n  const systemColorScheme = useColorScheme()\n  // Our saved theme context: can be \"light\", \"dark\", or undefined (system theme)\n  const [themeScheme, setThemeScheme] = useMMKVString(\"ignite.themeScheme\", storage)\n\n  /**\n   * This function is used to set the theme context and is exported from the useAppTheme() hook.\n   *  - setThemeContextOverride(\"dark\") sets the app theme to dark no matter what the system theme is.\n   *  - setThemeContextOverride(\"light\") sets the app theme to light no matter what the system theme is.\n   *  - setThemeContextOverride(undefined) the app will follow the operating system theme.\n   */\n  const setThemeContextOverride = useCallback(\n    (newTheme: ThemeContextModeT) => {\n      setThemeScheme(newTheme)\n    },\n    [setThemeScheme],\n  )\n\n  /**\n   * initialContext is the theme context passed in from the app.tsx file and always takes precedence.\n   * themeScheme is the value from MMKV. If undefined, we fall back to the system theme\n   * systemColorScheme is the value from the device. If undefined, we fall back to \"light\"\n   */\n  const themeContext: ImmutableThemeContextModeT = useMemo(() => {\n    const t = initialContext || themeScheme || (!!systemColorScheme ? systemColorScheme : \"light\")\n    return t === \"dark\" ? \"dark\" : \"light\"\n  }, [initialContext, themeScheme, systemColorScheme])\n\n  const navigationTheme: NavTheme = useMemo(() => {\n    switch (themeContext) {\n      case \"dark\":\n        return NavDarkTheme\n      default:\n        return NavDefaultTheme\n    }\n  }, [themeContext])\n\n  const theme: Theme = useMemo(() => {\n    switch (themeContext) {\n      case \"dark\":\n        return darkTheme\n      default:\n        return lightTheme\n    }\n  }, [themeContext])\n\n  useEffect(() => {\n    setImperativeTheming(theme)\n  }, [theme])\n\n  const themed = useCallback(\n    <T,>(styleOrStyleFn: AllowedStylesT<T>) => {\n      const flatStyles = [styleOrStyleFn].flat(3) as (ThemedStyle<T> | StyleProp<T>)[]\n      const stylesArray = flatStyles.map((f) => {\n        if (typeof f === \"function\") {\n          return (f as ThemedStyle<T>)(theme)\n        } else {\n          return f\n        }\n      })\n      // Flatten the array of styles into a single object\n      return Object.assign({}, ...stylesArray) as T\n    },\n    [theme],\n  )\n\n  const value = {\n    navigationTheme,\n    theme,\n    themeContext,\n    setThemeContextOverride,\n    themed,\n  }\n\n  return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>\n}\n\n/**\n * This is the primary hook that you will use to access the theme context in your components.\n * Documentation: https://docs.infinite.red/ignite-cli/boilerplate/app/theme/useAppTheme.tsx/\n */\nexport const useAppTheme = () => {\n  const context = useContext(ThemeContext)\n  if (!context) {\n    throw new Error(\"useAppTheme must be used within an ThemeProvider\")\n  }\n  return context\n}\n"
  },
  {
    "path": "boilerplate/app/theme/context.utils.ts",
    "content": "import type { Theme } from \"./types\"\n\nconst systemui = require(\"expo-system-ui\")\n\n/**\n * Set the system UI background color to the given color. This is only available if the app has\n * installed expo-system-ui.\n *\n * @param color The color to set the system UI background to\n */\nexport const setSystemUIBackgroundColor = (color: string) => {\n  if (systemui) {\n    systemui.setBackgroundColorAsync(color)\n  }\n}\n\n/**\n * Set the app's native background color to match the theme.\n * This is only available if the app has installed expo-system-ui\n *\n * @param theme The theme object to use for the background color\n */\nexport const setImperativeTheming = (theme: Theme) => {\n  setSystemUIBackgroundColor(theme.colors.background)\n}\n"
  },
  {
    "path": "boilerplate/app/theme/spacing.ts",
    "content": "/**\n  Use these spacings for margins/paddings and other whitespace throughout your app.\n */\nexport const spacing = {\n  xxxs: 2,\n  xxs: 4,\n  xs: 8,\n  sm: 12,\n  md: 16,\n  lg: 24,\n  xl: 32,\n  xxl: 48,\n  xxxl: 64,\n} as const\n"
  },
  {
    "path": "boilerplate/app/theme/spacingDark.ts",
    "content": "const SPACING_MULTIPLIER = 1.0\n\n// This is an example of how you can have different spacing values for different themes.\nexport const spacing = {\n  xxxs: 2 * SPACING_MULTIPLIER,\n  xxs: 4 * SPACING_MULTIPLIER,\n  xs: 8 * SPACING_MULTIPLIER,\n  sm: 12 * SPACING_MULTIPLIER,\n  md: 16 * SPACING_MULTIPLIER,\n  lg: 24 * SPACING_MULTIPLIER,\n  xl: 32 * SPACING_MULTIPLIER,\n  xxl: 48 * SPACING_MULTIPLIER,\n  xxxl: 64 * SPACING_MULTIPLIER,\n} as const\n"
  },
  {
    "path": "boilerplate/app/theme/styles.ts",
    "content": "import { ViewStyle } from \"react-native\"\n\nimport { spacing } from \"./spacing\" // @demo remove-current-line\n\n/* Use this file to define styles that are used in multiple places in your app. */\nexport const $styles = {\n  row: { flexDirection: \"row\" } as ViewStyle,\n  flex1: { flex: 1 } as ViewStyle,\n  flexWrap: { flexWrap: \"wrap\" } as ViewStyle,\n\n  // @demo remove-block-start\n  container: {\n    paddingTop: spacing.lg + spacing.xl,\n    paddingHorizontal: spacing.lg,\n  } as ViewStyle,\n  // @demo remove-block-end\n  toggleInner: {\n    width: \"100%\",\n    height: \"100%\",\n    alignItems: \"center\",\n    justifyContent: \"center\",\n    overflow: \"hidden\",\n  } as ViewStyle,\n}\n"
  },
  {
    "path": "boilerplate/app/theme/theme.ts",
    "content": "import { colors as colorsLight } from \"./colors\"\nimport { colors as colorsDark } from \"./colorsDark\"\nimport { spacing as spacingLight } from \"./spacing\"\nimport { spacing as spacingDark } from \"./spacingDark\"\nimport { timing } from \"./timing\"\nimport type { Theme } from \"./types\"\nimport { typography } from \"./typography\"\n\n// Here we define our themes.\nexport const lightTheme: Theme = {\n  colors: colorsLight,\n  spacing: spacingLight,\n  typography,\n  timing,\n  isDark: false,\n}\nexport const darkTheme: Theme = {\n  colors: colorsDark,\n  spacing: spacingDark,\n  typography,\n  timing,\n  isDark: true,\n}\n"
  },
  {
    "path": "boilerplate/app/theme/timing.ts",
    "content": "export const timing = {\n  /**\n   * The duration (ms) for quick animations.\n   */\n  quick: 300,\n}\n"
  },
  {
    "path": "boilerplate/app/theme/types.ts",
    "content": "import type { StyleProp } from \"react-native\"\n\nimport { colors as colorsLight } from \"./colors\"\nimport { colors as colorsDark } from \"./colorsDark\"\nimport { spacing as spacingLight } from \"./spacing\"\nimport { spacing as spacingDark } from \"./spacingDark\"\nimport { timing } from \"./timing\"\nimport { typography } from \"./typography\"\n\n// This supports \"light\" and \"dark\" themes by default. If undefined, it'll use the system theme\nexport type ImmutableThemeContextModeT = \"light\" | \"dark\"\nexport type ThemeContextModeT = ImmutableThemeContextModeT | undefined\n\n// Because we have two themes, we need to define the types for each of them.\n// colorsLight and colorsDark should have the same keys, but different values.\nexport type Colors = typeof colorsLight | typeof colorsDark\n// The spacing type needs to take into account the different spacing values for light and dark themes.\nexport type Spacing = typeof spacingLight | typeof spacingDark\n\n// These two are consistent across themes.\nexport type Timing = typeof timing\nexport type Typography = typeof typography\n\n// The overall Theme object should contain all of the data you need to style your app.\nexport interface Theme {\n  colors: Colors\n  spacing: Spacing\n  typography: Typography\n  timing: Timing\n  isDark: boolean\n}\n\n/**\n * Represents a function that returns a styled component based on the provided theme.\n * @template T The type of the style.\n * @param theme The theme object.\n * @returns The styled component.\n *\n * @example\n * const $container: ThemedStyle<ViewStyle> = (theme) => ({\n *   flex: 1,\n *   backgroundColor: theme.colors.background,\n *   justifyContent: \"center\",\n *   alignItems: \"center\",\n * })\n * // Then use in a component like so:\n * const Component = () => {\n *   const { themed } = useAppTheme()\n *   return <View style={themed($container)} />\n * }\n */\nexport type ThemedStyle<T> = (theme: Theme) => T\nexport type ThemedStyleArray<T> = (\n  | ThemedStyle<T>\n  | StyleProp<T>\n  | (StyleProp<T> | ThemedStyle<T>)[]\n)[]\n\n/**\n */\nexport type AllowedStylesT<T> = ThemedStyle<T> | StyleProp<T> | ThemedStyleArray<T>\n/**\n */\nexport type ThemedFnT = <T>(styleOrStyleFn: AllowedStylesT<T>) => T\n"
  },
  {
    "path": "boilerplate/app/theme/typography.ts",
    "content": "// TODO: write documentation about fonts and typography along with guides on how to add custom fonts in own\n// markdown file and add links from here\n\nimport { Platform } from \"react-native\"\nimport {\n  SpaceGrotesk_300Light as spaceGroteskLight,\n  SpaceGrotesk_400Regular as spaceGroteskRegular,\n  SpaceGrotesk_500Medium as spaceGroteskMedium,\n  SpaceGrotesk_600SemiBold as spaceGroteskSemiBold,\n  SpaceGrotesk_700Bold as spaceGroteskBold,\n} from \"@expo-google-fonts/space-grotesk\"\n\nexport const customFontsToLoad = {\n  spaceGroteskLight,\n  spaceGroteskRegular,\n  spaceGroteskMedium,\n  spaceGroteskSemiBold,\n  spaceGroteskBold,\n}\n\nconst fonts = {\n  spaceGrotesk: {\n    // Cross-platform Google font.\n    light: \"spaceGroteskLight\",\n    normal: \"spaceGroteskRegular\",\n    medium: \"spaceGroteskMedium\",\n    semiBold: \"spaceGroteskSemiBold\",\n    bold: \"spaceGroteskBold\",\n  },\n  helveticaNeue: {\n    // iOS only font.\n    thin: \"HelveticaNeue-Thin\",\n    light: \"HelveticaNeue-Light\",\n    normal: \"Helvetica Neue\",\n    medium: \"HelveticaNeue-Medium\",\n  },\n  courier: {\n    // iOS only font.\n    normal: \"Courier\",\n  },\n  sansSerif: {\n    // Android only font.\n    thin: \"sans-serif-thin\",\n    light: \"sans-serif-light\",\n    normal: \"sans-serif\",\n    medium: \"sans-serif-medium\",\n  },\n  monospace: {\n    // Android only font.\n    normal: \"monospace\",\n  },\n}\n\nexport const typography = {\n  /**\n   * The fonts are available to use, but prefer using the semantic name.\n   */\n  fonts,\n  /**\n   * The primary font. Used in most places.\n   */\n  primary: fonts.spaceGrotesk,\n  /**\n   * An alternate font used for perhaps titles and stuff.\n   */\n  secondary: Platform.select({ ios: fonts.helveticaNeue, android: fonts.sansSerif }),\n  /**\n   * Lets get fancy with a monospace font!\n   */\n  code: Platform.select({ ios: fonts.courier, android: fonts.monospace }),\n}\n"
  },
  {
    "path": "boilerplate/app/utils/crashReporting.ts",
    "content": "/**\n * If you're using Sentry\n *   Expo https://docs.expo.dev/guides/using-sentry/\n */\n// import * as Sentry from \"@sentry/react-native\"\n\n/**\n * If you're using Crashlytics: https://rnfirebase.io/crashlytics/usage\n */\n// import crashlytics from \"@react-native-firebase/crashlytics\"\n\n/**\n * If you're using Bugsnag:\n *   RN   https://docs.bugsnag.com/platforms/react-native/)\n *   Expo https://docs.bugsnag.com/platforms/react-native/expo/\n */\n// import Bugsnag from \"@bugsnag/react-native\"\n// import Bugsnag from \"@bugsnag/expo\"\n\n/**\n *  This is where you put your crash reporting service initialization code to call in `./app/app.tsx`\n */\nexport const initCrashReporting = () => {\n  // Sentry.init({\n  //   dsn: \"YOUR DSN HERE\",\n  //   debug: true, // If `true`, Sentry will try to print out useful debugging information if something goes wrong with sending the event. Set it to `false` in production\n  // })\n  // Bugsnag.start(\"YOUR API KEY\")\n}\n\n/**\n * Error classifications used to sort errors on error reporting services.\n */\nexport enum ErrorType {\n  /**\n   * An error that would normally cause a red screen in dev\n   * and force the user to sign out and restart.\n   */\n  FATAL = \"Fatal\",\n  /**\n   * An error caught by try/catch where defined using Reactotron.tron.error.\n   */\n  HANDLED = \"Handled\",\n}\n\n/**\n * Manually report a handled error.\n */\nexport const reportCrash = (error: Error, type: ErrorType = ErrorType.FATAL) => {\n  if (__DEV__) {\n    // Log to console and Reactotron in development\n    const message = error.message || \"Unknown\"\n    console.error(error)\n    console.log(message, type)\n  } else {\n    // In production, utilize crash reporting service of choice below:\n    // RN\n    // Sentry.captureException(error)\n    // crashlytics().recordError(error)\n    // Bugsnag.notify(error)\n  }\n}\n"
  },
  {
    "path": "boilerplate/app/utils/delay.ts",
    "content": "/**\n * A \"modern\" sleep statement.\n *\n * @param ms The number of milliseconds to wait.\n */\nexport const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n"
  },
  {
    "path": "boilerplate/app/utils/formatDate.ts",
    "content": "// Note the syntax of these imports from the date-fns library.\n// If you import with the syntax: import { format } from \"date-fns\" the ENTIRE library\n// will be included in your production bundle (even if you only use one function).\n// This is because react-native does not support tree-shaking.\nimport { format } from \"date-fns/format\"\nimport type { Locale } from \"date-fns/locale\"\nimport { parseISO } from \"date-fns/parseISO\"\nimport i18n from \"i18next\"\n\ntype Options = Parameters<typeof format>[2]\n\nlet dateFnsLocale: Locale\nexport const loadDateFnsLocale = () => {\n  const primaryTag = i18n.language.split(\"-\")[0]\n  switch (primaryTag) {\n    case \"en\":\n      dateFnsLocale = require(\"date-fns/locale/en-US\").default\n      break\n    case \"ar\":\n      dateFnsLocale = require(\"date-fns/locale/ar\").default\n      break\n    case \"ko\":\n      dateFnsLocale = require(\"date-fns/locale/ko\").default\n      break\n    case \"es\":\n      dateFnsLocale = require(\"date-fns/locale/es\").default\n      break\n    case \"fr\":\n      dateFnsLocale = require(\"date-fns/locale/fr\").default\n      break\n    case \"hi\":\n      dateFnsLocale = require(\"date-fns/locale/hi\").default\n      break\n    case \"ja\":\n      dateFnsLocale = require(\"date-fns/locale/ja\").default\n      break\n    default:\n      dateFnsLocale = require(\"date-fns/locale/en-US\").default\n      break\n  }\n}\n\nexport const formatDate = (date: string, dateFormat?: string, options?: Options) => {\n  const dateOptions = {\n    ...options,\n    locale: dateFnsLocale,\n  }\n  return format(parseISO(date), dateFormat ?? \"MMM dd, yyyy\", dateOptions)\n}\n"
  },
  {
    "path": "boilerplate/app/utils/gestureHandler.native.ts",
    "content": "// Only import react-native-gesture-handler on native platforms\n// https://reactnavigation.org/docs/drawer-navigator/#installation\nimport \"react-native-gesture-handler\"\n"
  },
  {
    "path": "boilerplate/app/utils/gestureHandler.ts",
    "content": "// Don't import react-native-gesture-handler on web\n// https://reactnavigation.org/docs/drawer-navigator/#installation\n\n// This however is needed at the moment\n// https://github.com/software-mansion/react-native-gesture-handler/issues/2402\nimport \"setimmediate\"\n"
  },
  {
    "path": "boilerplate/app/utils/openLinkInBrowser.ts",
    "content": "import { Linking } from \"react-native\"\n\n/**\n * Helper for opening a give URL in an external browser.\n */\nexport function openLinkInBrowser(url: string) {\n  Linking.canOpenURL(url).then((canOpen) => canOpen && Linking.openURL(url))\n}\n"
  },
  {
    "path": "boilerplate/app/utils/storage/index.ts",
    "content": "import { MMKV } from \"react-native-mmkv\"\n\nexport const storage = new MMKV()\n\n/**\n * Loads a string from storage.\n *\n * @param key The key to fetch.\n */\nexport function loadString(key: string): string | null {\n  try {\n    return storage.getString(key) ?? null\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 */\nexport function saveString(key: string, value: string): boolean {\n  try {\n    storage.set(key, value)\n    return true\n  } catch {\n    return false\n  }\n}\n\n/**\n * Loads something from storage and runs it thru JSON.parse.\n *\n * @param key The key to fetch.\n */\nexport function load<T>(key: string): T | null {\n  let almostThere: string | null = null\n  try {\n    almostThere = loadString(key)\n    return JSON.parse(almostThere ?? \"\") as T\n  } catch {\n    return (almostThere as T) ?? 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 */\nexport function save(key: string, value: unknown): boolean {\n  try {\n    saveString(key, JSON.stringify(value))\n    return true\n  } catch {\n    return false\n  }\n}\n\n/**\n * Removes something from storage.\n *\n * @param key The key to kill.\n */\nexport function remove(key: string): void {\n  try {\n    storage.delete(key)\n  } catch {}\n}\n\n/**\n * Burn it all to the ground.\n */\nexport function clear(): void {\n  try {\n    storage.clearAll()\n  } catch {}\n}\n"
  },
  {
    "path": "boilerplate/app/utils/storage/storage.test.ts",
    "content": "import { load, loadString, save, saveString, clear, remove, storage } from \".\"\n\nconst VALUE_OBJECT = { x: 1 }\nconst VALUE_STRING = JSON.stringify(VALUE_OBJECT)\n\ndescribe(\"MMKV Storage\", () => {\n  beforeEach(() => {\n    storage.clearAll()\n    storage.set(\"string\", \"string\")\n    storage.set(\"object\", JSON.stringify(VALUE_OBJECT))\n  })\n\n  it(\"should be defined\", () => {\n    expect(storage).toBeDefined()\n  })\n\n  it(\"should have default keys\", () => {\n    expect(storage.getAllKeys()).toEqual([\"string\", \"object\"])\n  })\n\n  it(\"should load data\", () => {\n    expect(load<object>(\"object\")).toEqual(VALUE_OBJECT)\n    expect(loadString(\"object\")).toEqual(VALUE_STRING)\n\n    expect(load<string>(\"string\")).toEqual(\"string\")\n    expect(loadString(\"string\")).toEqual(\"string\")\n  })\n\n  it(\"should save strings\", () => {\n    saveString(\"string\", \"new string\")\n    expect(loadString(\"string\")).toEqual(\"new string\")\n  })\n\n  it(\"should save objects\", () => {\n    save(\"object\", { y: 2 })\n    expect(load<object>(\"object\")).toEqual({ y: 2 })\n    save(\"object\", { z: 3, also: true })\n    expect(load<object>(\"object\")).toEqual({ z: 3, also: true })\n  })\n\n  it(\"should save strings and objects\", () => {\n    saveString(\"object\", \"new string\")\n    expect(loadString(\"object\")).toEqual(\"new string\")\n  })\n\n  it(\"should remove data\", () => {\n    remove(\"object\")\n    expect(load<object>(\"object\")).toBeNull()\n    expect(storage.getAllKeys()).toEqual([\"string\"])\n\n    remove(\"string\")\n    expect(load<string>(\"string\")).toBeNull()\n    expect(storage.getAllKeys()).toEqual([])\n  })\n\n  it(\"should clear all data\", () => {\n    expect(storage.getAllKeys()).toEqual([\"string\", \"object\"])\n    clear()\n    expect(storage.getAllKeys()).toEqual([])\n  })\n})\n"
  },
  {
    "path": "boilerplate/app/utils/useHeader.tsx",
    "content": "import { useEffect, useLayoutEffect } from \"react\"\nimport { Platform } from \"react-native\"\nimport { useNavigation } from \"@react-navigation/native\"\n\nimport { Header, HeaderProps } from \"@/components/Header\"\n\n/**\n * A hook that can be used to easily set the Header of a react-navigation screen from within the screen's component.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/utils/useHeader.tsx/}\n * @param {HeaderProps} headerProps - The props for the `Header` component.\n * @param {any[]} deps - The dependencies to watch for changes to update the header.\n */\nexport function useHeader(\n  headerProps: HeaderProps,\n  deps: Parameters<typeof useLayoutEffect>[1] = [],\n) {\n  const navigation = useNavigation()\n\n  /**\n   * We need to have multiple implementations of this hook for web and mobile.\n   * Web needs to use useEffect to avoid a rendering loop.\n   * In mobile and also to avoid a visible header jump when navigating between screens, we use\n   * `useLayoutEffect`, which will apply the settings before the screen renders.\n   */\n  const usePlatformEffect = Platform.OS === \"web\" ? useEffect : useLayoutEffect\n\n  // To avoid a visible header jump when navigating between screens, we use\n  // `useLayoutEffect`, which will apply the settings before the screen renders.\n  usePlatformEffect(() => {\n    navigation.setOptions({\n      headerShown: true,\n      header: () => <Header {...headerProps} />,\n    })\n    // intentionally created API to have user set when they want to update the header via `deps`\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [...deps, navigation])\n}\n"
  },
  {
    "path": "boilerplate/app/utils/useIsMounted.ts",
    "content": "import { useEffect, useCallback, useRef } from \"react\"\n/**\n * A common react custom hook to check if the component is mounted.\n * @returns {() => boolean} - A function that returns true if the component is mounted.\n */\nexport function useIsMounted() {\n  const isMounted = useRef(false)\n\n  useEffect(() => {\n    isMounted.current = true\n\n    return () => {\n      isMounted.current = false\n    }\n  }, [])\n\n  return useCallback(() => isMounted.current, [])\n}\n"
  },
  {
    "path": "boilerplate/app/utils/useSafeAreaInsetsStyle.ts",
    "content": "import { Edge, useSafeAreaInsets } from \"react-native-safe-area-context\"\n\nexport type ExtendedEdge = Edge | \"start\" | \"end\"\n\nconst propertySuffixMap = {\n  top: \"Top\",\n  bottom: \"Bottom\",\n  left: \"Start\",\n  right: \"End\",\n  start: \"Start\",\n  end: \"End\",\n}\n\nconst edgeInsetMap: Record<string, Edge> = {\n  start: \"left\",\n  end: \"right\",\n}\n\nexport type SafeAreaInsetsStyle<\n  Property extends \"padding\" | \"margin\" = \"padding\",\n  Edges extends Array<ExtendedEdge> = Array<ExtendedEdge>,\n> = {\n  [K in Edges[number] as `${Property}${Capitalize<K>}`]: number\n}\n\n/**\n * A hook that can be used to create a safe-area-aware style object that can be passed directly to a View.\n * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/utils/useSafeAreaInsetsStyle.ts/}\n * @param {ExtendedEdge[]} safeAreaEdges - The edges to apply the safe area insets to.\n * @param {\"padding\" | \"margin\"} property - The property to apply the safe area insets to.\n * @returns {SafeAreaInsetsStyle<Property, Edges>} - The style object with the safe area insets applied.\n */\nexport function useSafeAreaInsetsStyle<\n  Property extends \"padding\" | \"margin\" = \"padding\",\n  Edges extends Array<ExtendedEdge> = [],\n>(\n  safeAreaEdges: Edges = [] as unknown as Edges,\n  property: Property = \"padding\" as Property,\n): SafeAreaInsetsStyle<Property, Edges> {\n  const insets = useSafeAreaInsets()\n\n  return safeAreaEdges.reduce((acc, e) => {\n    const value = edgeInsetMap[e] ?? e\n    return { ...acc, [`${property}${propertySuffixMap[e]}`]: insets[value] }\n  }, {}) as SafeAreaInsetsStyle<Property, Edges>\n}\n"
  },
  {
    "path": "boilerplate/app.config.ts",
    "content": "import { ExpoConfig, ConfigContext } from \"@expo/config\"\n\n/**\n * Use tsx/cjs here so we can use TypeScript for our Config Plugins\n * and not have to compile them to JavaScript.\n * \n * See https://docs.expo.dev/config-plugins/plugins/#add-typescript-support-and-convert-to-dynamic-app-config\n */\nimport \"tsx/cjs\"\n\n/**\n * @param config ExpoConfig coming from the static config app.json if it exists\n *\n * You can read more about Expo's Configuration Resolution Rules here:\n * https://docs.expo.dev/workflow/configuration/#configuration-resolution-rules\n */\nmodule.exports = ({ config }: ConfigContext): Partial<ExpoConfig> => {\n  const existingPlugins = config.plugins ?? []\n\n  return {\n    ...config,\n    ios: {\n      ...config.ios,\n      // This privacyManifests is to get you started.\n      // See Expo's guide on apple privacy manifests here:\n      // https://docs.expo.dev/guides/apple-privacy/\n      // You may need to add more privacy manifests depending on your app's usage of APIs.\n      // More details and a list of \"required reason\" APIs can be found in the Apple Developer Documentation.\n      // https://developer.apple.com/documentation/bundleresources/privacy-manifest-files\n      privacyManifests: {\n        NSPrivacyAccessedAPITypes: [\n          {\n            NSPrivacyAccessedAPIType: \"NSPrivacyAccessedAPICategoryUserDefaults\",\n            NSPrivacyAccessedAPITypeReasons: [\"CA92.1\"], // CA92.1 = \"Access info from same app, per documentation\"\n          },\n        ],\n      },\n    },\n    plugins: [...existingPlugins],\n  }\n}\n"
  },
  {
    "path": "boilerplate/app.json",
    "content": "{\n  \"name\": \"HelloWorld\",\n  \"slug\": \"HelloWorld\",\n  \"scheme\": \"helloworld\",\n  \"version\": \"1.0.0\",\n  \"orientation\": \"portrait\",\n  \"userInterfaceStyle\": \"automatic\",\n  \"icon\": \"./assets/images/app-icon-all.png\",\n  \"updates\": {\n    \"fallbackToCacheTimeout\": 0\n  },\n  \"newArchEnabled\": true,\n  \"jsEngine\": \"hermes\",\n  \"assetBundlePatterns\": [\n    \"**/*\"\n  ],\n  \"android\": {\n    \"icon\": \"./assets/images/app-icon-android-legacy.png\",\n    \"package\": \"com.helloworld\",\n    \"adaptiveIcon\": {\n      \"foregroundImage\": \"./assets/images/app-icon-android-adaptive-foreground.png\",\n      \"backgroundImage\": \"./assets/images/app-icon-android-adaptive-background.png\"\n    },\n    \"allowBackup\": false,\n    \"edgeToEdgeEnabled\": true\n  },\n  \"ios\": {\n    \"icon\": \"./assets/images/app-icon-ios.png\",\n    \"supportsTablet\": true,\n    \"bundleIdentifier\": \"com.helloworld\"\n  },\n  \"web\": {\n    \"favicon\": \"./assets/images/app-icon-web-favicon.png\",\n    \"bundler\": \"metro\"\n  },\n  \"plugins\": [\n    \"expo-localization\",\n    \"expo-font\",\n    [\n      \"expo-splash-screen\",\n      {\n        \"image\": \"./assets/images/app-icon-android-adaptive-foreground.png\",\n        \"imageWidth\": 300,\n        \"resizeMode\": \"contain\",\n        \"backgroundColor\": \"#191015\"\n      }\n    ],\n    [\n      \"react-native-edge-to-edge\",\n      {\n        \"android\": {\n          \"parentTheme\": \"Light\",\n          \"enforceNavigationBarContrast\": false\n        }\n      }\n    ],\n    \"expo-build-properties\"\n  ],\n  \"experiments\": {\n    \"tsconfigPaths\": true\n  },\n  \"extra\": {\n    \"ignite\": {\n      \"version\": \"UNKNOWN\"\n    }\n  }\n}\n"
  },
  {
    "path": "boilerplate/babel.config.js",
    "content": "/** @type {import('@babel/core').TransformOptions} */\nmodule.exports = function (api) {\n  api.cache(true)\n  return {\n    presets: [\"babel-preset-expo\"],\n  }\n}\n"
  },
  {
    "path": "boilerplate/eas.json",
    "content": "{\n  \"cli\": {\n    \"version\": \">= 3.15.1\"\n  },\n  \"build\": {\n    \"development\": {\n      \"extends\": \"production\",\n      \"distribution\": \"internal\",\n      \"android\": {\n        \"gradleCommand\": \":app:assembleDebug\"\n      },\n      \"ios\": {\n        \"buildConfiguration\": \"Debug\",\n        \"simulator\": true\n      }\n    },\n    \"development:device\": {\n      \"extends\": \"development\",\n      \"distribution\": \"internal\",\n      \"ios\": {\n        \"buildConfiguration\": \"Debug\",\n        \"simulator\": false\n      }\n    },\n    \"preview\": {\n      \"extends\": \"production\",\n      \"distribution\": \"internal\",\n      \"ios\": { \"simulator\": true },\n      \"android\": { \"buildType\": \"apk\" }\n    },\n    \"preview:device\": {\n      \"extends\": \"preview\",\n      \"ios\": { \"simulator\": false }\n    },\n    \"production\": {}\n  },\n  \"submit\": {\n    \"production\": {}\n  }\n}\n"
  },
  {
    "path": "boilerplate/ignite/templates/component/NAME.tsx.ejs",
    "content": "---\ndestinationDir: app/components/<%= props.subdirectory %>\n---\nimport { StyleProp, TextStyle, View, ViewStyle } from \"react-native\"\nimport { useAppTheme } from \"@/theme/context\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { Text } from \"@/components/Text\"\n\nexport interface <%= props.pascalCaseName %>Props {\n  /**\n   * An optional style override useful for padding & margin.\n   */\n  style?: StyleProp<ViewStyle>\n}\n\n/**\n * Describe your component here\n */\nexport const <%= props.pascalCaseName %> = (props: <%= props.pascalCaseName %>Props) => {\n  const { style } = props\n  const $styles = [$container, style]\n  const { themed } = useAppTheme();\n\n  return (\n    <View style={$styles}>\n      <Text style={themed($text)}>Hello</Text>\n    </View>\n  )\n}\n\nconst $container: ViewStyle = {\n  justifyContent: \"center\",\n}\n\nconst $text: ThemedStyle<TextStyle> = ({ colors, typography }) => ({\n  fontFamily: typography.primary.normal,\n  fontSize: 14,\n  color: colors.palette.primary500,\n})\n"
  },
  {
    "path": "boilerplate/ignite/templates/navigator/NAMENavigator.tsx.ejs",
    "content": "---\ndestinationDir: app/navigators\n---\nimport { createNativeStackNavigator } from \"@react-navigation/native-stack\"\nimport { WelcomeScreen } from \"@/screens/WelcomeScreen\"\n\nexport type <%= props.pascalCaseName %>NavigatorParamList = {\n  Demo: undefined\n}\n\nconst Stack = createNativeStackNavigator<<%= props.pascalCaseName %>NavigatorParamList>()\nexport const <%= props.pascalCaseName %>Navigator = () => {\n  return (\n    <Stack.Navigator screenOptions={{ cardStyle: { backgroundColor: \"transparent\" }, headerShown: false, }}>\n      <Stack.Screen name=\"Demo\" component={WelcomeScreen} />\n    </Stack.Navigator>\n  )\n}\n"
  },
  {
    "path": "boilerplate/ignite/templates/screen/NAMEScreen.tsx.ejs",
    "content": "---\ndestinationDir: app/screens\npatches:\n- path: \"app/navigators/navigationTypes.ts\"\n  replace: \"// IGNITE_GENERATOR_ANCHOR_APP_STACK_PARAM_LIST\"\n  insert: \"<%= props.pascalCaseName %>: undefined\\n  // IGNITE_GENERATOR_ANCHOR_APP_STACK_PARAM_LIST\"\n---\nimport { FC } from \"react\"\nimport { ViewStyle } from \"react-native\"\nimport type { AppStackScreenProps } from \"@/navigators/navigationTypes\"\nimport { Screen } from \"@/components/Screen\"\nimport { Text } from \"@/components/Text\"\n// import { useNavigation } from \"@react-navigation/native\"\n\ninterface <%= props.pascalCaseName %>ScreenProps extends AppStackScreenProps<\"<%= props.pascalCaseName %>\"> {}\n\nexport const <%= props.pascalCaseName %>Screen: FC<<%= props.pascalCaseName %>ScreenProps> = () => {\n  // Pull in navigation via hook\n  // const navigation = useNavigation()\n  return (\n    <Screen style={$root} preset=\"scroll\">\n      <Text text=\"<%= props.camelCaseName %>\" />\n    </Screen>\n  )\n}\n\nconst $root: ViewStyle = {\n  flex: 1,\n}\n"
  },
  {
    "path": "boilerplate/index.tsx",
    "content": "import \"@expo/metro-runtime\" // this is for fast refresh on web w/o expo-router\nimport { registerRootComponent } from \"expo\"\n\nimport { App } from \"@/app\"\n\n// registerRootComponent calls AppRegistry.registerComponent('main', () => App);\n// It also ensures that whether you load the app in Expo Go or in a native build,\n// the environment is set up appropriately\nregisterRootComponent(App)\n"
  },
  {
    "path": "boilerplate/jest.config.js",
    "content": "/** @type {import('@jest/types').Config.ProjectConfig} */\nmodule.exports = {\n  preset: \"jest-expo\",\n  setupFiles: [\"<rootDir>/test/setup.ts\"],\n}\n"
  },
  {
    "path": "boilerplate/metro.config.js",
    "content": "/* eslint-env node */\n// Learn more https://docs.expo.io/guides/customizing-metro\nconst { getDefaultConfig } = require(\"expo/metro-config\")\n\n/** @type {import('expo/metro-config').MetroConfig} */\nconst config = getDefaultConfig(__dirname)\n\nconfig.transformer.getTransformOptions = async () => ({\n  transform: {\n    // Inline requires are very useful for deferring loading of large dependencies/components.\n    // For example, we use it in app.tsx to conditionally load Reactotron.\n    // However, this comes with some gotchas.\n    // Read more here: https://reactnative.dev/docs/optimizing-javascript-loading\n    // And here: https://github.com/expo/expo/issues/27279#issuecomment-1971610698\n    inlineRequires: true,\n  },\n})\n\n// This is a temporary fix that helps fixing an issue with axios/apisauce.\n// See the following issues in Github for more details:\n// https://github.com/infinitered/apisauce/issues/331\n// https://github.com/axios/axios/issues/6899\n// The solution was taken from the following issue:\n// https://github.com/facebook/metro/issues/1272\nconfig.resolver.unstable_conditionNames = [\"require\", \"default\", \"browser\"]\n\n// This helps support certain popular third-party libraries\n// such as Firebase that use the extension cjs.\nconfig.resolver.sourceExts.push(\"cjs\")\n\nmodule.exports = config\n"
  },
  {
    "path": "boilerplate/package.json",
    "content": "{\n  \"name\": \"hello-world\",\n  \"version\": \"0.0.1\",\n  \"private\": true,\n  \"main\": \"index.tsx\",\n  \"scripts\": {\n    \"start\": \"expo start --dev-client\",\n    \"android\": \"expo run:android\",\n    \"ios\": \"expo run:ios\",\n    \"web\": \"expo start --web\",\n    \"bundle:web\": \"npx expo export --platform web\",\n    \"serve:web\": \"npx serve dist\",\n    \"prebuild:clean\": \"npx expo prebuild --clean\",\n    \"compile\": \"tsc --noEmit -p . --pretty\",\n    \"lint\": \"eslint . --fix\",\n    \"lint:check\": \"eslint .\",\n    \"depcruise\": \"depcruise app --config .dependency-cruiser.js\",\n    \"depcruise:graph\": \"depcruise app --include-only \\\"^app\\\" --config .dependency-cruiser.js --output-type dot > app-dependency-graph.dot && dot -T svg app-dependency-graph.dot -o app-dependency-graph.svg && dot -T png app-dependency-graph.dot -o app-dependency-graph.png && rm app-dependency-graph.dot\",\n    \"align-deps\": \"npx expo install --fix\",\n    \"test\": \"jest\",\n    \"test:watch\": \"jest --watch\",\n    \"test:maestro\": \"maestro test -e MAESTRO_APP_ID=com.helloworld .maestro/flows\",\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    \"build:ios:sim\": \"eas build --profile development --platform ios --local\",\n    \"build:ios:device\": \"eas build --profile development:device --platform ios --local\",\n    \"build:ios:preview\": \"eas build --profile preview --platform ios --local\",\n    \"build:ios:prod\": \"eas build --profile production --platform ios --local\",\n    \"build:android:sim\": \"eas build --profile development --platform android --local\",\n    \"build:android:device\": \"eas build --profile development:device --platform android --local\",\n    \"build:android:preview\": \"eas build --profile preview --platform android --local\",\n    \"build:android:prod\": \"eas build --profile production --platform android --local\"\n  },\n    \"dependencies\": {\n    \"@expo-google-fonts/space-grotesk\": \"^0.4.0\",\n    \"@expo/metro-runtime\": \"~55.0.6\",\n    \"@react-navigation/bottom-tabs\": \"^7.2.0\",\n    \"@react-navigation/native\": \"^7.0.14\",\n    \"@react-navigation/native-stack\": \"^7.2.0\",\n    \"apisauce\": \"3.1.1\",\n    \"date-fns\": \"^4.1.0\",\n    \"expo\": \"55.0.5\",\n    \"expo-application\": \"~55.0.8\",\n    \"expo-build-properties\": \"~55.0.9\",\n    \"expo-dev-client\": \"~55.0.11\",\n    \"expo-font\": \"~55.0.4\",\n    \"expo-linking\": \"~55.0.7\",\n    \"expo-localization\": \"~55.0.8\",\n    \"expo-splash-screen\": \"~55.0.10\",\n    \"expo-system-ui\": \"~55.0.9\",\n    \"i18next\": \"^23.14.0\",\n    \"intl-pluralrules\": \"^2.0.1\",\n    \"react\": \"19.2.0\",\n    \"react-dom\": \"19.2.0\",\n    \"react-i18next\": \"^15.0.1\",\n    \"react-native\": \"0.83.2\",\n    \"react-native-drawer-layout\": \"^4.0.1\",\n    \"react-native-edge-to-edge\": \"~1.6.1\",\n    \"react-native-gesture-handler\": \"~2.30.0\",\n    \"react-native-keyboard-controller\": \"1.20.7\",\n    \"react-native-mmkv\": \"3.3.3\",\n    \"react-native-reanimated\": \"4.2.1\",\n    \"react-native-safe-area-context\": \"~5.6.2\",\n    \"react-native-screens\": \"~4.23.0\",\n    \"react-native-web\": \"~0.21.0\",\n    \"react-native-worklets\": \"0.7.2\",\n    \"setimmediate\": \"^1.0.5\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.20.0\",\n    \"@babel/preset-env\": \"^7.20.0\",\n    \"@babel/runtime\": \"^7.20.0\",\n    \"@testing-library/react-native\": \"^13.2.0\",\n    \"@types/jest\": \"^29.5.14\",\n    \"@types/react\": \"~19.2.10\",\n    \"babel-jest\": \"^29.2.1\",\n    \"dependency-cruiser\": \"^17.0.2\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-config-expo\": \"~55.0.0\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"eslint-plugin-prettier\": \"^5.2.1\",\n    \"eslint-plugin-react-native\": \"^4.1.0\",\n    \"eslint-plugin-reactotron\": \"^0.1.2\",\n    \"jest\": \"~29.7.0\",\n    \"jest-expo\": \"~55.0.9\",\n    \"prettier\": \"^3.3.3\",\n    \"react-test-renderer\": \"19.2.0\",\n    \"reactotron-core-client\": \"^2.9.4\",\n    \"reactotron-react-js\": \"^3.3.11\",\n    \"reactotron-react-native\": \"^5.0.5\",\n    \"reactotron-react-native-mmkv\": \"^0.2.6\",\n    \"ts-jest\": \"^29.1.1\",\n    \"tsx\": \"^4.20.3\",\n    \"typescript\": \"~5.9.2\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  }\n}"
  },
  {
    "path": "boilerplate/src/app/_layout.tsx",
    "content": "import { useEffect, useState } from \"react\"\nimport { Slot, SplashScreen } from \"expo-router\"\nimport { useFonts } from \"@expo-google-fonts/space-grotesk\"\nimport { KeyboardProvider } from \"react-native-keyboard-controller\"\nimport { initialWindowMetrics, SafeAreaProvider } from \"react-native-safe-area-context\"\n\nimport { initI18n } from \"@/i18n\"\nimport { ThemeProvider } from \"@/theme/context\"\nimport { customFontsToLoad } from \"@/theme/typography\"\nimport { loadDateFnsLocale } from \"@/utils/formatDate\"\n\nSplashScreen.preventAutoHideAsync()\n\nif (__DEV__) {\n  // Load Reactotron configuration in development. We don't want to\n  // include this in our production bundle, so we are using `if (__DEV__)`\n  // to only execute this in development.\n  require(\"@/devtools/ReactotronConfig\")\n}\n\nexport default function Root() {\n  const [fontsLoaded, fontError] = useFonts(customFontsToLoad)\n  const [isI18nInitialized, setIsI18nInitialized] = useState(false)\n\n  useEffect(() => {\n    initI18n()\n      .then(() => setIsI18nInitialized(true))\n      .then(() => loadDateFnsLocale())\n  }, [])\n\n  const loaded = fontsLoaded && isI18nInitialized\n\n  useEffect(() => {\n    if (fontError) throw fontError\n  }, [fontError])\n\n  useEffect(() => {\n    if (loaded) {\n      SplashScreen.hideAsync()\n    }\n  }, [loaded])\n\n  if (!loaded) {\n    return null\n  }\n\n  return (\n    <SafeAreaProvider initialMetrics={initialWindowMetrics}>\n      <ThemeProvider>\n        <KeyboardProvider>\n          <Slot />\n        </KeyboardProvider>\n      </ThemeProvider>\n    </SafeAreaProvider>\n  )\n}\n"
  },
  {
    "path": "boilerplate/src/app/index.tsx",
    "content": "import { WelcomeScreen } from '@/screens/WelcomeScreen'\n\nexport default function Index() {\n  return <WelcomeScreen />\n}\n"
  },
  {
    "path": "boilerplate/test/i18n.test.ts",
    "content": "import { exec } from \"child_process\"\n\nimport en from \"../app/i18n/en\"\n\n// Use this array for keys that for whatever reason aren't greppable so they\n// don't hold your test suite hostage by always failing.\nconst EXCEPTIONS: string[] = [\n  // \"welcomeScreen:readyForLaunch\",\n\n  /**\n   * This translation key actually shows up in a comment describing the usage of the translate\n   * function in the app/i18n/translate.ts file. Because the grep command in the i18n test below\n   * doesn't account for commented out code, we must manually exclude it so tests don't fail\n   * because of a comment.\n   */\n  \"hello\",\n]\n\nfunction iterate(obj, stack, array) {\n  for (const property in obj) {\n    if (Object.prototype.hasOwnProperty.call(obj, property)) {\n      if (typeof (obj as object)[property] === \"object\") {\n        iterate(obj[property], `${stack}.${property}`, array)\n      } else {\n        array.push(`${stack.slice(1)}.${property}`)\n      }\n    }\n  }\n\n  return array\n}\n\n/**\n * This tests your codebase for missing i18n strings so you can avoid error strings at render time\n *\n * It was taken from https://gist.github.com/Michaelvilleneuve/8808ba2775536665d95b7577c9d8d5a1\n * and modified slightly to account for our Ignite higher order components,\n * which take 'tx' and 'fooTx' props.\n * The grep command is nasty looking, but it's essentially searching the codebase for a few different things:\n *\n * tx=\"*\"\n * Tx=\"\"\n * tx={\"\"}\n * Tx={\"\"}\n * translate(\"\"\n *\n * and then grabs the i18n key between the double quotes\n *\n * This approach isn't 100% perfect. If you are storing your key string in a variable because you\n * are setting it conditionally, then it won't be picked up.\n *\n */\n\ndescribe(\"i18n\", () => {\n  test(\"There are no missing keys\", (done) => {\n    // Actual command output:\n    // grep \"[T\\|t]x=[{]\\?\\\"\\S*\\\"[}]\\?\\|translate(\\\"\\S*\\\"\" -ohr './app' | grep -o \"\\\".*\\\"\"\n    const command = `grep \"[T\\\\|t]x=[{]\\\\?\\\\\"\\\\S*\\\\\"[}]\\\\?\\\\|translate(\\\\\"\\\\S*\\\\\"\" -ohr './app' | grep -o \"\\\\\".*\\\\\"\"`\n    exec(command, (_, stdout) => {\n      const allTranslationsDefinedOld = iterate(en, \"\", [])\n      // Replace first instance of \".\" because of i18next namespace separator\n      const allTranslationsDefined = allTranslationsDefinedOld.map((key) => key.replace(\".\", \":\"))\n      const allTranslationsUsed = stdout.replace(/\"/g, \"\").split(\"\\n\")\n      allTranslationsUsed.splice(-1, 1)\n\n      for (let i = 0; i < allTranslationsUsed.length; i += 1) {\n        if (!EXCEPTIONS.includes(allTranslationsUsed[i])) {\n          // You can add keys to EXCEPTIONS (above) if you don't want them included in the test\n          expect(allTranslationsDefined).toContainEqual(allTranslationsUsed[i])\n        }\n      }\n      done()\n    })\n  }, 240000)\n})\n"
  },
  {
    "path": "boilerplate/test/mockFile.ts",
    "content": "export default {\n  height: 100,\n  width: 100,\n  scale: 2.0,\n  uri: \"https://placecats.com/200/200\",\n}\n"
  },
  {
    "path": "boilerplate/test/setup.ts",
    "content": "// we always make sure 'react-native' gets included first\n// eslint-disable-next-line no-restricted-imports\nimport * as ReactNative from \"react-native\"\n\nimport mockFile from \"./mockFile\"\n\n// libraries to mock\njest.doMock(\"react-native\", () => {\n  // Extend ReactNative\n  return Object.setPrototypeOf(\n    {\n      Image: {\n        ...ReactNative.Image,\n        resolveAssetSource: jest.fn((_source) => mockFile), // eslint-disable-line @typescript-eslint/no-unused-vars\n        getSize: jest.fn(\n          (\n            uri: string, // eslint-disable-line @typescript-eslint/no-unused-vars\n            success: (width: number, height: number) => void,\n            failure?: (_error: any) => void, // eslint-disable-line @typescript-eslint/no-unused-vars\n          ) => success(100, 100),\n        ),\n      },\n    },\n    ReactNative,\n  )\n})\n\njest.mock(\"i18next\", () => ({\n  currentLocale: \"en\",\n  t: (key: string, params: Record<string, string>) => {\n    return `${key} ${JSON.stringify(params)}`\n  },\n  translate: (key: string, params: Record<string, string>) => {\n    return `${key} ${JSON.stringify(params)}`\n  },\n}))\n\njest.mock(\"expo-localization\", () => ({\n  ...jest.requireActual(\"expo-localization\"),\n  getLocales: () => [{ languageTag: \"en-US\", textDirection: \"ltr\" }],\n}))\n\njest.mock(\"../app/i18n/index.ts\", () => ({\n  i18n: {\n    isInitialized: true,\n    language: \"en\",\n    t: (key: string, params: Record<string, string>) => {\n      return `${key} ${JSON.stringify(params)}`\n    },\n    numberToCurrency: jest.fn(),\n  },\n}))\n\ndeclare const tron // eslint-disable-line @typescript-eslint/no-unused-vars\n\ndeclare global {\n  let __TEST__: boolean\n}\n"
  },
  {
    "path": "boilerplate/test/test-tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"compilerOptions\": {\n    \"noImplicitAny\": false,\n    \"noUnusedLocals\": false\n  },\n  \"include\": [\"**/*.test.ts\", \"**/*.test.tsx\"]\n}"
  },
  {
    "path": "boilerplate/tsconfig.json",
    "content": "{\n  \"extends\": \"expo/tsconfig.base\",\n  \"compilerOptions\": {\n    \"allowJs\": false,\n    \"allowSyntheticDefaultImports\": true,\n    \"experimentalDecorators\": true,\n    \"jsx\": \"react-native\",\n    \"customConditions\": [\"react-native\"],\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"strict\": true,\n    \"noImplicitAny\": true,\n    \"noImplicitReturns\": true,\n    \"noImplicitThis\": true,\n    \"sourceMap\": true,\n    \"target\": \"esnext\",\n    \"lib\": [\n      \"esnext\",\n      \"dom\"\n    ],\n    \"skipLibCheck\": true,\n    \"resolveJsonModule\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./app/*\"],\n      \"@assets/*\": [\"./assets/*\"]\n    },\n    \"typeRoots\": [\n      \"./node_modules/@types\",\n      \"./types\"\n    ]\n  },\n  \"ts-node\": {\n    \"compilerOptions\": {\n      \"module\": \"commonjs\"\n    }\n  },\n  \"include\": [\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".expo/types/**/*.ts\",\n    \"expo-env.d.ts\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n    \"test/**/*\"\n  ]\n}\n"
  },
  {
    "path": "boilerplate/types/lib.es5.d.ts",
    "content": "/**\n * Fixes https://github.com/microsoft/TypeScript/issues/16655 for `Array.prototype.filter()`\n * For example, using the fix the type of `bar` is `string[]` in the below snippet as it should be.\n *\n *  const foo: (string | null | undefined)[] = [];\n *  const bar = foo.filter(Boolean);\n *\n * For related definitions, see https://github.com/microsoft/TypeScript/blob/master/src/lib/es5.d.ts\n *\n * Original licenses apply, see\n *  - https://github.com/microsoft/TypeScript/blob/master/LICENSE.txt\n *  - https://stackoverflow.com/help/licensing\n */\n\n/** See https://stackoverflow.com/a/51390763/1470607  */\ntype Falsy = false | 0 | \"\" | null | undefined\n\ninterface Array<T> {\n  /**\n   * Returns the elements of an array that meet the condition specified in a callback function.\n   * @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.\n   * @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.\n   */\n  filter<S extends T>(predicate: BooleanConstructor, thisArg?: any): Exclude<S, Falsy>[]\n}\n"
  },
  {
    "path": "docs/Guide.md",
    "content": "---\nsidebar_position: 2\n---\n\n# Getting Started Guide\n\n## What is Ignite?\n\nIgnite is best described as \"[Infinite Red](https://infinite.red)'s favorite way to build React Native apps\". It's a CLI and a boilerplate React Native project dating back to the early days of React Native (2016), as well as some command-line generators. It's also a community of like-minded developers who like the way we do things!\n\nIn short -- if you use Ignite to start your next React Native project, you're using a battle-tested, familiar stack.\n\n### Ignite CLI\n\nIn order to start a new Ignite project, you can use the CLI. No need to install it globally as it works great with `npx`:\n\n```bash\nnpx ignite-cli@latest new PizzaApp\n```\n\nIt'll walk you through several prompts to configure your package manager, navigation library and state management. Or you can simply take all the defaults via `--yes` and jump right into the demo application.\n\nOnce it's up and running, you can use the Ignite CLI to [generate](./concept/Generators.md) components, screens, React context, and more.\n\nRunning into errors? have a look at [Troubleshooting](./cli/Troubleshooting.md)\n\n### Ignite Boilerplate\n\nYour new Ignite project comes with a full stack of useful libraries, pre-set up for you so you can start coding. Some of the following are optional, but this list details the default options:\n\n- React Native\n- React Navigation\n- TypeScript\n- React Native MMKV (integrated with React context for restoring state)\n- apisauce (to talk to REST servers)\n- Reactotron-ready\n- Supports Expo (and Expo web) out of the box\n- About a dozen prebuilt [components](./boilerplate/app/components/Components.md) to build out your UI with\n- And more!\n\n## Where do I start?\n\nFirst, spin up the app and make sure you can see the initial screen. If you have any issues, please report them.\n\nOnce it's running, you'll want to get familiarized with the following concepts:\n\n### Navigation\n\nWe use React Navigation v7 in the current version of Ignite. You'll find any navigators in `./app/navigators`, with the `AppNavigator.tsx` being the primary one.\n\nThere's also a `navigationUtilities.ts` file which provides some utility functions we find useful in building apps, such as `getActiveRouteName`, `useBackButtonHandler` and `useNavigationPersistence`.\n\nLearn more in our [Navigation](./boilerplate/app/navigators/Navigation.md) documentation.\n\n### Components\n\nIgnite comes with some prebuilt, flexible, and customizable components. Unlike most component libraries, it's not built to drop in out of the box, but rather with custom design in mind (you do have a nice, custom design, don't you?)\n\nIgnite works fine with other component libraries, but the built-in component system works the best for custom-designed apps.\n\nCheck out the [Components](./boilerplate/app/components/Components.md) documentation.\n\n### Testing\n\nIgnite is pre-configured to use Jest for unit tests and [React Native Testing Library](https://callstack.github.io/react-native-testing-library/) for component tests.\n\nIgnite includes samples of UI end-to-end tests using [Maestro](https://maestro.mobile.dev/). See our [Ignite Cookbook recipe](https://ignitecookbook.com/docs/recipes/MaestroSetup) for setup and walkthrough of the test samples or check out Maestro's docs on [Installing Maestro](https://maestro.mobile.dev/getting-started/installing-maestro) to run the flows.\n\nYou can learn more about why we chose these tests and how to use them in the [Testing](./concept/Testing.md) docs.\n\n### Styling\n\nIgnite's approach to styling is, like many other things in Ignite, straightforward and blunt.\n\nWe don't use `StyleSheet.create()` as a general rule, as it doesn't provide any real benefits over bare objects.\n\nWe instead use a strategy of constants, co-located with our components, camelCase and prefixed with `$`, and typed with TypeScript:\n\n```tsx\nimport { View, ViewStyle } from \"react-native\"\nimport { useAppTheme } from \"@/theme/context\"\nimport type { ThemedStyle } from \"@/theme/types\"\n\n// This is a themed style that you must wrap with `themed()` to pass the style object.\nconst $container: ThemedStyle<ViewStyle> = ({ colors }) => ({\n  flex: 1,\n  backgroundColor: colors.palette.bgColor,\n})\n\n// This is a non-themed style\nconst $innerView: ViewStyle{\n  backgroundColor: '#fff',\n  alignItems: \"center\",\n}\n\nconst MyComponent = () => {\n  const { themed } = useAppTheme()\n  return (\n    <View style={themed($container)}>\n    <View style={$innerView}>...</View>\n    </View>\n  )\n}\n```\n\nVery often, we use [components with presets](./boilerplate/app/components/Components.md) to share styles across our whole app.\n\nRead more about styling in the [Styling](./concept/Styling.md) docs.\n\n## Patching local dependencies\n\nThe react-native ecosystem moves quickly and sometimes, you may need to patch a library locally to get it working. This is especially true for libraries that are not well maintained or have not been updated to work with the latest version of React Native. Ignite used to ship with patches (pre-v10) that were applied with `yarn` and `patch-package`, but the boilerplate no longer needs any patches by default.\n\nIgniting an app allows you to choose `npm`, `yarn`, `pnpm`, or `bun` as a package manager, so we don't have configuration in the boilerplate for patches. If you find that you need to patch a library in your app, we recommend the following:\n\n- `npm` - use [`patch-package`](https://www.npmjs.com/package/patch-package).\n- `yarn@4` - use [`yarn patch` tool](https://yarnpkg.com/features/patching).\n- `pnpm` - use [`pnpm patch <package>`](https://pnpm.io/cli/patch).\n- `bun` - use [`bun patch <package>`](https://bun.sh/docs/install/patch).\n"
  },
  {
    "path": "docs/QuickStart.md",
    "content": "---\nsidebar_position: 3\n---\n\n# Quick Start Guide\n\n## 0. Prerequisites\n\n### For development\n\nThis guide assumes you have completed the [Environment Setup](https://reactnative.dev/docs/set-up-your-environment) from the React Native docs. You'll also need the following:\n\n- [VS Code](https://code.visualstudio.com/) (or another IDE)\n- A macOS, Linux, or Windows (PowerShell or WSL2) with a terminal window open\n\nKeep in mind if you're looking to build iOS applications, you'll have to do this on macOS. Android can be developed from any OS.\n\n### For building\n\nYou'll need:\n\n- EAS CLI installed\n\n```bash\nnpm install -g eas-cli\n```\n\n- An Expo account. [Create one for free here](https://expo.dev/signup).\n\n## 1. Create your first app\n\nWith the CLI, use the new command to generate your first application.\n\n```bash\nnpx ignite-cli new MyFirstApp --yes\n```\n\n## 2. Building the app\n\nOnce the app has been created, change to the project directory and fire it up via one of the following:\n\n```bash\n# first\ncd MyFirstApp\n# then one of the following\npnpm run android\npnpm run ios\npnpm run web\n```\n\nAndroid and iOS will take a few moments to build depending on your machine's hardware.\n\n## 3. Making changes\n\nTo make the app yours, simply edit the source code in the `app/` directory (or `src/app/` if you chose to build an Expo Router project). If you have an app running from the previous step, you'll instantly see those changes appear as you save your changes.\n\n## 4. Publishing your application\n\nIgnite uses EAS Build to make the proper binaries for App Store and Google Play distribution. You can build these on EAS, a cloud service provided by Expo or run them locally yourself.\n\n```bash\n# Running a local Android build\npnpm run build:android:prod\n# or a local iOS build\npnpm build:ios:prod\n```\n\nFollow the instructions in your terminal upon running these commands. With a successful build you'll end up with binaries in the AAB or IPA format (depending on which platform you're building for). Those can be submitted to the appropriate app stores.\n\n## FAQs\n\n#### Can I run without Expo?\n\nNo, you cannot, not if you want to start your application with Ignite.\n\nIn the end, though, Ignite is just a boilerplate. Feel free to adopt application structure, code snippets, components and other logic that makes sense for your needs. If you want to opt out of the Expo ecosystem entirely (not recommended) to [build your own framework](https://reactnative.dev/docs/getting-started-without-a-framework), you can do so with the React Native CLI.\n\n#### Why is Package A included, but not Package B?\n\nThe Ignite Boilerplate represents the current stack that [Infinite Red](https://infinite.red) generally uses to start a new project. Of course, each case is different, but this is generally the stack that works well for us.\n"
  },
  {
    "path": "docs/README.md",
    "content": "---\nsidebar_position: 1\n---\n\n<p align=\"center\"><img src=\"https://user-images.githubusercontent.com/1479215/206780298-2b98221d-9c57-4cd3-866a-cf85ec1ddd9e.jpg\" alt=\"Ignite README Splash Image\" /></p>\n\n# Ignite - the battle-tested React Native boilerplate\n\n<a href=\"https://badge.fury.io/js/ignite-cli\" target=\"_blank\"><img src=\"https://badge.fury.io/js/ignite-cli.svg\" alt=\"npm version\" height=\"20\" /></a>\n![GitHub Repo stars](https://img.shields.io/github/stars/infinitered/ignite)\n![Twitter Follow](https://img.shields.io/twitter/follow/ir_ignite)\n[![CircleCI](https://dl.circleci.com/status-badge/img/gh/infinitered/ignite/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/infinitered/ignite/tree/master)\n\n## Proven React Native boilerplate\n\nDeveloped and maintained consistently since 2016, Ignite is the oldest active and most popular third-party React Native / Expo app boilerplate.\n\nThis is the React Native starting point that the [Infinite Red](https://infinite.red/react-native-app-development-company) team uses on a day-to-day basis to build client apps. Developers who use Ignite report that it saves them two to four weeks of time on average off the beginning of their React Native project!\n\n## Intro Videos\n\nHere are a few videos / talks that introduce Ignite and show off some of its features. Check them out!\n\n<table>\n  <tr>\n    <td width=\"50%\">\n      <figure>\n        <a href=\"https://www.youtube.com/watch?v=KOSvDlFyg20\">\n          <img src=\"https://img.youtube.com/vi/KOSvDlFyg20/sddefault.jpg\" alt=\"Getting Started with Ignite\"  width=\"100%\" /><br />\n        <figcaption><strong>Getting Started with Ignite</strong></figcaption>\n        </a>\n      </figure>\n    </td>\n    <td>\n      <figure>\n        <a href=\"https://www.youtube.com/watch?v=dNWkJOpD6YE&list=PLFHvL21g9bk0XOO9XK6d6S9w1jBU6Dz_U&index=16\">\n          <img src=\"https://img.youtube.com/vi/dNWkJOpD6YE/sddefault.jpg\" alt=\"Sweetening React Native Development With Ignite\" width=\"100%\" /><br />\n        <figcaption><strong>Ignite talk at Chain React 2024 - Robin Heinze</strong></figcaption>\n        </a>\n      </figure>\n    </td>\n  </tr>\n</table>\n\n## [Full Documentation](https://github.com/infinitered/ignite/blob/master/docs/README.md)\n\nWe've put great effort into the documentation as a team, please [read through it here](https://github.com/infinitered/ignite/blob/master/docs). If you're unsure why a certain decision was made related to this boilerplate or how to proceed with a particular feature, it's likely documented. If it still isn't clear, go through the proper [help channels](#reporting-bugs--getting-help) and we always welcome PRs to improve the docs!\n\n## Tech Stack\n\nNothing makes it into Ignite unless it's been proven on projects that Infinite Red works on. Ignite apps include the following rock-solid technical decisions out of the box:\n\n| Library                          | Category             | Version | Description                                    |\n| -------------------------------- | -------------------- | ------- | ---------------------------------------------- |\n| React Native                     | Mobile Framework     | v0.81   | The best cross-platform mobile framework       |\n| React                            | UI Framework         | v19     | The most popular UI framework in the world     |\n| TypeScript                       | Language             | v5      | Static typechecking                            |\n| React Navigation                 | Navigation           | v7      | Performant and consistent navigation framework |\n| Expo                             | SDK                  | v55     | Allows (optional) Expo modules                 |\n| Expo Font                        | Custom Fonts         | v14     | Import custom fonts                            |\n| Expo Localization                | Internationalization | v17     | i18n support (including RTL!)                  |\n| RN Reanimated                    | Animations           | v4      | Beautiful and performant animations            |\n| MMKV                             | Persistence          | v3      | State persistence                              |\n| apisauce                         | REST client          | v3      | Communicate with back-end                      |\n| Jest                             | Test Runner          | v29     | Standard test runner for JS apps               |\n| date-fns                         | Date library         | v4      | Excellent date library                         |\n| react-native-keyboard-controller | Keyboard library     | v1      | Great keyboard manager library                 |\n| react-native-edge-to-edge        | UI library           | v1      | Enables edge-to-edge in Android                |\n| Reactotron RN                    | Inspector/Debugger   | v5      | JS debugging                                   |\n| Maestro                          | Testing Framework    |         | Automate end-to-end UI testing                 |\n| Hermes                           | JS engine            |         | Fine-tuned JS engine for RN                    |\n\nIgnite also comes with a [component library](./boilerplate/app/components/Components.md) that is tuned for custom designs, theming support, testing, custom fonts, generators, and much, much more.\n\n## Quick Start\n\nPrerequisites:\n\n- You'll need at least a recent version of [Node](https://nodejs.org/en) to run the CLI\n- For compiling/running in a simulator, make sure you're set up for React Native by following [the official documentation](https://reactnative.dev/docs/environment-setup).\n\nThe Ignite CLI will walk you through the steps to ignite a new React Native app:\n\n```bash\n# Get walked through the prompts for the different options to start your new app\nnpx ignite-cli@latest new PizzaApp\n\n# Accept all the recommended defaults and get straight to coding!\nnpx ignite-cli@latest new PizzaApp --yes\n```\n\nOnce you're up and running, check out our [Getting Started Guide](https://docs.infinite.red/ignite-cli/Guide/).\n\nIf you'd like to follow a tutorial, check out [this one from Robin Heinze](https://shift.infinite.red/creating-a-trivia-app-with-ignite-bowser-part-1-1987cc6e93a1).\n\n### Troubleshooting\n\nThe above commands may fail with various errors, depending on your operating system and dependency versions. Some troubleshooting steps to follow:\n\n- Uninstall global versions of the Ignite CLI via `npm uninstall -g ignite-cli` and use the CLI via `npx ignite-cli`\n- Make sure you are using a reasonably recent version of Node. This can be checked via the `node --version` command. If you require multiple Node versions on your system, install `nvm`, and then run `nvm install --lts`. At the time of writing, Node LTS is v20.x.x.\n- If the installation fails because of an Xcode error (missing Xcode command line tools), the easiest way to install them is to run `sudo xcode-select --install` in your terminal.\n- If Xcode and command line tools are already installed, but the installation complains about missing patch dependencies, you may need to switch the Xcode location to something else: `sudo xcode-select -s /Applications/Xcode.app/Contents/Developer`\n- Opening the project in Xcode can give you other insights into what's happening: `open ./ios/<yourapp>.xcworkspace`\n- Add the `--debug` switch to the Ignite CLI new command to provide additional output during project initialization\n\n## Reporting Bugs / Getting Help\n\nIf you run into problems, first search the issues and discussions in this repository. If you don't find anything, you can come talk to our friendly and active developers in the Infinite Red Community Slack ([community.infinite.red](https://community.infinite.red)).\n\nIf you still need help after reaching out to the proper channels, feel free to open a new GitHub issue via `npx ignite-cli issue \"Unable to Ignite new app\"` (as an example). This will help start writing your issue with the correct diagnostic information included.\n\n## Contributing to Ignite\n\nWant to contribute to Ignite? Check out [the contributing guide](./contributing/Contributing-To-Ignite.md) for more info on how to work with the codebase.\n\n## Need Inspiration?\n\nIf you need battle-tested solutions from Infinite Red experts on everything from Accessibility, to CI/CD configuration, head to [Ignite Cookbook](https://ignitecookbook.com) for code snippets from our team and the community!\n\n## No time to learn React Native? Hire Infinite Red for your next project\n\nWe get it – sometimes there just isn’t enough time on a project to learn the ins and outs of a new framework. Infinite Red’s here to help! Our experienced team of React Native engineers have worked with companies like Microsoft, GasBuddy, Zoom, and Mercari to bring some of the most complex React Native projects to life.\n\nWhether it’s running a full project or training a team on React Native, we can help you solve your company’s toughest engineering challenges – and make it a great experience at the same time.\n\nReady to see how we can work together? [Send us a message](https://infinite.red/contact)\n\n## Further Reading\n\n- Watch Jamon Holmgren's talk at React Live Amsterdam 2019 where he uses Ignite to build an app in less than 30 minutes: [https://www.youtube.com/watch?v=OgiFKMd_TeY](https://www.youtube.com/watch?v=OgiFKMd_TeY)\n- Prior art includes [Ignite Andross](https://github.com/infinitered/ignite-andross) and [Ignite Bowser](https://github.com/infinitered/ignite-bowser) (which is very similar to the current version of Ignite).\n- [Who are We?](https://infinite.red/react-native-app-development-company) - Learn More About Infinite Red, the top React Native app development company\n\n## License and Trademark Notice\n\nThis project's source code is licensed under the [MIT License](https://github.com/infinitered/ignite/blob/master/LICENSE). The Ignite name, its logo, and any other brand assets associated with Ignite and Infinite Red are the exclusive property of Infinite Red, Inc. These marks are not covered by the MIT License provided herein and may not be used without explicit written permission from Infinite Red, Inc.\n\n### Note on Generated Code\n\nThe MIT License applies solely to the source code of the Ignite CLI and the source code of the included boilerplate project. Any source code generated by using the Ignite CLI, not including trademark assets described above, is owned entirely by the individual or entity that generated it.\n\nHowever, some files may be added or installed automatically as part of the generation process (e.g. through npm packages). These files are subject to their own licenses, which may include more restrictive terms. It is your responsibility to review and comply with the licenses of any third-party dependencies included in the generated project.\n"
  },
  {
    "path": "docs/_category_.json",
    "content": "{\n  \"label\": \"ignite-cli\",\n  \"link\": null,\n  \"customProps\": {\n    \"description\": \"\",\n    \"projectName\": \"ignite-cli\",\n    \"repoUrl\": \"https://github.com/infinitered/ignite\",\n    \"sidebar\": {\n      \"type\": \"autogenerated\",\n      \"dirName\": \"ignite-cli\"\n    }\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/Boilerplate.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Ignite's Boilerplate\n\n:::tip\nA \"boilerplate\" project is one that you can use as a starting point for your own project.\n:::\n\nAt its heart, Ignite is a boilerplate. Rather than using a basic template from something like react-native-cli or Expo, Ignite is more full-featured and opinionated. However, it still really customizable -- after all, we have many different types of projects we work on and don't want to be painted into a corner either.\n\nWhen you [spin up a new Ignite project](../Guide.md), you'll get a project with several folders. Feel free to explore each one and see what's inside.\n\n## Explanation of the Ignite folder structure\n\nA new Ignite boilerplate project's structure looks similar to this:\n\n```\nyour-project\n├── .maestro\n├── android\n├── app\n│   ├── components\n│   ├── config\n│   ├── devtools\n│   ├── i18n\n│   ├── context\n│   ├── navigators\n│   ├── screens\n│   ├── services\n│   ├── theme\n│   ├── utils\n│   ├── app.tsx\n├── assets\n├── ignite\n│   └── templates\n├── ios\n├── test\n│   ├── i18n.test.ts\n│   ├── mockFile.ts\n│   ├── setup.ts\n│   ├── test-tsconfig.json\n├── app.config.ts\n├── app.json\n├── index.tsx\n├── eas.json\n├── package.json\n└── README.md\n```\n\n### ./app directory\n\nThe vast majority of your code will live in the [/app folder](./app/app.md). This is where you'll spend most of your time.\n\n**[components](./app/components/Components.md)**\n\nThis is where your components will live, the reusable building blocks to create your screens. A handful of built-in components come with Ignite that are adaptable to any custom design system you wish to implement.\n\n**[config](./app/config/Config.md)**\n\nThis contains configuration for your app that might vary depending if you're running in development or production.\n\n**[devtools](./app/devtools/Devtools.md)**\n\nThis is where setup and configuration of devtools like Reactotron occurs.\n\n**[i18n (Internationalization)](./app/i18n/Internationalization.md)**\n\nThis is where your translations will live if you are using the included `react-native-i18n`.\n\n**[context](./app/context/Context.md)**\n\nThis is where your app's react context providers live.\n\n**[navigators](./app/navigators/Navigation.md)**\n\nThis is where your `react-navigation` navigators will live.\n\n**[screens](./app/screens/Screens.md)**\n\nThis is where your screen components will live. A screen is a React component which will take up the entire screen and be part of the navigation hierarchy. Each screen will have a directory containing the `.tsx` file, along with any assets or other helper files.\n\n**[services](./app/services/Services.md)**\n\nAny services that interface with the outside world will live here (think REST APIs, Push Notifications, etc.).\n\n**[theme](./app/theme/Theming.md)**\n\nHere lives the theme for your application, including spacing, colors, and typography.\n\n- For help with adding custom fonts to your application, check out [Fonts & Typography](../boilerplate/app/theme/typography.ts.md).\n\n**[utils](./app/utils/Utils.md)**\n\nThis is a great place to put miscellaneous helpers and utilities. Things like date helpers, formatters, etc. are often found here. However, it should only be used for things that are truly shared across your application. If a helper or utility is only used by a specific component or model, consider co-locating your helper with that component or model.\n\n**[app.tsx](./app/app.tsx.md)**\n\nThe main entry point for your app!\n\n### Root Directory\n\n#### Directories\n\n**[.maestro](./maestro.md)** - Maestro e2e tests\n\n**[android](./android.md)** - Native Android / Android Studio project files for manual workflows\n\n**[assets](./assets.md)** - icons and images\n\n**[ignite](./ignite.md)** - all things Ignite, including generator templates.\n\n**[ios](./ios.md)** - Native iOS / Xcode project files for manual workflows\n\n**[plugins](./plugins/Plugins.md)** - any custom Expo Config Plugins to be applied during the prebuild process when generating the native code for the project.\n\n**[test](./test/Test.md)** - Jest configs and mocks\n\n#### Files\n\n**[app.json/app.config.ts](./app.json.md)** - configuration files for your app. `app.json` contains the static configuration which will be fed into the dynamic configuration in `app.config.ts`, where Expo builds it's final configuration for the app.\n\n**[index.tsx](./index.tsx.md)** - entry point to your app. This is where you will find the main App component which renders the rest of the application.\n\n**[eas.json](./eas.json.md)** - build configurations for Expo EAS builds\n\n## License and Trademark Notice\n\nWe've gotten questions about whether you own the generated code or whether it still has to be MIT licensed. We want to make it clear that you own the generated code -- we don't retain any ownership of source code generated by the Ignite CLI.\n\nYou can read more about the license and trademark notice in the main project [README](../README.md#license-and-trademark-notice).\n"
  },
  {
    "path": "docs/boilerplate/_category_.json",
    "content": "{\n  \"label\": \"Boilerplate\",\n  \"position\": 2,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Boilerplate\"\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/android.md",
    "content": "---\ntitle: android\nsidebar_position: 5\n---\n\n# `android`\n\nIf you choose the `manual` workflow option when spinning up a new app (or you run `pnpm prebuild:clean`) you'll get an `android` (and probably [`ios`](./ios.md)) folder in your project root. This folder contains your native Android / Android Studio project, which has been pre-configured to work with React Native.\n\nWe generally recommend using the [Expo CNG (continuous native generation)](../expo/CNG.md) workflow, but if you need to customize your native code manually, you can do so here.\n\nJust like any React Native project, you can open this folder in Android Studio and run your app on an emulator or device. Learn more here: [https://reactnative.dev/docs/native-debugging#debugging-native-code](https://reactnative.dev/docs/native-debugging#debugging-native-code)\n"
  },
  {
    "path": "docs/boilerplate/app/_category_.json",
    "content": "{\n  \"label\": \"app\",\n  \"position\": 10,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"app\"\n  },\n  \"collapsed\": false\n}\n"
  },
  {
    "path": "docs/boilerplate/app/app.md",
    "content": "---\ntitle: app\n---\n\n# `app` folder\n\nThe vast majority of your code will live in the `app` folder. This is where you'll spend most of your time.\n\nIn this folder, there's only one file ([app.tsx](./app.tsx.md)). The rest are folders for containing components, context, screens, and more.\n\n- [app.tsx](./app.tsx.md) - The main entry point for your app\n- [components](./components/Components.md) - Reusable components\n- [config](./config/Config.md) - Configuration files\n- [devtools](./devtools/Devtools.md) - Configuration/setup for Reactotron and other dev tools\n- [i18n](./i18n/Internationalization.md) - Internationalization setup\n- [context](./context/Context.md) - React context providers\n- [navigators](./navigators/Navigation.md) - React Navigation navigators\n- [screens](./screens/Screens.md) - The main screens of your app\n- [services](./services/Services.md) - Services, such as API clients\n- [theme](./theme/Theming.md) - Theme files\n- [utils](./utils/Utils.md) - Utility functions and hooks\n"
  },
  {
    "path": "docs/boilerplate/app/app.tsx.md",
    "content": "# app.tsx\n\nThe `app/app.tsx` file is the main entry point for your app.\n\n:::tip\nDon't confuse this `app/app.tsx` file with the `index.tsx` component in the root of your project -- that's the entry point for Expo/React Native itself and just immediately loads this one after setting up the splash screen.\n:::\n\nMost of this file is boilerplate and you shouldn't need to modify it very often. But take some time to look through and understand what is going on.\n\nThings that this file is responsible for:\n\n- Loading fonts\n- Setting up internationalization\n- Initializing the root navigator\n- Ensuring everything is loaded and then hiding the splash screen\n- Setting up the safe area provider\n- Rendering error boundaries and error screen\n- Enabling deep linking\n"
  },
  {
    "path": "docs/boilerplate/app/components/AutoImage.md",
    "content": "---\nsidebar_position: 30\n---\n\n# AutoImage\n\nIgnite's `AutoImage` Component is an enhanced version of the built-in React Native [Image](https://reactnative.dev/docs/image) component. It automatically resizes the image view to fit a max width or height constraint\n\n![autoimage-component](https://github.com/user-attachments/assets/8fba1f1d-81d2-4f0d-84bb-8286b048ff16)\n\n```tsx\n<AutoImage\n  source={{ uri: \"https://pbs.twimg.com/profile_images/845384502067159040/pqF2RQ2q_400x400.jpg\" }}\n  maxWidth={200}\n/>\n```\n\n`AutoImage` uses a `useAutoImage` hook to calculate the image's dimensions when you have a specific values you need to constrain the image within. This hook is also available for use in your own components.\n\n```tsx\nconst { width, height } = useAutoImage(uri, maxWidth, maxHeight)\n```\n\n## Props\n\nIgnite's `AutoImage` component has these props of its own:\n\n### `maxWidth` and `maxHeight`\n\nThese props are used to constrain the image to a specific size. Use `maxWidth` or `maxHeight` to set the maximum width or height of the image, and it will resize to whichever dimension you specify without skewing the aspect ratio. e.g. If the image is 300w x 200h, and you set `maxWidth={200}`, the image will be resized to 200w x 133h.\n\n```tsx\n<AutoImage\n  source={{ uri: \"https://pbs.twimg.com/profile_images/845384502067159040/pqF2RQ2q_400x400.jpg\" }}\n  maxWidth={200}\n  maxHeight={200}\n/>\n```\n\n### `headers`\n\nThis props let you use the image with additional headers\n\n```tsx\n<AutoImage\n  source={{ uri: \"https://pbs.twimg.com/profile_images/845384502067159040/pqF2RQ2q_400x400.jpg\" }}\n  headers: {\n    Authorization: `Bearer abc123`,\n  }\n/>\n```\n\n## Default Image props\n\nAs `AutoImage` is a wrapper around React Native's `Image` component, it also accepts all of the props that `Image` accepts. See the [React Native Image documentation](https://reactnative.dev/docs/image) for more information.\n\n### `source`\n\nAs with React Native's built in Image component, the `source` prop is always required. This can be _almost_ anything that conforms to ReactNative's [ImageSource](https://reactnative.dev/docs/image#imagesource) type. (See [Notes](#Notes) below for caveats.)\n\n```tsx\n<AutoImage source={logoIgnite} />\n```\n\n### `style`\n\nSetting the `style` prop will override the default styles. With `AutoImage`, you generally only need to specify width _or_ height with dynamically loaded images. Setting both will override the resizing of `AutoImage` altogether, and if that is needed it's best to just use the default React Native `Image` component.\n\n```tsx\n<AutoImage source={logoIgnite} style={{ width: 200 }} />\n```\n\n```tsx\n<AutoImage source={logoIgnite} style={{ height: 200 }} />\n```\n\n## Notes\n\nAs noted above, the `source` prop can be almost anything, the one exception being an array of objects, which `AutoImage` doesn't support. See the React Native [Image#source](https://reactnative.dev/docs/image#source) documentation for more information.\n"
  },
  {
    "path": "docs/boilerplate/app/components/Button.md",
    "content": "---\nsidebar_position: 31\n---\n\n# Button\n\nThe `Button` component is a wrapper around the [`Pressable`](https://reactnative.dev/docs/pressable) component. Any prop that can be passed to `Pressable` can be passed to `Button` and it will be forwarded. `Button` renders a button with given text in a [`Text`](./Text.md) component or children. It allows you to specify the preset style of the button, you can override both the `Pressable` and `Text` styles.\n\n![button-component](https://github.com/user-attachments/assets/485e0fe9-caba-4477-ae29-39bd30107809)\n\n```tsx\n<Button\n  text=\"Click It\"\n  tx=\"button:clickIt\"\n  preset=\"default\"\n  onPress={() => Alert.alert(\"pressed\")}\n  style={[{ paddingVertical: 100 }, { borderRadius: 0 }]}\n  pressedStyle={[{ backgroundColor: \"red\" }, { borderRadius: 0 }]}\n  textStyle={[{ fontSize: 20 }, { color: \"#a511dc\" }]}\n  pressedTextStyle={[{ fontSize: 20 }, { color: \"#a51111\" }]}\n  RightAccessory={(props) => <Icon icon=\"check\" />}\n  LeftAccessory={(props) => <Icon icon=\"close\" />}\n/>\n```\n\n## Props\n\n### `text`\n\nThe `text` prop is required if `tx` or `children` are not provided. This is the text to be rendered in the button.\n\n```tsx\n<Button text=\"Click me\" />\n```\n\n### `tx`\n\nThe `tx` prop is required if `text` or `children` are not provided. This is the translation key to be used to translate the text.\n\n```tsx\n<Button tx=\"button:clickMe\" />\n```\n\n### `children`\n\nThe `children` prop is required if no `tx` or `text` prop is passed. This is the content to be rendered in the button in place of the default `Text` component.\n\n```tsx\n<Button>\n  <Text>Click me</Text>\n</Button>\n```\n\n### `preset`\n\nThe `preset` prop is optional. This is the preset style of the button. It can be one of the following built-in options: `default`, `filled`, `reversed`\n\n```tsx\n<Button preset=\"default\" tx=\"button:clickMe\" />\n```\n\nTo make a custom preset, add a key to the `$viewPresets`, `$textPresets`, `$pressedViewPresets` and `$pressedTextPresets` objects in `app/components/Button.tsx` and then pass the name of the preset to the `preset` prop.\n\n```tsx\nconst $viewPresets = {\n  // ...\n  danger: [$baseViewStyle, { backgroundColor: colors.palette.angry500 }] as StyleProp<ViewStyle>,\n}\n\nconst $textPresets: Record<Presets, StyleProp<TextStyle>> = {\n  // ...\n  danger: [$baseTextStyle, { color: colors.palette.angry500 }] as StyleProp<TextStyle>,\n}\n\nconst $pressedViewPresets: Record<Presets, StyleProp<ViewStyle>> = {\n  // ...\n  danger: { backgroundColor: colors.palette.angry500 },\n}\n\nconst $pressedTextPresets: Record<Presets, StyleProp<TextStyle>> = {\n  angry: { opacity: 0.7 },\n}\n```\n\n```tsx\n<Button preset=\"danger\" text=\"Delete\" />\n```\n\n### `textStyle`\n\nThe `textStyle` prop is optional. This can be used to style text in the button. Values passed here will override anything set in the preset.\n\n```tsx\n<Button textStyle={{ fontSize: 20, color: \"#a511dc\" }} />\n```\n\n### `pressedTextStyle`\n\nThe `pressedTextStyle` prop is optional. This can be used to style text in the button when it is pressed. Values passed here will override anything set in the preset.\n\n```tsx\n<Button pressedTextStyle={{ fontSize: 20, color: \"#a51111\" }} />\n```\n\n### `disabledTextStyle`\n\nThe `disabledTextStyle` prop is optional. It can be used to style text in the button when the `disabled` prop is set. Values here will override anything set in the preset.\n\n```tsx\n<Button disabled disabledTextStyle={{ fontSize: 20, color: \"#000000\" }} />\n```\n\n### `style`\n\nThe `style` prop is optional. This can be used to style the `Pressable` component of the `Button`. Values passed here will override anything set in the preset.\n\n```tsx\n<Button style={{ paddingVertical: 20, borderRadius: 10 }}>\n```\n\n### `pressedStyle`\n\nThe `pressedStyle` prop is optional. This can be used to style the `Pressable` component of the `Button` when it is pressed. Values passed here will override anything set in the preset.\n\n```tsx\n<Button pressedStyle={{ backgroundColor: \"red\" }} />\n```\n\n### `disabledStyle`\n\nThe `disabledStyle` prop is optional. This can be used to style the `Pressable` component of the `Button` when the `disabled` prop is truthy. Values passed here will override anything set in the preset.\n\n```tsx\n<Button disabledStyle={{ opacity: 0.5 }} />\n```\n\n### `LeftAccessory` and `RightAccessory`\n\nThe `LeftAccessory` and `RightAccessory` props are optional. They can be used to render an accessory on the left or right side of the button. It can be a React component or a function that returns a React component. The accessory component will receive the pressed state of the `Pressable` via the `pressableState` prop, so you can make a custom accessory component render differently when pressed. Additionally, you can utilize the default accessory styles via the `style` prop.\n\n```tsx\n<Button\n  LeftAccessory={(props) => (\n    <Icon containerStyle={props.style} size={props.pressableState.pressed ? 50 : 40} icon=\"check\" />\n  )}\n/>\n```\n\n```tsx\n<Button\n  RightAccessory={(props) => (\n    <Icon containerStyle={props.style} size={props.pressableState.pressed ? 50 : 40} icon=\"check\" />\n  )}\n/>\n```\n\nIf the accessories flicker when some prop or state changes, you can memoize the accessory with `useMemo`.\n\n```tsx\n<Button\n  LeftAccessory={useMemo(\n    () =>\n      function LeftIcon(props: ButtonAccessoryProps) {\n        return <Icon icon={props.pressableState.pressed ? \"view\" : \"hidden\"} />\n      },\n    [],\n  )}\n/>\n```\n\n### `disabled`\n\nThe `disabled` prop is optional. It gets passed to the underlying `Pressable` component. When truthy, it will [disable the press behavior on the pressable](https://reactnative.dev/docs/pressable#disabled). It will also update the [`accessibilityState`](https://reactnative.dev/docs/0.72/touchablewithoutfeedback#accessibilitystate) of the `Pressable` when set.\n\nThis prop will be passed down to the `LeftAccessory` and `RightAccessory` components, if they exist, and will cause the `disabledStyle` and `disabledTextStyle` props to be used, if they have been set.\n"
  },
  {
    "path": "docs/boilerplate/app/components/Card.md",
    "content": "---\nsidebar_position: 32\n---\n\n# Card\n\nThe `Card` component is intended to be used for vertically aligned related content. It is a container that can hold a heading, content, and footer. It can also hold a left and right component that will be aligned to the left and right of the card body.\n\n## Props\n\n```tsx\n<Card\n  preset=\"reversed\"\n  verticalAlignment=\"space-between\"\n  LeftComponent={<Text>Left</Text>}\n  RightComponent={<Text>Right</Text>}\n  heading=\"Card Heading\"\n  headingStyle={{ color: \"#a511dc\" }}\n  HeadingTextProps={{ weight: \"bold\" }}\n  content=\"Card Content\"\n  contentStyle={{ color: \"#a511dc\" }}\n  ContentTextProps={{ weight: \"light\" }}\n  footer=\"Card Footer\"\n  footerStyle={{ color: \"#a511dc\" }}\n  FooterTextProps={{ weight: \"medium\" }}\n/>\n```\n\n### `preset`\n\nThe `preset` prop is used to set the preset container style of the card. This affects the border and background color of the container. There are two preconfigured presets: `default` and `reversed`.\n\n![card-component-01](https://github.com/user-attachments/assets/e5a19ec9-b426-428e-ae19-a1086dc2e4bc)\n\n```tsx\n<Card preset=\"reversed\" heading=\"Card Heading\" content=\"Card Content\" footer=\"Card Footer\" />\n```\n\n### `verticalAlignment`\n\nThe `verticalAlignment` prop is used to set the vertical alignment of the card's content. This affects the alignment of the heading, content, and footer. There are four preconfigured alignments: `top`, `center`, `space-between`, and `force-footer-bottom`. `force-footer-bottom` behaves like `top`, but will force the footer to the bottom of the card.\n\n![card-component-02](https://github.com/user-attachments/assets/e5e9f331-6c4d-4ce3-833d-a00fdf7244f1)\n\n```tsx\n<Card\n  verticalAlignment=\"space-between\"\n  heading=\"Card Heading\"\n  content=\"Card Content\"\n  footer=\"Card Footer\"\n/>\n```\n\n### `LeftComponent` & `RightComponent`\n\nThe `LeftComponent` and `RightComponent` props are used to set the component that will be aligned to the left or right of the card body, respectively.\n\n![card-component-03](https://github.com/user-attachments/assets/68df8495-ee9b-452f-b86f-b39ee76a052c)\n\n```tsx\n<Card\n  LeftComponent={\n    <AutoImage\n      maxWidth={80}\n      maxHeight={60}\n      style={{ alignSelf: \"center\" }}\n      source={{\n        uri: \"https://user-images.githubusercontent.com/1775841/184508739-f90d0ce5-7219-42fd-a91f-3382d016eae0.png\",\n      }}\n    />\n  }\n  RightComponent={\n    <Button preset=\"default\" text=\"Click It\" onPress={() => Alert.alert(\"pressed\")} />\n  }\n  heading=\"Card Heading\"\n  content=\"Card Content\"\n  footer=\"Card Footer\"\n/>\n```\n\n### `heading`\n\nThe `heading` prop is used to set the heading text of the card.\n\n```tsx\n<Card heading=\"Card Heading\" content=\"Card Content\" footer=\"Card Footer\" />\n```\n\n### `headingTx`\n\nThe `headingTx` prop is used to set the heading text of the card using a translation key.\n\n```tsx\n<Card headingTx=\"card:heading\" content=\"Card Content\" footer=\"Card Footer\" />\n```\n\n### `headingTxOptions`\n\nThe `headingTxOptions` prop is used to set the options for the translation key used by the `headingTx` prop.\n\n```tsx\n<Card\n  headingTx=\"card:heading\"\n  headingTxOptions={{ count: 2 }}\n  content=\"Card Content\"\n  footer=\"Card Footer\"\n/>\n```\n\n### `headingStyle`\n\nThe `headingStyle` prop is used to set the style of the heading text.\n\n```tsx\n<Card\n  heading=\"Card Heading\"\n  headingStyle={{ color: \"red\" }}\n  content=\"Card Content\"\n  footer=\"Card Footer\"\n/>\n```\n\n### `HeadingTextProps`\n\nThe `HeadingTextProps` prop is used to pass any additional props to the heading `Text` component. It will accept any prop that the [`Text`](./Text.md) component accepts.\n\n```tsx\n<Card\n  heading=\"Card Heading\"\n  HeadingTextProps={{ size: \"lg\" }}\n  content=\"Card Content\"\n  footer=\"Card Footer\"\n/>\n```\n\n### `HeadingComponent`\n\nThe `HeadingComponent` prop is used to set the component that will be used for the heading. This can be used to set a custom heading component.\n\n```tsx\n<Card\n  HeadingComponent={<Button preset=\"reversed\" text=\"HeadingComponent\" icon=\"ladybug\" />}\n  content=\"Card Content\"\n  footer=\"Card Footer\"\n/>\n```\n\n### `content`\n\nThe `content` prop is used to set the content text of the card.\n\n```tsx\n<Card heading=\"Card Heading\" content=\"Card Content\" footer=\"Card Footer\" />\n```\n\n### `contentTx`\n\nThe `contentTx` prop is used to set the content text of the card using a translation key.\n\n```tsx\n<Card heading=\"Card Heading\" contentTx=\"card:content\" footer=\"Card Footer\" />\n```\n\n### `contentTxOptions`\n\nThe `contentTxOptions` prop is used to set the options for the translation key used by the `contentTx` prop.\n\n```tsx\n<Card\n  heading=\"Card Heading\"\n  contentTx=\"card:content\"\n  contentTxOptions={{ count: 2 }}\n  footer=\"Card Footer\"\n/>\n```\n\n### `contentStyle`\n\nThe `contentStyle` prop is used to set the style of the content text.\n\n```tsx\n<Card\n  heading=\"Card Heading\"\n  content=\"Card Content\"\n  contentStyle={{ backgroundColor: colors.error, color: colors.palette.neutral100 }}\n  footer=\"Card Footer\"\n/>\n```\n\n### `ContentTextProps`\n\nThe `ContentTextProps` prop is used to pass any additional props to the content `Text` component. It will accept any prop that the [`Text`](./Text.md) component accepts.\n\n```tsx\n<Card\n  heading=\"Card Heading\"\n  content=\"Card Content\"\n  ContentTextProps={{ size: \"lg\" }}\n  footer=\"Card Footer\"\n/>\n```\n\n### `ContentComponent`\n\nThe `ContentComponent` prop is used to set the component that will be used for the content. This can be used to set a custom content component.\n\n```tsx\n<Card\n  heading=\"Card Heading\"\n  ContentComponent={<Button preset=\"reversed\" text=\"ContentComponent\" icon=\"ladybug\" />}\n  footer=\"Card Footer\"\n/>\n```\n\n### `footer`\n\nThe `footer` prop is used to set the footer text of the card.\n\n```tsx\n<Card heading=\"Card Heading\" content=\"Card Content\" footer=\"Card Footer\" />\n```\n\n### `footerTx`\n\nThe `footerTx` prop is used to set the footer text of the card using a translation key.\n\n```tsx\n<Card heading=\"Card Heading\" content=\"Card Content\" footerTx=\"card:footer\" />\n```\n\n### `footerTxOptions`\n\nThe `footerTxOptions` prop is used to set the options for the translation key used by the `footerTx` prop.\n\n```tsx\n<Card\n  heading=\"Card Heading\"\n  content=\"Card Content\"\n  footerTx=\"card:footer\"\n  footerTxOptions={{ count: 2 }}\n/>\n```\n\n### `footerStyle`\n\nThe `footerStyle` prop is used to set the style of the footer text.\n\n```tsx\n<Card\n  heading=\"Card Heading\"\n  content=\"Card Content\"\n  footer=\"Card Footer\"\n  footerStyle={{ color: \"red\" }}\n/>\n```\n\n### `FooterTextProps`\n\nThe `FooterTextProps` prop is used to pass any additional props to the footer `Text` component. It will accept any prop that the [`Text`](./Text.md) component accepts.\n\n```tsx\n<Card\n  heading=\"Card Heading\"\n  content=\"Card Content\"\n  footer=\"Card Footer\"\n  FooterTextProps={{ size: \"lg\" }}\n/>\n```\n\n### `FooterComponent`\n\nThe `FooterComponent` prop is used to set the component that will be used for the footer. This can be used to set a custom footer component.\n\n```tsx\n<Card\n  heading=\"Card Heading\"\n  content=\"Card Content\"\n  FooterComponent={<Button preset=\"reversed\" text=\"FooterComponent\" icon=\"ladybug\" />}\n/>\n```\n"
  },
  {
    "path": "docs/boilerplate/app/components/Checkbox.md",
    "content": "---\nsidebar_position: 32\n---\n\nimport ToggleProps from './\\_toggle_props.mdx';\n\n# Checkbox\n\nThe `Checkbox` component provides a simple way to collect user input for a boolean value.\n\n## Checkbox Props\n\n### `icon`\n\nThe `icon` is a prop for the checkbox variant that allows you to customize the icon used for the \"on\" state.\n\n```tsx\n<Checkbox icon=\"ladybug\" />\n```\n\n<ToggleProps componentName=\"Checkbox\" />\n"
  },
  {
    "path": "docs/boilerplate/app/components/Components.md",
    "content": "---\nsidebar_position: 3\n---\n\n# Ignite Built-in Components\n\nIgnite comes with a number of customizable built-in React Native components -- sort of a lightweight design system, in a way. It's the system we (at Infinite Red) tend to use the most often with our own custom mobile designs, and is designed to emphasize flexibility and customizability _over_ out-of-the-box power.\n\nThere are a number of other options out there that work great with Ignite -- [UI Kitten](https://akveo.github.io/react-native-ui-kitten/), [RN Elements](https://reactnativeelements.com/), and more. But if you're building something with a totally custom design, Ignite's built-in components work great.\n\n## Components\n\nHere's a summary of each component. Click through to view detailed documentation and code examples!\n\n### AutoImage\n\nThis is a wrapper around React Native's [Image](https://reactnative.dev/docs/image) component, which automatically resizes the image to fit the container.\n\n```tsx\n<AutoImage\n  source={{ uri: \"https://pbs.twimg.com/profile_images/845384502067159040/pqF2RQ2q_400x400.jpg\" }}\n/>\n```\n\n[Full AutoImage Component Documentation](./AutoImage.md)\n\n### Button\n\nThis is a component that renders a [`TouchableOpacity`](https://reactnative.dev/docs/touchableopacity) with given text or children.\n\n```tsx\n<Button\n  text=\"Click It\"\n  tx=\"button:clickIt\"\n  preset=\"primary\"\n  onPress={() => Alert.alert(\"pressed\")}\n  style={[{ paddingVertical: 100 }, { borderRadius: 0 }]}\n  textStyle={[{ fontSize: 20 }, { color: \"#a511dc\" }]}\n/>\n```\n\n```tsx\n<Button onPress={() => Alert.alert(\"pressed\")}>\n  <Text>Click It</Text>\n</Button>\n```\n\n[Full Button Component Documentation](./Button.md)\n\n### Card\n\nThe `Card` component is useful for displaying related information in a contained way. Where you'll use `ListItem` for horizontal information, `Card` can be used for vertical information.\n\n```tsx\n<Card\n  preset=\"reversed\"\n  verticalAlignment=\"space-between\"\n  LeftComponent={<Text>Left</Text>}\n  RightComponent={<Text>Right</Text>}\n  heading=\"Card Heading\"\n  headingStyle={{ color: \"#a511dc\" }}\n  HeadingTextProps={{ weight: \"bold\" }}\n  content=\"Card Content\"\n  contentStyle={{ color: \"#a511dc\" }}\n  ContentTextProps={{ weight: \"light\" }}\n  footer=\"Card Footer\"\n  footerStyle={{ color: \"#a511dc\" }}\n  FooterTextProps={{ weight: \"medium\" }}\n/>\n```\n\n[Full Card Component Documentation](./Card.md)\n\n### Checkbox\n\nThe `Checkbox` component is useful for displaying a user's choice for a boolean value.\n\n```tsx\n<Checkbox\n  value={value}\n  icon=\"check\"\n  onValueChange={setValue}\n  labelTx=\"signup:rememberMe\"\n  labelStyle={{ color: \"#a511dc\" }}\n  containerStyle={{ backgroundColor: \"#fff\" }}\n/>\n```\n\n[Full Checkbox Component Documentation](./Checkbox.md)\n\n### EmptyState\n\nThe `EmptyState` component can be used when there is no data to display and direct the user on how to proceed.\n\n```tsx\n<EmptyState\n  preset=\"default\"\n  style={{ padding: 10 }}\n  imageSource={require(\"@assets/images/sad-face.png\")}\n  imageStyle={{ height: 400, width: 400 }}\n  ImageProps={{ resizeMode: \"contain\" }}\n  heading=\"EmptyState Heading\"\n  headingStyle={{ color: \"#a511dc\" }}\n  HeadingTextProps={{ weight: \"bold\" }}\n  content=\"EmptyState Content\"\n  contentStyle={{ color: \"#a511dc\" }}\n  ContentTextProps={{ weight: \"light\" }}\n  button=\"Press here\"\n  buttonOnPress={handleButtonPress}\n/>\n```\n\n[Full EmptyState Component Documentation](./EmptyState.md)\n\n### Header\n\nThe `Header` component is a component that will appear at the top of your screen. It is used to hold navigation buttons and the screen title.\n\n```tsx\n<Header\n  headerTx=\"header:title\"\n  headerText=\"Header Title\"\n  leftIcon=\"back\"\n  rightIcon=\"bullet\"\n  onLeftPress={() => navigation.goBack()}\n  onRightPress={() => Alert.alert(\"pressed\")}\n  style={{ backgroundColor: \"purple\" }}\n  titleStyle={{ color: \"white\" }}\n/>\n```\n\n[Full Header Component Documentation](./Header.md)\n\n### Icon\n\nThis is a component that renders an icon.\n\n```tsx\n<Icon\n  icon=\"back\"\n  color=\"#a511dc\"\n  size={30}\n  containerStyle={{ backgroundColor: \"#fff\" }}\n  style={{ resizeMode: \"contain\" }}\n  onPress={() => Alert.alert(\"pressed\")}\n/>\n```\n\n[Full Icon Component Documentation](./Icon.md)\n\n### Radio\n\nThe `Radio` component is useful for displaying a user's choice for a boolean value.\n\n```tsx\n<Radio\n  value={value}\n  onValueChange={setValue}\n  labelTx=\"signup:rememberMe\"\n  labelStyle={{ color: \"#a511dc\" }}\n  containerStyle={{ backgroundColor: \"#fff\" }}\n/>\n```\n\n[Full Radio Component Documentation](./Radio.md)\n\n### Screen\n\nThis is a component that renders a screen. It is used to wrap your entire screen, and handles scrolling, [safe areas](https://reactnavigation.org/docs/handling-safe-area/), and keyboard avoiding behavior.\n\n```tsx\n<Screen preset=\"scroll\">\n  <Header headerTitle=\"screen\" />\n  // ... content here ...\n</Screen>\n```\n\n[Full Screen Component Documentation](./Screen.md)\n\n### Switch\n\nThe `Switch` component is useful for displaying a user's choice for a boolean value.\n\n```tsx\n<Switch\n  value={value}\n  accessibilityMode=\"icon\"\n  onValueChange={setValue}\n  labelTx=\"signup:rememberMe\"\n  labelStyle={{ color: \"#a511dc\" }}\n  containerStyle={{ backgroundColor: \"#fff\" }}\n/>\n```\n\n[Full Switch Component Documentation](./Switch.md)\n\n### Text\n\nThis is an enhanced version of the built-in React Native Text component. It adds internationalization and property presets.\n\n```tsx\n<Text\n  preset=\"header\"\n  tx=\"welcome:header\"\n  txOptions={{\n    name: currentUser.name,\n  }}\n  style={$header}\n/>\n```\n\n[Full Text Component Documentation](./Text.md)\n\n### TextField\n\nThis component renders a View with a [`TextInput`](https://reactnative.dev/docs/textinput) and a text label.\n\n```tsx\nconst [input, setInput] = useState(\"\")\nconst inputRef = useRef()\n<TextField\n  value={input}\n  onChangeText={setInput}\n  labelTx=\"signup:name\"\n  placeholderTx=\"signup:nameplaceholder\"\n  style={$header}\n  inputStyle={$inputStyle}\n  preset=\"default\"\n  forwardedRef={inputRef}\n/>\n```\n\n[Full Text Component Documentation](./TextField.md)\n\n## Custom Components\n\nIgnite includes a generator for creating custom components. If the built in components don't fit your needs, you can create your own.\n\n`npx ignite-cli generate component MyCustomButton`\n\nRunning the generator will create a new component in the `components` directory.\n\n```\n-- app\n  -- components\n    -- MyCustomButton.tsx\n```\n"
  },
  {
    "path": "docs/boilerplate/app/components/EmptyState.md",
    "content": "---\nsidebar_position: 33\n---\n\n# EmptyState\n\nThe `EmptyState` component is to be used when there is no data to display, usually after attempting to load some content from an external API. It is a container that can hold a heading and content. It can also display an image and provide a button to interact with.\n\n## Props\n\n```tsx\n<EmptyState\n  preset=\"generic\"\n  style={{ padding: 10 }}\n  imageSource={require(\"@assets/images/sad-face.png\")}\n  imageStyle={{ height: 400, width: 400 }}\n  ImageProps={{ resizeMode: \"contain\" }}\n  heading=\"EmptyState Heading\"\n  headingStyle={{ color: \"#a511dc\" }}\n  HeadingTextProps={{ weight: \"bold\" }}\n  content=\"EmptyState Content\"\n  contentStyle={{ color: \"#a511dc\" }}\n  ContentTextProps={{ weight: \"light\" }}\n  button=\"Press here\"\n  buttonOnPress={handleButtonPress}\n/>\n```\n\n### `preset`\n\nThe `preset` prop is used to set the preset container style of the EmptyState. This affects the default image, heading, content and button. Currently, only one preconfigured preset exists: `generic`.\n![empty-state](https://github.com/user-attachments/assets/aa8bca01-24f2-45e5-977d-5f6ac949d580)\n\n```tsx\n<EmptyState preset=\"generic\" heading=\"EmptyState Heading\" content=\"EmptyState Content\" />\n```\n\n### `style`\n\nThe `style` prop is used to set the style override for the container.\n\n```tsx\n<EmptyState\n  style={{ padding: 10, marginTop: 20 }}\n  heading=\"EmptyState Heading\"\n  content=\"EmptyState Content\"\n/>\n```\n\n### `imageSource`\n\nThe `imageSource` prop is used to set the Image source to be displayed above the heading.\n\n```tsx\n<EmptyState\n  imageSource={require(\"@assets/images/empty-state.png\")}\n  heading=\"EmptyState Heading\"\n  content=\"EmptyState Content\"\n/>\n```\n\n### `imageStyle`\n\nThe `imageStyle` prop is used to set any style overrides to be applied to the image about the heading.\n\n```tsx\n<EmptyState\n  imageSource={require(\"@assets/images/empty-state.png\")}\n  imageStyle={{ borderRadius: 5 }}\n  heading=\"EmptyState Heading\"\n  content=\"EmptyState Content\"\n/>\n```\n\n### `ImageProps`\n\nThe `ImageProps` prop is used to pass any additional props directly to the `Image` component. It will accept any prop that the `Image` component accepts.\n\n```tsx\n<EmptyState\n  imageSource={require(\"@assets/images/empty-state.png\")}\n  ImageProps={{ onLoad: handleImageLoaded }}\n  heading=\"EmptyState Heading\"\n  content=\"EmptyState Content\"\n/>\n```\n\n### `heading`\n\nThe `heading` prop is used to set the heading text of the EmptyState.\n\n```tsx\n<EmptyState heading=\"EmptyState Heading\" content=\"EmptyState Content\" button=\"EmptyState Button\" />\n```\n\n### `headingTx`\n\nThe `headingTx` prop is used to set the heading text of the EmptyState using a translation key.\n\n```tsx\n<EmptyState\n  headingTx=\"EmptyState:heading\"\n  content=\"EmptyState Content\"\n  button=\"EmptyState Button\"\n/>\n```\n\n### `headingTxOptions`\n\nThe `headingTxOptions` prop is used to set the options for the translation key used by the `headingTx` prop.\n\n```tsx\n<EmptyState\n  headingTx=\"EmptyState:heading\"\n  headingTxOptions={{ count: 2 }}\n  content=\"EmptyState Content\"\n  button=\"EmptyState Button\"\n/>\n```\n\n### `headingStyle`\n\nThe `headingStyle` prop is used to set the style of the heading text.\n\n```tsx\n<EmptyState\n  heading=\"EmptyState Heading\"\n  headingStyle={{ color: \"red\" }}\n  content=\"EmptyState Content\"\n  button=\"EmptyState Button\"\n/>\n```\n\n### `HeadingTextProps`\n\nThe `HeadingTextProps` prop is used to pass any additional props to the heading `Text` component. It will accept any prop that the [`Text`](./Text.md) component accepts.\n\n```tsx\n<EmptyState\n  heading=\"EmptyState Heading\"\n  HeadingTextProps={{ size: \"lg\" }}\n  content=\"EmptyState Content\"\n  button=\"EmptyState Button\"\n/>\n```\n\n### `content`\n\nThe `content` prop is used to set the content text of the EmptyState.\n\n```tsx\n<EmptyState heading=\"EmptyState Heading\" content=\"EmptyState Content\" button=\"EmptyState Button\" />\n```\n\n### `contentTx`\n\nThe `contentTx` prop is used to set the content text of the EmptyState using a translation key.\n\n```tsx\n<EmptyState\n  heading=\"EmptyState Heading\"\n  contentTx=\"EmptyState:content\"\n  button=\"EmptyState Button\"\n/>\n```\n\n### `contentTxOptions`\n\nThe `contentTxOptions` prop is used to set the options for the translation key used by the `contentTx` prop.\n\n```tsx\n<EmptyState\n  heading=\"EmptyState Heading\"\n  contentTx=\"EmptyState:content\"\n  contentTxOptions={{ count: 2 }}\n  button=\"EmptyState Button\"\n/>\n```\n\n### `contentStyle`\n\nThe `contentStyle` prop is used to set the style of the content text.\n\n```tsx\n<EmptyState\n  heading=\"EmptyState Heading\"\n  content=\"EmptyState Content\"\n  contentStyle={{ backgroundColor: colors.error, color: colors.palette.neutral100 }}\n  button=\"EmptyState Button\"\n/>\n```\n\n### `ContentTextProps`\n\nThe `ContentTextProps` prop is used to pass any additional props to the content `Text` component. It will accept any prop that the [`Text`](./Text.md) component accepts.\n\n```tsx\n<EmptyState\n  heading=\"EmptyState Heading\"\n  content=\"EmptyState Content\"\n  ContentTextProps={{ size: \"lg\" }}\n  button=\"EmptyState Button\"\n/>\n```\n\n### `button`\n\nThe `button` prop is used to set the button text of the EmptyState.\n\n```tsx\n<EmptyState heading=\"EmptyState Heading\" content=\"EmptyState Content\" button=\"EmptyState Button\" />\n```\n\n### `buttonTx`\n\nThe `buttonTx` prop is used to set the button text of the EmptyState using a translation key.\n\n```tsx\n<EmptyState\n  heading=\"EmptyState Heading\"\n  content=\"EmptyState Content\"\n  buttonTx=\"EmptyState:button\"\n/>\n```\n\n### `buttonTxOptions`\n\nThe `buttonTxOptions` prop is used to set the options for the translation key used by the `buttonTx` prop.\n\n```tsx\n<EmptyState\n  heading=\"EmptyState Heading\"\n  content=\"EmptyState Content\"\n  buttonTx=\"EmptyState:button\"\n  buttonTxOptions={{ count: 2 }}\n/>\n```\n\n### `buttonStyle`\n\nThe `buttonStyle` prop is used to set the style overrides of the button.\n\n```tsx\n<EmptyState\n  heading=\"EmptyState Heading\"\n  content=\"EmptyState Content\"\n  button=\"EmptyState Button\"\n  buttonStyle={{ backgroundColor: \"red\" }}\n/>\n```\n\n### `buttonTextStyle`\n\nThe `buttonTextStyle` prop is used to set the style of the button text.\n\n```tsx\n<EmptyState\n  heading=\"EmptyState Heading\"\n  content=\"EmptyState Content\"\n  button=\"EmptyState Button\"\n  buttonTextStyle={{ color: \"red\" }}\n/>\n```\n\n### `ButtonProps`\n\nThe `ButtonProps` prop is used to pass any additional props to the `Button` component. It will accept any prop that the [`Button`](./Button.md) component accepts.\n\n```tsx\n<EmptyState\n  heading=\"EmptyState Heading\"\n  content=\"EmptyState Content\"\n  ButtonProps={{ preset: \"reversed\" }}\n/>\n```\n"
  },
  {
    "path": "docs/boilerplate/app/components/Header.md",
    "content": "---\nsidebar_position: 34\n---\n\n# Header\n\nThe `Header` component is a component that will appear at the top of your screen. It is used to hold navigation buttons and the screen title.\n\n![header-component](https://github.com/user-attachments/assets/ab308ec1-21e8-41dc-a7f3-bbc6cac866e0)\n\n```tsx\n<Header\n  titleTx=\"header:title\"\n  title=\"Header Title\"\n  leftIcon=\"back\"\n  rightIcon=\"bullet\"\n  onLeftPress={() => navigation.goBack()}\n  onRightPress={() => Alert.alert(\"pressed\")}\n  style={{ height: 60 }}\n  backgroundColor=\"purple\"\n  titleStyle={{ color: \"white\" }}\n/>\n```\n\n## Props\n\n### `titleMode`\n\nThe layout of the title relative to the action components.\n\n`center` will force the title to always be centered relative to the header. If the title or the action buttons are too long, the title will be cut off.\n`flex` will attempt to center the title relative to the action buttons. If the action buttons are different widths, the title will be off-center relative to the header.\n\n### `titleStyle`\n\nThe `titleStyle` prop is an optional prop that is used to set the style of the header title. This is a [`StyleProp<TextStyle>`](https://reactnative.dev/docs/text-style-props) object.\n\n```tsx\n<Header title=\"Header Title\" titleStyle={{ color: \"white\" }} />\n```\n\n### `titleContainerStyle`\n\nThe `titleContainerStyle` prop is an optional prop that is used to set the style of the header title's outer container. This is a [`StyleProp<ViewStyle>`](https://reactnative.dev/docs/view-style-props) object.\n\n```tsx\n<Header title=\"Header Title\" titleContainerStyle={{ backgroundColor: \"purple\" }} />\n```\n\n### `containerStyle`\n\nThe `containerStyle` prop is an optional prop that is used to set the style of the header's outer container. This is useful specifically on notched devices to override insets. This is a [`StyleProp<ViewStyle>`](https://reactnative.dev/docs/view-style-props) object.\n\n```tsx\n<Header title=\"Header Title\" containerStyle={{ backgroundColor: \"purple\" }} />\n```\n\n### `style`\n\nThe `style` prop is an optional prop that is used to set the style of the header's inner container. You can use this to override the header's height. This is a [`StyleProp<ViewStyle>`](https://reactnative.dev/docs/view-style-props) object.\n\n```tsx\n<Header title=\"Header Title\" style={{ height: 50 }} />\n```\n\n### `backgroundColor`\n\nThe `backgroundColor` prop is an optional prop that is used to set the background color of the header's outer container.\n\n```tsx\n<Header title=\"Header Title\" onLeftPress={() => navigation.goBack()} backgroundColor=\"purple\" />\n```\n\n### `title`\n\nThe `title` is an optional prop that is used to set the header title. If this is not set, the `titleTx` prop must be present to set the title. If both are set, the `title` value will be used.\n\n```tsx\n<Header title=\"Header Title\" leftIcon=\"back\" onLeftPress={() => navigation.goBack()} />\n```\n\n### `titleTx`\n\nThe `titleTx` is an optional prop that is used to lookup the translation for the header title. If this is not set, the `title` prop must be present to set the header title. If both are set, the `title` value will be used.\n\n```tsx\n<Header titleTx=\"header:title\" leftIcon=\"back\" onLeftPress={() => navigation.goBack()} />\n```\n\n### `titleTxOptions`\n\nThe `titleTxOptions` is an optional prop that is used to pass props to the translation lookup for the header title. This is useful if you need to pass in dynamic values to the translation.\n\n```tsx\n<Header\n  titleTx=\"header:title\"\n  titleTxOptions={{ name: \"John\" }}\n  leftIcon=\"back\"\n  onLeftPress={() => navigation.goBack()}\n/>\n```\n\n### `leftIcon`\n\nThe `leftIcon` is an optional prop that is used to set the icon for the left navigation button. Options are 'back', 'bullet', and 'bug'. Custom icons can be created by using the [`Icon` component](./Icon.md#custom-icons). Once you create a custom icon, just pass the string name of the icon to the `leftIcon` prop.\n\n```tsx\n<Header titleTx=\"header:title\" leftIcon=\"back\" onLeftPress={() => navigation.goBack()} />\n```\n\n### `leftIconColor`\n\nThe `leftIconColor` is an optional prop that is used to set the tint color of the left navigation icon.\n\n```tsx\n<Header\n  titleTx=\"header:title\"\n  leftIcon=\"back\"\n  leftIconColor=\"white\"\n  onLeftPress={() => navigation.goBack()}\n/>\n```\n\n### `leftText`\n\nThe `leftText` is an optional prop that is used to set the text for the left navigation button. Overrides the `leftIcon` prop.\n\n```tsx\n<Header titleTx=\"header:title\" leftText=\"Back\" onLeftPress={() => navigation.goBack()} />\n```\n\n### `leftTx`\n\nThe `leftTx` is an optional prop that is used to lookup the translation for the left navigation button. Overrides the `leftIcon` and `leftText` prop`.\n\n```tsx\n<Header titleTx=\"header:title\" leftTx=\"header:back\" onLeftPress={() => navigation.goBack()} />\n```\n\n### `leftTxOptions`\n\nThe `leftTxOptions` is an optional prop that is used to pass props to the translation lookup for the left navigation button. This is useful if you need to pass in dynamic values to the translation.\n\n```tsx\n<Header\n  titleTx=\"header:title\"\n  leftTx=\"header:back\"\n  leftTxOptions={{ name: \"John\" }}\n  onLeftPress={() => navigation.goBack()}\n/>\n```\n\n### `LeftActionComponent`\n\nThe `LeftActionComponent` is an optional `ReactElement` prop that is used to set a custom component for the left navigation button. Overrides the `leftIcon`, `leftText`, `leftTx`, and `onLeftText` props (since the passed component is completely customizable).\n\n```tsx\n<Header titleTx=\"header:title\" LeftActionComponent={<Text>Back</Text>} />\n```\n\n### `onLeftPress`\n\nThe `onLeftPress` is an optional prop that is used to set the function to be called when the left navigation button is pressed.\n\n```tsx\n<Header titleTx=\"header:title\" leftIcon=\"back\" onLeftPress={() => navigation.goBack()} />\n```\n\n### `rightIcon`\n\nThe `rightIcon` is an optional prop that is used to set the icon for the right navigation button. Custom icons can be created by using the [`Icon` component](./Icon.md#custom-icons). Once you create a custom icon, just pass the string name of the icon to the `rightIcon` prop.\n\n```tsx\n<Header titleTx=\"header:title\" rightIcon=\"back\" onRightPress={() => navigation.goBack()} />\n```\n\n### `rightIconColor`\n\nThe `rightIconColor` is an optional prop that is used to set the tint color of the right navigation icon.\n\n```tsx\n<Header\n  titleTx=\"header:title\"\n  rightIcon=\"back\"\n  onRightPress={() => navigation.goBack()}\n  rightIconColor=\"white\"\n/>\n```\n\n### `rightText`\n\nThe `rightText` is an optional prop that is used to set the text for the right navigation button. Overrides the `rightIcon` prop.\n\n```tsx\n<Header titleTx=\"header:title\" rightText=\"Back\" onRightPress={() => navigation.goBack()} />\n```\n\n### `rightTx`\n\nThe `rightTx` is an optional prop that is used to lookup the translation for the right navigation button. Overrides the `rightIcon` and `rightText` prop`.\n\n```tsx\n<Header titleTx=\"header:title\" rightTx=\"header:back\" onRightPress={() => navigation.goBack()} />\n```\n\n### `rightTxOptions`\n\nThe `rightTxOptions` is an optional prop that is used to pass props to the translation lookup for the right navigation button. This is useful if you need to pass in dynamic values to the translation.\n\n```tsx\n<Header\n  titleTx=\"header:title\"\n  rightTx=\"header:back\"\n  rightTxOptions={{ name: \"John\" }}\n  onRightPress={() => navigation.goBack()}\n/>\n```\n\n### `RightActionComponent`\n\nThe `RightActionComponent` is an optional `ReactElement` prop that is used to set a custom component for the right navigation button. Overrides the `rightIcon`, `rightText`, `rightTx`, and `onRightPress` props (since the right action component can customize all that).\n\n```tsx\n<Header titleTx=\"header:title\" RightActionComponent={<Text>Back</Text>} />\n```\n\n### `onRightPress`\n\nThe `onRightPress` is an optional prop that is used to set the function to be called when the right navigation button is pressed.\n\n```tsx\n<Header titleTx=\"header:title\" rightIcon=\"back\" onRightPress={() => navigation.goBack()} />\n```\n\n### `safeAreaEdges`\n\nThe `safeAreaEdges` optional prop can be used to override the default safe area edges. By default, the header will use the `top` safe area edge. If you want to not account for the safe area edges, you can pass in `[]` to the `safeAreaEdges` prop.\n\n```tsx\n<Header titleTx=\"header:title\" safeAreaEdges={[]} />\n```\n\n## Usage\n\nThe Header is a flexible component that can be integrated within your application using a few different methods:\n\n### Method 1 (recommended) - Using `navigation.setOptions()` method in your screen component or `useHeader()` hook.\n\nThis method gives you the most control over your Header and co-locates the logic inside of your screen while preserving react-navigation's optimizations by keeping it outside of the screen component's render lifecycle.\n\n```tsx\nfunction AccountScreen(props) {\n  const { navigation } = props\n\n  useLayoutEffect(() => {\n    navigation.setOptions({\n      headerShown: true,\n      header: () => <Header title=\"Hello\" />,\n    })\n  }, [])\n\n  return <Screen />\n}\n```\n\nA convenience [`useHeader`](../utils/useHeader.tsx.md) hook is provided that abstracts and cleans up some of this logic.\n\n```tsx\nfunction AccountScreen(props) {\n  useHeader({\n    title: \"Hello\",\n  })\n\n  return <Screen />\n}\n```\n\n### Method 2 - Directly in the render of you screen component.\n\nThe Header can be rendered directly in your screen's body. This gives you the most control over the Header component at the expense of performance as it is now a part of the screen's render lifecycle.\n\n```tsx\nfunction AccountScreen(props) {\n  return (\n    <View>\n      <Header title=\"Hello\" />\n    </View>\n  )\n}\n```\n\nIf you prefer this method, it might be a good idea to memoize the component to prevent unnecessary re-renders.\n\n### Method 3 - Defining the header in your navigator/screen config.\n\nIf your Header shares a lot of the same logic within a navigator, it might be a better solution to set the Header inside the navigator config or navigator's screen config. For any customization between screens, you will still need to set the Header props using `navigation.setOptions()`. Additionally, the Header might need to be updated/refactored to use react-navigation's header properties as it isn't very compatible out-of-the-box.\n\n```tsx\n<Stack.Navigator\n  screenOptions={{\n    header: (props) => <Header title={props.options.headerTitle ?? props.route.name} />,\n  }}\n/>\n```\n\nOr, you can define it on the screen config.\n\n```tsx\n<Stack.Navigator screenOptions={{ headerShown: false }}>\n  <Stack.Screen\n    name=\"Welcome\"\n    component={WelcomeScreen}\n    options={{ headerShown: true, header: () => <Header title=\"Hello\" /> }}\n  />\n</Stack.Navigator>\n```\n"
  },
  {
    "path": "docs/boilerplate/app/components/Icon.md",
    "content": "---\nsidebar_position: 35\n---\n\n# Icon\n\nIgnite's `Icon` and `PressableIcon` Components render an icon using predefined icon images. You can use those, override them, or customize this component to create any number of image based icons. `PressableIcon` will wrap the icon in a [`TouchableOpacity`](https://reactnative.dev/docs/touchableopacity) component, `Icon` will use a [`View`](https://reactnative.dev/docs/view) component.\n\n![icon-component](https://github.com/user-attachments/assets/25888c72-8bd9-4cbd-b55f-909b0f6b0bca)\n\n```tsx\n<PressableIcon icon=\"ladybug\" onPress={() => Alert.alert(\"Hello\")} />\n```\n\n```tsx\n<Icon icon=\"debug\" />\n```\n\n## Props\n\nOther than these props, you can pass any props that `TouchableOpacity` or `View` support and they will be forwarded to the respective wrapper component.\n\n### `icon`\n\nThe `icon` prop is required. This is the string name of the icon to be rendered. The options are:\n\n- back\n- bell\n- caretLeft\n- caretRight\n- check\n- community\n- components\n- debug\n- heart\n- hidden\n- ladybug\n- lock\n- menu\n- more\n- pin\n- settings\n- view\n- x\n\n```tsx\n<Icon icon=\"bell\" />\n```\n\nYou can easily change or add [custom icons](#custom-icons) to the `Icon` component.\n\n### `color`\n\nThe `color` optional prop is a string that will be used to set the [`tintColor`](https://reactnative.dev/docs/image-style-props#tintcolor) of the icon image.\n\n```tsx\n<Icon icon=\"x\" color=\"#7C7C7C\">\n```\n\n### `size`\n\nThe `size` optional prop is a number that is used to set the dimensions of the icon image.\n\n```tsx\n<Icon icon=\"x\" size={24} />\n```\n\n### `style`\n\nThe `style` prop is optional. This is an object that overrides the default style of the icon, and is of the type `StyleProp<ImageStyle>`. By default this just sets the image's `resizeMode` to `contain`. See React Native docs on [ImageStyle](https://reactnative.dev/docs/image#style) for more information.\n\n```tsx\n<Icon icon=\"ladybug\" style={{ width: 20, height: 20 }} />\n```\n\n### `containerStyle`\n\nThe `containerStyle` is an optional prop that sets the style of the icon container (either a `TouchableOpacity` or `View`), and is of the type `StyleProp<ViewStyle>`. See React Native docs on [ViewStyle](https://reactnative.dev/docs/view-style-props) for more information.\n\n```tsx\n<Icon icon=\"bug\" containerStyle={{ backgroundColor: \"red\" }} />\n```\n\n### `onPress`\n\nThe `onPress` prop can be passed to a `PressableIcon` Component, which will forward it to the wrapped `TouchableOpacity`s `onPress` prop.\n\n```tsx\n<PressableIcon icon=\"ladybug\" onPress={() => Alert.alert(\"Hello\")} />\n```\n\n## Custom Icons\n\nTo create your own custom icon, add your icon image(s) to the `assets/icons/` directory and then add it with its own name to the `iconRegistry` object in `app/components/Icon.tsx`.\n\n```\n-- icon/\n  -- icons/\n    -- index.ts\n    -- my-custom-icon.png\n```\n\n```tsx\nexport const iconRegistry = {\n  // ...\n  custom: require(\"./myCustomIcon.png\"),\n}\n```\n\nYou can then use your custom icon by passing its name through the `icon` prop.\n\n```tsx\n<Icon icon=\"custom\" />\n```\n\n```tsx\n<PressableIcon icon=\"custom\" onPress={() => Alert.alert(\"I'm a custom PressableIcon!\")} />\n```\n"
  },
  {
    "path": "docs/boilerplate/app/components/ListItem.md",
    "content": "---\nsidebar_position: 36\n---\n\n# ListItem\n\nThe `ListItem` component is a component that is used to display a single item in a list. It provides a lot of flexibility in terms of what you can do with it. It can be used to display a simple piece text, or a complex component with multiple actionable and custom styled elements inside.\n\n![listitem-component](https://github.com/user-attachments/assets/009aed59-5597-4d0b-b861-972608ddb8ea)\n\n```tsx\n<ListItem height={50} />\n```\n\n## Props\n\nAs the `ListItem` component is interactable, it includes a `TouchableOpacity` component, which means that in addition to the custom props listed here, you can pass any props that are valid for a `TouchableOpacity` component.\n\n### `height`\n\nThe `height` prop is used to set the height of the `ListItem` component. The default is `56`.\n\n```tsx\n<ListItem height={50} />\n```\n\n### `topSeparator` and `bottomSeparator`\n\nThe `topSeparator` and `bottomSeparator` props are used to display a separator above or below the `ListItem` component. The default is `false`.\n\n```tsx\n<ListItem topSeparator={true} bottomSeparator={true} />\n```\n\n### `text`\n\nThe `text` prop is used to display a simple piece of text inside the `ListItem` component.\n\n```tsx\n<ListItem text=\"Hello World\" />\n```\n\n### `tx`\n\nThe `tx` prop is used to display a simple piece of text inside the `ListItem` component. It is used to display a localized string.\n\n```tsx\n<ListItem tx=\"example:helloWorld\" />\n```\n\n### `children`\n\nThe `children` prop is used to display components inside the `ListItem` component. Note that these will be nested inside a [`Text`](./Text.md) component.\n\n```tsx\n<ListItem height={100}>\n  <Text>Subtext</Text>\n</ListItem>\n```\n\n### `txOptions`\n\nThe `txOptions` prop is used to pass options to the `tx` prop. It is used to display a localized string.\n\n```tsx\n<ListItem tx=\"example:helloWorld\" txOptions={{ name: \"John\" }} />\n```\n\n### `textStyle`\n\nThe `textStyle` prop is used to pass a style to the `Text` component that is used to display the text inside the `ListItem` component.\n\n```tsx\n<ListItem text=\"Hello World\" textStyle={{ color: \"red\" }} />\n```\n\n### `TextProps`\n\nThe `TextProps` prop is used to pass any additional props directly to the [`Text`](./Text.md) component.\n\n```tsx\n<ListItem text=\"Hello World\" TextProps={{ weight: \"bold\" }} />\n```\n\n### `containerStyle`\n\nThe `containerStyle` prop is used to pass a style to the `View` component that is used to display the `ListItem` component.\n\n```tsx\n<ListItem text=\"Hello World\" containerStyle={{ backgroundColor: \"red\" }} />\n```\n\n### `style`\n\nThe `style` prop is used to pass a style to the `TouchableOpacity` component that is used to display the `ListItem` component.\n\n```tsx\n<ListItem text=\"Hello World\" style={{ backgroundColor: \"red\" }} />\n```\n\n### `leftIcon` and `rightIcon`\n\nThe `leftIcon` and `rightIcon` props are used to display an icon on the left or right side of the `ListItem` component, respectively.\n\n```tsx\n<ListItem text=\"Hello World\" leftIcon=\"bell\" rightIcon=\"bell\" />\n```\n\n### `leftIconColor` and `rightIconColor`\n\nThe `leftIconColor` and `rightIconColor` props are used to set the color of the icon on the left or right side of the `ListItem` component, respectively.\n\n```tsx\n<ListItem\n  text=\"Hello World\"\n  leftIcon=\"bell\"\n  leftIconColor=\"red\"\n  rightIcon=\"bell\"\n  rightIconColor=\"red\"\n/>\n```\n\n### `RightComponent` and `LeftComponent`\n\nThe `RightComponent` and `LeftComponent` props are `ReactElement` objects used to display a component on the left or right side of the `ListItem` component, respectively.\n\n```tsx\n<ListItem\n  text=\"Hello World\"\n  LeftComponent={<Text>Left</Text>}\n  RightComponent={<Text>Right</Text>}\n/>\n```\n"
  },
  {
    "path": "docs/boilerplate/app/components/Radio.md",
    "content": "---\nsidebar_position: 37\n---\n\nimport ToggleProps from './\\_toggle_props.mdx';\n\n# Radio\n\nThe `Radio` component provides a simple way to collect user input for a boolean value.\n\n<ToggleProps componentName=\"Radio\" />\n"
  },
  {
    "path": "docs/boilerplate/app/components/Screen.md",
    "content": "---\nsidebar_position: 38\n---\n\n# Screen\n\nThis is a component that renders a screen. It is used to wrap your entire screen, and handles scrolling, [safe areas insets](https://reactnavigation.org/docs/handling-safe-area/), and keyboard avoiding behavior.\n\n```tsx\n<Screen preset=\"scroll\">{/* ... content here ... */}</Screen>\n```\n\n## Props\n\n### `children`\n\nAs the `Screen` component is a top level wrapper component, it is expected that you will pass in your screen's content as children.\n\n```tsx\n<Screen preset=\"scroll\">{/* ... content here ... */}</Screen>\n```\n\n### `style`\n\nThe `style` prop is an optional `StyleProp<ViewStyle>` object that applies to the outer content `View` component. This is useful for applying styles such as `margin` and `padding`.\n\n```tsx\n<Screen style={{ padding: 10 }}>{/* ... content here ... */}</Screen>\n```\n\n### `contentContainerStyle`\n\nThe `contentContainerStyle` prop is an optional `StyleProp<ViewStyle>` object that applies to the inner content `View` component that wraps the `children`. This is useful for applying styles to the innermost component, such as `margin` and `padding`.\n\n```tsx\n<Screen contentContainerStyle={{ margin: 10 }}>{/* ... content here ... */}</Screen>\n```\n\n### `safeAreaEdges`\n\nThe `safeAreaEdges` prop is an an array of `SafeAreaEdges` that determines which edges of the screen should be considered safe areas. This is useful for handling the notch on iPhone X and other devices. Usage of this prop depends on how the Screen is used. If you have a Header above the screen, the \"top\" Edge can be omitted. If you have a TabBar, the \"bottom\" edge can be omitted. In other cases, a value of `[\"top\", \"bottom\"]` is recommended. The default value is `undefined`.\n\n```tsx\n<Screen safeAreaEdges={[\"top\", \"bottom\"]}>{/* ... content here ... */}</Screen>\n```\n\n### `backgroundColor`\n\nThe `backgroundColor` prop is an optional `string` that determines the background color of the outer wrapper component. The default value is `colors.background`.\n\n```tsx\n<Screen backgroundColor=\"red\">{/* ... content here ... */}</Screen>\n```\n\n### `systemBarStyle`\n\nThe `systemBarStyle` prop is a prop that determines the style of the system bar. It can be either `\"light\"` or `\"dark\"`. The default value is `\"dark\"`.\n\n```tsx\n<Screen systemBarStyle=\"light\">{/* ... content here ... */}</Screen>\n```\n\n### `keyboardOffset`\n\nThe `keyboardOffset` prop is an optional `number` that determines the offset of the keyboard when it is shown. It is passed to the `keyboardVerticalOffset` of the `KeyboardAvoidingView`. The default value is `0`.\n\n```tsx\n<Screen keyboardOffset={10}>{/* ... content here ... */}</Screen>\n```\n\n### `SystemBarProps`\n\nThe `SystemBarProps` prop is an object that is passed as props to the `react-native-edge-to-edge` [`SystemBar`](https://github.com/zoontek/react-native-edge-to-edge/) component.\n\n```tsx\n<Screen SystemBarProps={{ animated: false }}>{/* ... content here ... */}</Screen>\n```\n\n### `KeyboardAvoidingViewProps`\n\nThe `KeyboardAvoidingViewProps` prop is an object that is passed as props to the [`KeyboardAvoidingView`](https://reactnative.dev/docs/keyboardavoidingview).\n\n```tsx\n<Screen KeyboardAvoidingViewProps={{ behavior: \"padding\" }}>{/* ... content here ... */}</Screen>\n```\n\n### `preset`\n\nThe `preset` prop is an optional enum that applies to the outer `KeyboardAvoidingView` component. The predefined presets with ignite deal with different use cases for scroll behavior and keyboard avoiding behavior. It defaults to `scroll` behavior if not set.\n\nThe predefined presets are:\n\n- `scroll` - A preset that applies a scroll behavior to the screen. This is useful for forms or other screens which require a keyboard.\n- `fixed` - A preset that applies a fixed behavior to the screen. i.e. The screen will not scroll. This is useful if you have a component such as a `FlatList` that has its own scrolling behavior.\n- `auto` - A preset that applies an automatic behavior to the screen. i.e. The screen will scroll if the content is larger than the screen, but not otherwise.\n\n```tsx\n<Screen preset=\"scroll\">{/* ... content here ... */}</Screen>\n```\n\n### `keyboardShouldPersistTaps`\n\nThe `keyboardShouldPersistTaps` optional prop is a enum that determines if the keyboard should persist taps. It defaults to `\"handled\"`. This only applies for the `scroll` preset, because it is passed into the React Native [`ScrollView`](https://facebook.github.io/react-native/docs/scrollview.html) component under its `keyboardShouldPersistTaps` prop.\n\nThe valid values for this prop are: `\"handled\"`, `\"always\"`,and `\"never\"`.\n\n```tsx\n<Screen preset=\"scroll\" keyboardShouldPersistTaps=\"never\">\n  {/* ... content here ... */}\n</Screen>\n```\n\n### `ScrollViewProps`\n\nThe `ScrollViewProps` prop is an object that is passed as props to the React Native [`ScrollView`](https://facebook.github.io/react-native/docs/scrollview.html) component. This only applies for the `scroll` preset.\n\n```tsx\n<Screen preset=\"scroll\" ScrollViewProps={{ contentContainerStyle: { padding: 10 } }}>\n  {/* ... content here ... */}\n</Screen>\n```\n\n### `scrollEnabledToggleThreshold`\n\nThe `scrollEnabledToggleThreshold` prop is an optional `number` that determines the threshold at which the `scrollEnabled` prop of the `ScrollView` is toggled. This only applies for the `auto` preset. The default value is `{ percent: 0.92 }`. You can pass a point value in lieu of a percentage, e.g. `{ point: 100 }` will enable scrolling when the scroll view height is less than 100 points larger than the scroll view content height.\n\n```tsx\n<Screen preset=\"scroll\" scrollEnabledToggleThreshold={{ percent: 0.95 }}>\n  {/* ... content here ... */}\n</Screen>\n```\n"
  },
  {
    "path": "docs/boilerplate/app/components/Switch.md",
    "content": "---\nsidebar_position: 38\n---\n\nimport ToggleProps from './\\_toggle_props.mdx';\n\n# Switch\n\nThe `Switch` component provides a simple way to collect user input for a boolean value.\n\n## Switch Props\n\n### `accessibilityMode`\n\nThe `accessibilityMode` is a special prop for the switch variant that adds a text/icon label for on/off states. Options are `text` and `icon`\n\n```tsx\n<Switch value={value} onValueChange={setValue} accessibilityMode=\"icon\" />\n```\n\n<ToggleProps componentName=\"Switch\" />\n"
  },
  {
    "path": "docs/boilerplate/app/components/Text.md",
    "content": "---\nsidebar_position: 39\n---\n\n# Text\n\nIgnite's `Text` Component is an enhanced version of the built-in React Native [`Text`](https://reactnative.dev/docs/text) component. It adds internationalization and several useful (and customizable) property presets. You shouldn't need the built-in React Native Text component if you use this. It does everything the built-in one does and more.\n\nBy enhancing the Ignite Text component and using it across your app, you can make sure the right fonts, font weight, and other styles and behaviors are shared across your whole app.\n\n![text-component](https://github.com/user-attachments/assets/61277e64-c530-4043-93fe-5da41c9e9351)\n\n## Props\n\n### `text`\n\nThe `text` optional prop is the text of the component. We encourage you to not use this but rather use the `tx` prop instead.\n\n```tsx\n<Text text=\"My Text\" />\n```\n\n### `tx`\n\nThe `tx` optional prop is the string key used to look up the translated text for the user's locale. Ignite uses [`i18next`](https://www.i18next.com/) for internationalization.\n\n````tsx\n\n```tsx\n<Text tx=\"welcomeScreen:readyForLaunch\" />\n````\n\n### `txOptions`\n\nThe `txOptions` optional prop is an object of options to pass to i18n. Useful for [interpolation](https://www.i18next.com/) as well as explicitly setting locale or translation fallbacks. You'll be defining these in the `app/i18n/*.json` files, and can use `{{variableName}}` for interpolation.\n\n```tsx\n// in en.json\nprofile: {\n  details: \"{{name}} who is {{age}} years old\"\n}\n```\n\n```tsx\n// in your component\n<Text\n  tx=\"profile:details\"\n  txOptions={{\n    name: \"Jamon\",\n    age: 40,\n  }}\n/>\n```\n\n### `style`\n\nThe `style` optional prop is an object with overrides for this particular component. You can use `style` overrides with presets.\n\n```tsx\n<Text tx=\"welcome:title\" style={{ fontSize: 40 }} />\n```\n\n### `weight`\n\nThe `weight` optional prop is the font weight to use for the text. It utilizes the fonts defined in the `app/theme/typography.tsx` file.\n\n```tsx\n<Text tx=\"welcome:title\" weight=\"medium\" />\n```\n\n### `size`\n\nThe `size` optional prop is the font size to use for the text. The options are defined as `$sizeStyles` in `app/components/Text.tsx`. You can add sizes as you need to the `$sizeStyles` object and use them in your `Text` component.\n\n```tsx\n<Text tx=\"welcome:title\" size=\"lg\" />\n```\n\n### `preset`\n\nThe `preset` optional prop specifies the string of the preset style you want to use. You can use style overrides with presets.\n\n```tsx\n<Text preset=\"bold\" text=\"This is bold\" />\n```\n\n## Presets\n\nPresets allow you to have a consistent look and feel across your app without having to redefine the styles all the time.\n\n```tsx\n<View preset=\"heading\" text=\"My Header\" />\n```\n\nYou'll want to customize these presets and add more. You can do this in the `text.presets.ts` file.\n\nIf you find yourself overriding styles with the `style` prop a lot, you probably need a new preset for that use case.\n\n### Available Presets\n\n`default`: The default text styles.\n\n`bold`: A bold version of the default text.\n\n`heading`: Large headers.\n\n`subheading`: A smaller piece of secondary information.\n\n`formLabel`: Form input label.\n\n`formHelper`: Form assistive information.\n\n### Custom Presets\n\nFeel free to add your own presets by emulating the style you see with the provided presets. In a typical Infinite Red project, we will have a dozen or more presets that we use across the project. Simply add a new key to the `$presets` object in `app/components/Text.tsx` and then pass the name of the preset to the `preset` prop.\n\n```tsx\n$presets = {\n  label: [$baseStyle, $sizeStyles.md, $fontWeightStyles.medium],\n}\n```\n\n```tsx\n<Text preset=\"label\" text=\"Email\" />\n```\n"
  },
  {
    "path": "docs/boilerplate/app/components/TextField.md",
    "content": "---\nsidebar_position: 40\n---\n\n# TextField\n\nIgnite's `TextField` Component is an enhanced version of the built-in React Native [`TextInput`](https://reactnative.dev/docs/textinput) component. It consists of TextInput and a Text(./Text.md) label.\n\nWith this component you will be able to standardize TextInput component across your app.\n\n![textfield-component](https://github.com/user-attachments/assets/cfdc97dc-5692-4286-8682-9243ee0e7650)\n\n```tsx\nimport { TextField } from '../components';\n\nconst [input, setInput] = useState(\"\")\n<TextField\n  value={input}\n  onChangeText={(value) => setInput(value)}\n  status=\"error\"\n  label=\"Name\"\n  labelTx=\"login.nameLabel\"\n  labelTxOptions={{ name: \"John\" }}\n  LabelTextProps={{ style: { color: \"#FFFFFF\" } }}\n  placeholder=\"John Doe\"\n  placeholderTx=\"login.namePlaceholder\"\n  placeholderTxOptions={{ name: \"John\" }}\n  helper=\"Enter your name\"\n  helperTx=\"login.nameHelper\"\n  helperTxOptions={{ name: \"John\" }}\n  HelperTextProps={{ style: { color: \"#FFFFFF\" } }}\n  style={{ backgroundColor: \"#BFBFBF\" }}\n  containerStyle={{ backgroundColor: \"#BFBFBF\" }}\n  inputWrapperStyle={{ backgroundColor: \"#BFBFBF\" }}\n  RightAccessory={() => <Icon icon=\"check\" />}\n  LeftAccessory={() => <Icon icon=\"bell\" />}\n/>\n\n```\n\n## Props\n\nThe `TextField` component accepts all the props of the built-in React Native [`TextInput`](https://reactnative.dev/docs/textinput) component which will be forwarded to the `TextInput` component, as well as the following props:\n\n### `status`\n\nThe `status` prop is used to set an `'error'` or `'disabled'` state on the component. The default value is `null`. You can use it to show an error style for validations or to disable the component. By default the `'error'` status will set the `borderColor` on the input wrapper to whatever `colors.error` is set to. Setting the status to `'disabled'` will disable editing on the `TextInput` component.\n\n```tsx\n<TextField value={input} onChangeText={(value) => setInput(value)} status=\"error\" />\n```\n\n### `label`\n\nThe `label` optional prop is a string that is used to set the label. If this is not set, the `labelTx` prop must be present to set the label. If both are set, the `label` value will be used.\n\n```tsx\n<TextField value={input} onChangeText={(value) => setInput(value)} label=\"Name\" />\n```\n\n### `labelTx`\n\nThe `labelTx` optional prop is the string key used to look up the translated text for the user's locale. Ignite uses [`i18next`](https://www.i18next.com/) for internationalization. If this is not set, the `label` prop must be present to set the label. If both are set, the `label` value will be used.\n\n```tsx\n<TextField value={input} onChangeText={(value) => setInput(value)} labelTx=\"signup.name\" />\n```\n\n### `labelTxOptions`\n\nThe `labelTxOptions` is an optional prop that is used to pass props to the translation lookup for the header title. This is useful if you need to pass in dynamic values to the translation.\n\n```tsx\n<TextField\n  value={input}\n  labelTx=\"signup.name\"\n  labelTxOptions={{ name: \"John\" }}\n  onChangeText={(value) => setInput(value)}\n/>\n```\n\n### `LabelTextProps`\n\nThe `LabelTextProps` is an optional prop that is used to pass props to the [`Text`](./Text.md) component that renders the label.\n\n```tsx\n<TextField\n  value={input}\n  labelTx=\"signup.name\"\n  onChangeText={(value) => setInput(value)}\n  LabelTextProps={{ style: { color: \"red\" } }}\n/>\n```\n\n### `helper`\n\nThe `helper` optional prop is a string that is used to set the helper text. If this is not set, the `helperTx` prop must be present to set the helper text. If both are set, the `helper` value will be used. The helper text is rendered with a [`Text`](./Text.md) component.\n\n```tsx\n<TextField value={input} onChangeText={(value) => setInput(value)} helper=\"This is a helper text\" />\n```\n\n### `helperTx`\n\nThe `helperTx` optional prop is the string key used to look up the translated text for the user's locale. Ignite uses [`i18next`](https://www.i18next.com/) for internationalization. If this is not set, the `helper` prop must be present to set the helper text. If both are set, the `helper` value will be used.\n\n```tsx\n<TextField value={input} onChangeText={(value) => setInput(value)} helperTx=\"signup.name\" />\n```\n\n### `helperTxOptions`\n\nThe `helperTxOptions` is an optional prop that is used to pass props to the translation lookup for the helper text. This is useful if you need to pass in dynamic values to the translation.\n\n```tsx\n<TextField\n  value={input}\n  helperTx=\"login.name\"\n  helperTxOptions={{ name: \"John\" }}\n  onChangeText={(value) => setInput(value)}\n/>\n```\n\n### `HelperTextProps`\n\nThe `HelperTextProps` is an optional prop that is used to pass props to the [`Text`](./Text.md) component that renders the helper text.\n\n```tsx\n<TextField\n  value={input}\n  helper=\"Name\"\n  onChangeText={(value) => setInput(value)}\n  HelperTextProps={{ style: { color: \"red\" } }}\n/>\n```\n\n### `placeholder`\n\nThe `placeholder` optional prop is a string that is used to set the placeholder. If this is not set, the `placeholderTx` prop must be present to set the placeholder. If both are set, the `placeholder` value will be used.\n\n```tsx\n<TextField value={input} onChangeText={(value) => setInput(value)} placeholder=\"Name\" />\n```\n\n### `placeholderTx`\n\nThe `placeholderTx` optional prop is the string key used to look up the translated text for the user's locale. Ignite uses [`i18next`](https://www.i18next.com/) for internationalization. If this is not set, the `placeholder` prop must be present to set the placeholder. If both are set, the `placeholder` value will be used.\n\n```tsx\n<TextField value={input} onChangeText={(value) => setInput(value)} placeholderTx=\"signup.name\" />\n```\n\n### `placeholderTxOptions`\n\nThe `placeholderTxOptions` is an optional prop that is used to pass props to the translation lookup for the placeholder text. This is useful if you need to pass in dynamic values to the translation.\n\n```tsx\n<TextField value={input} onChangeText={(value) => setInput(value)} />\n```\n\n### `style`\n\nThe `style` optional prop is an object used to override the input style.\n\n```tsx\n<TextField\n  value={input}\n  onChangeText={(value) => setInput(value)}\n  style={{ backgroundColor: \"red\" }}\n/>\n```\n\n### `containerStyle`\n\nThe `containerStyle` optional prop is an object used to override the container style.\n\n```tsx\n<TextField\n  value={input}\n  onChangeText={(value) => setInput(value)}\n  containerStyle={{ backgroundColor: \"red\" }}\n/>\n```\n\n### `inputWrapperStyle`\n\nThe `inputWrapperStyle` optional prop is an object used to override the input wrapper style.\n\n```tsx\n<TextField\n  value={input}\n  onChangeText={(value) => setInput(value)}\n  inputWrapperStyle={{ backgroundColor: \"red\" }}\n/>\n```\n\n### `RightAccessory` and `LeftAccessory`\n\nThe `RightAccessory` and `LeftAccessory` optional props are components that are rendered on the right and left sides of the input, respectively. This is useful for rendering icons or buttons. The [`status`](#status), `multiline` from the `TextInputProps`, `editable` (negation of `disabled` status), and a default `style` attribute are passed into it via props for custom usage.\n\n```tsx\n<TextField\n  value={input}\n  onChangeText={(value) => setInput(value)}\n  RightAccessory={(props) => (\n    // props has `multiline`, `status`, `disabled`, and `style` attributes\n    {disabled, status} = props\n\n    if (!!disabled) return <Icon icon=\"lock\" color=\"gray\" />\n    if (status === 'error') return <Icon icon=\"x\" color=\"red\" />\n\n    return <Icon icon=\"check\" color=\"green\" />\n  )}\n/>\n```\n\nIt's also recommended to use `useMemo` on accessories to prevent flickering, as without `useMemo` they will rerender whenever the input value changes.\n\n```tsx\nconst PasswordRightAccessory = useMemo(\n  () =>\n    function PasswordRightAccessory(props: TextFieldAccessoryProps) {\n      return (\n        <Icon\n          icon={isAuthPasswordHidden ? \"view\" : \"hidden\"}\n          color={colors.palette.neutral800}\n          containerStyle={props.style}\n          onPress={() => setIsAuthPasswordHidden(!isAuthPasswordHidden)}\n        />\n      )\n    },\n  [isAuthPasswordHidden],\n)\n```\n\nThis could then be passed to the `TextField` component directly.\n\n```tsx\n<TextField\n  value={password}\n  onChangeText={(value) => setPassword(password)}\n  RightAccessory={PasswordRightAccessory}\n/>\n```\n"
  },
  {
    "path": "docs/boilerplate/app/components/_category_.json",
    "content": "{\n  \"label\": \"components\",\n  \"position\": 5,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Components\"\n  },\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "docs/boilerplate/app/components/_toggle_props.mdx",
    "content": "import CodeBlock from \"@theme/CodeBlock\"\n\n## Toggle Props\n\n![toggle-component](https://github.com/user-attachments/assets/ffbbe61e-9aea-4895-ab19-d38f76b3e379)\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} />`}</CodeBlock>\n\n### `status`\n\nThe `status` prop is used to determine the interactability or style of the toggle. It can be set to `disabled` or `error`. It is `null` by default.\n\nWhen set to `error`, elements of the toggle will have their colors set to `colors.errorBackground` or `colors.error`.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} status=\"disabled\" />`}</CodeBlock>\n\n### `editable`\n\nThe `editable` prop determines whether the toggle is interactable. It is `true` by default. Setting the `status` prop to `disabled` also will set `editable` to `false`.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} editable={false} />`}</CodeBlock>\n\n### `value`\n\nThe `value` prop determines whether the toggle is on or off. It is `false` by default.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} value={true} />`}</CodeBlock>\n\n### `onValueChange`\n\nThe `onValueChange` prop is a callback that is called when the toggle is toggled. It is `undefined` by default. Since the toggle is controlled, you must set the `value` prop in this callback to update the toggle.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} onValueChange={setValue} />`}</CodeBlock>\n\n### `containerStyle`\n\nThe `containerStyle` prop is a style object that is applied to the container of the toggle.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} containerStyle={{ backgroundColor: \"#fff\" }} />`}</CodeBlock>\n\n### `inputOuterStyle`\n\nThe `inputOuterStyle` prop is a style object that is applied to the outer container of the toggle input. This gives the inputs their size, shape, \"off\" background-color, and outer border.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} inputOuterStyle={{ backgroundColor: \"#fff\" }} />`}</CodeBlock>\n\n### `inputInnerStyle`\n\nThe `inputInnerStyle` prop is a style object that is applied to the inner container of the toggle input. This gives the inputs their \"on\" background-color and inner border.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} inputInnerStyle={{ backgroundColor: \"#000\" }} />`}</CodeBlock>\n\n### `inputDetailStyle`\n\nThe `inputDetailStyle` prop is a style object that is applied to the detail container of the toggle input. For checkbox, this affects the Image component. For radio, this affects the dot View. For switch, this affects the knob View.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} inputDetailStyle={{ backgroundColor: \"#000\" }} />`}</CodeBlock>\n\n### `labelPosition`\n\nThe `labelPosition` prop determines the position of the label relative to the action component. It can be `left` or `right`. It is `right` by default.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} labelPosition=\"left\" />`}</CodeBlock>\n\n### `label`\n\nThe `label` prop is a string that is used as the label for the toggle.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} label=\"Remember Me\" />`}</CodeBlock>\n\n### `labelTx`\n\nThe `labelTx` prop is a key to a string in the `i18n` translation file. It is used as the label for the toggle.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} labelTx=\"login:rememberUsername\" />`}</CodeBlock>\n\n### `labelTxOptions`\n\nThe `labelTxOptions` prop is an object that is passed to the `i18n` translation function. It is used to pass in values to the translation string.\n\n<CodeBlock language=\"tsx\">\n  {`\\<${props.componentName}\n  value={value}\n  onValueChange={setValue}\n  labelTx=\"login:rememberUsername\"\n  labelTxOptions={{ username: \"john\" }}\n/>`}\n</CodeBlock>\n\n### `labelStyle`\n\nThe `labelStyle` prop is a `StyleProp<TextStyle>` object that is applied to the label.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} labelStyle={{ color: \"#000\" }} />`}</CodeBlock>\n\n### `LabelTextProps`\n\nThe `LabelTextProps` prop is a `TextProps` object (from the [`Text`](./Text.md)) component that is applied to the label.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} LabelTextProps={{ size: \"lg\" }} />`}</CodeBlock>\n\n### `helper`\n\nThe `helper` prop is a string that is used as the helper for the toggle.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} helper=\"Remember Me\" />`}</CodeBlock>\n\n### `helperTx`\n\nThe `helperTx` prop is a key to a string in the `i18n` translation file. It is used as the helper for the toggle.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} helperTx=\"login:rememberUsername\" />`}</CodeBlock>\n\n### `helperTxOptions`\n\nThe `helperTxOptions` prop is an object that is passed to the `i18n` translation function. It is used to pass in values to the translation string.\n\n<CodeBlock language=\"tsx\">\n  {`\\<${props.componentName}\n  value={value}\n  onValueChange={setValue}\n  helperTx=\"login:rememberUsername\"\n  helperTxOptions={{ username: \"john\" }}\n/>`}\n</CodeBlock>\n\n### `HelperTextProps`\n\nThe `HelperTextProps` prop is a `TextProps` object (from the [`Text`](./Text.md)) component that is applied to the helper.\n\n<CodeBlock language=\"tsx\">{`\\<${props.componentName} value={value} onValueChange={setValue} HelperTextProps={{ size: \"lg\" }} />`}</CodeBlock>\n"
  },
  {
    "path": "docs/boilerplate/app/config/Config.md",
    "content": "# Config folder\n\nThis file imports configuration objects from either the config.dev.js file or the config.prod.js file depending on whether we are in `__DEV__` mode or not.\n\nNote that we do not gitignore these files. Unlike on web servers, just because these are not checked into your repo doesn't mean that they are secure. In fact, you're shipping a JavaScript bundle with every config variable in plain text. Anyone who downloads your app can easily extract them.\n\nIf you doubt this, just bundle your app, and then go look at the bundle and search it for one of your config variable values. You'll find it there.\n\nRead more here: https://reactnative.dev/docs/security#storing-sensitive-info\n\n## config.base.js\n\nThis file contains configuration variables that are shared between development and production. For example, we set a `exitRoutes` setting to tell the app which routes should be considered \"exit routes\" (i.e. routes that the user can exit the app from).\n\n## config.dev.js\n\nThis file contains configuration variables that are specific to development. For example, you might want to use a different API URL for development than you do for production.\n\n## config.prod.js\n\nThis file contains configuration variables that are specific to production.\n"
  },
  {
    "path": "docs/boilerplate/app/config/_category_.json",
    "content": "{\n  \"label\": \"config\",\n  \"position\": 50,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Config\"\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/app/context/Context.md",
    "content": "# Context folder\n\nThe `context` folder is where you can put your [React context providers](https://react.dev/learn/passing-data-deeply-with-context) or any other state management solutions you choose to use in your Ignited app.\n\nIgnite used to include [mobx-state-tree](https://mobx-state-tree.js.org/) as the default state management solution, but over time our projects have had more and more diverse state management solutions. We still love MST, and it was our go-to solution for many years, but shipping it by default with Ignite makes less sense now that there are so many other great options out there. Since we haven't settled on a single state management solution, simple React contexts are the default in this boilerplate.\n\nCurrently this folder contains the following React contexts that are meant to be simple examples of how to use React context in your app:\n\n- `AuthContext`: This is a basic context that provides the demo app with a way to manage simple authentication state. We persist the data using MMKV hooks in this context provider. The app can consume the AuthContext wih the `useAuth` hook.\n- `EpisodeContext`: This context provides the demo app with a way to manage the list of episodes in the demo podcast screen. This context provides a `useEpisodes` hook that can be used to fetch the episodes and manage the state of the list.\n\n## State management solutions\n\nThere are many state management solutions available for React Native apps. React context comes built in and is a is a reasonable solution for many apps. But you may want to consider other solutions depending on your app's complexity and needs.\n\nHere are some popular state management solutions you might consider (not in any particular order):\n\n- Redux using [Redux Toolkit](https://github.com/reduxjs/redux-toolkit): A predictable state container for JavaScript apps. It is widely used and has a large ecosystem of libraries and tools.\n- [MobX](https://mobx.js.org/README.html): A simple, scalable state management solution that uses observable data structures.\n- [Mobx State Tree (MST)](https://mobx-state-tree.js.org/): If MobX is a state management \"engine\", then MobX-State-Tree is a luxury car. MST gives you the structure, tools, and other features to get you where you're going.\n- [Zustand](https://github.com/pmndrs/zustand): A small, fast and scalable bearbones state-management solution using simplified flux principles.\n- [Legend State](https://github.com/LegendApp/legend-state): A super fast all-in-one state and sync library.\n- [React Query](https://react-query.tanstack.com/): A powerful data-fetching library that can also manage local state.\n- [XState](https://xstate.js.org/): Uses event-driven programming, state machines, statecharts, and the actor model to handle complex logic in predictable, robust, and visual ways.\n"
  },
  {
    "path": "docs/boilerplate/app/context/_category_.json",
    "content": "{\n  \"label\": \"context\",\n  \"position\": 50,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Context\"\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/app/devtools/Devtools.md",
    "content": "# Devtools Folder\n\n## Reactotron\n\nIgnite comes with Reactotron support for debugging your app.\nBy default, Reactotron is configured to work with web and mobile apps and is configured with a few plugins and commands we think are useful.\n\n### ReactotronConfig.ts\n\nThere are a few custom commands included with this configuration. You can use `reactotron.onCustomCommand` to add your own own custom debugging tools to Reactotron. Here is an example:\n\n```typescript\nreactotron.onCustomCommand({\n  title: \"Reset Navigation State\",\n  description: \"Resets the navigation state\",\n  command: \"resetNavigation\",\n  handler: () => {\n    Reactotron.log(\"resetting navigation state\")\n    resetRoot({ index: 0, routes: [] })\n  },\n})\n```\n\nFor more info check out the [Reactotron Documentation](https://docs.infinite.red/reactotron/)\n"
  },
  {
    "path": "docs/boilerplate/app/devtools/_category_.json",
    "content": "{\n  \"label\": \"devtools\",\n  \"position\": 50,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Devtools\"\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/app/i18n/Internationalization.md",
    "content": "---\nsidebar_position: 160\n---\n\n# Internationalization in Ignite Apps\n\nIgnite is currently set up to support Internationalization in English, Arabic, Korean, French, Japanese and Hindi. This is detected on app load and will set your app to that language.\n\n## Right to Left languages (RTL)\n\nSince Ignite already comes with an RTL language, Arabic, adding any new ones would work by default.\n\n### Removing RTL Support\n\nTo remove RTL support, follow the following steps:\n\n1. In `/app/i18n/index.ts`\n\n- Remove your RTL language imports\n- Remove references to those language objects\n- Remove lines where we allow and force RTL on the native layer\n\n```ts\nI18nManager.allowRTL(isRTL)\nI18nManager.forceRTL(isRTL)\n```\n\n2. Remove all other associated logic that uses the exported `isRTL` variable\n3. Remove any `tx=\"some:i18n.key\"` from your components and use `text=\"Some Text\"` instead\n   (e.g. `<Text text=\"Some Text\" />`\n\n## Adding more languages\n\nSee the [i18next Documentation](https://www.i18next.com/how-to/add-or-load-translations) to add languages to `app/i18n/index.ts`.\n"
  },
  {
    "path": "docs/boilerplate/app/i18n/_category_.json",
    "content": "{\n  \"label\": \"i18n\",\n  \"position\": 50,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Internationalization\"\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/app/navigators/AppNavigator.tsx.md",
    "content": "# AppNavigator.tsx\n\nIf you open the file `app/navigators/AppNavigator.tsx` up, you'll find the AppNavigator and the AppStack.\n\nThe AppNavigator is the root navigator for your whole app. It will have the navigation container and wrap the AppStack.\n\nThe AppStack is a native stack navigator (via [React Navigation](https://reactnavigation.org/docs/hello-react-navigation#creating-a-native-stack-navigator)) and contains all the screens and subnavigators of your app.\n\nIn the case of Ignite's demo code, it is prepared with an example flow for an app requiring authentication. The screens included within the AppStack are dependent on value of `isAuthenticated` from the `useAuth()` hook. If in an unauthenticated state, the only screen to be shown will be the `LoginScreen`. Otherwise, that screen is left out of the navigator and the user is presented with the `WelcomeScreen` and screens that fall under the `DemoNavigator`\n"
  },
  {
    "path": "docs/boilerplate/app/navigators/Navigation.md",
    "content": "---\nsidebar_position: 90\n---\n\n# Navigation in Ignite\n\nWe use [React Navigation v7](https://reactnavigation.org/docs/getting-started/) in the current version of Ignite. You'll find any navigators in `./app/navigators`, with the `AppNavigator.tsx` being the primary one.\n\nThere's also a `navigationUtilities.tsx` file which provides some utility functions we find useful in building apps, such as `getActiveRouteName`, `useBackButtonHandler` and `useNavigationPersistence`.\n\nThere's a provided Ignite CLI generator for creating new navigators. Learn more in the [Generator docs](../../../concept/Generators.md#navigator-generator).\n\n## General Structure\n\n```tsx\n<AppNavigator initialState={initialNavigationState} onStateChange={onNavigationStateChange} />\n```\n\nSee the [AppNavigator.tsx](./AppNavigator.tsx.md) docs for more info on how the app navigator is set up.\n\n## Useful Patterns\n\nWe've found that there are some useful patterns for building navigators in React Native.\n\n### Authentication Flow\n\nWe recommend following the guidance of [React Navigation's Authentication Flows](https://reactnavigation.org/docs/auth-flow/) and Ignite comes bootstrapped with this pattern in its demo code.\n\n```tsx\nconst AppStack = () => {\n  const { isAuthenticated } = useAuth()\n\n  return (\n    <Stack.Navigator\n      screenOptions={{ headerShown: false }}\n      initialRouteName={isAuthenticated ? \"Welcome\" : \"Login\"}\n    >\n      {isAuthenticated ? (\n        <>\n          <Stack.Screen name=\"Welcome\" component={WelcomeScreen} />\n          <Stack.Screen name=\"Demo\" component={DemoNavigator} />\n        </>\n      ) : (\n        <>\n          <Stack.Screen name=\"Login\" component={LoginScreen} />\n        </>\n      )}\n    </Stack.Navigator>\n  )\n}\n```\n\nThe screens included within the AppStack are dependent on value of `isAuthenticated` from `authenticationStore`. If the user hasn't been authenticated yet, the only screen to be shown will be the `LoginScreen`.\n\nWhen authenticated, `LoginScreen` is left out of the navigator and the user is presented with the `WelcomeScreen` and screens that fall under the `DemoNavigator`\n\n### Tab Navigation\n\nWe recommend using the [React Navigation Tabs](https://reactnavigation.org/docs/tab-based-navigation/) for tabs.\n\nInside `./app/navigators/DemoNavigator.tsx` you'll see the definition of the bottom tab navigator. Here is where you can customize the style and behavior of the tab bar itself, as well as each individual tab.\n\nIn addition to the user pressing the tab buttons, you may also navigate from tab to tab programmatically via the same familiar navigation API:\n\n```tsx\n// currently on the DemoShowroom tab, navigate to the DemoDebug tab\nnavigation.navigate(\"DemoDebug\")\n```\n\nTabs can jump to a single screen (as seen in Ignite's demo code) or another stack navigator comprised of many screens the user could navigate through within the same tab. Let's use a message inbox as an example:\n\n```tsx\nconst InboxStack = createNativeStackNavigator()\n\nfunction InboxStackScreen() {\n  return (\n    <InboxStack.Navigator>\n      <InboxStack.Screen name=\"List\" component={ListScreen} />\n      <InboxStack.Screen name=\"MessageDetails\" component={MessageDetailsScreen} />\n    </InboxStack.Navigator>\n  )\n}\n```\n\nA tab could be added to the tab navigator as a child component.\n\n`<Tab.Screen name=\"Inbox\" component={InboxStackScreen} />`\n\nThis would initially display the `ListScreen` with all of the messages and if the user happened to press a list item, the app would then navigate to a `MessageDetailsScreen`.\n\n### Sidebar Navigation\n\nIgnite comes with an example sidebar navigation, aka \"drawer\" navigator. It is implemented using the `DrawerLayout` from [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/api/components/drawer-layout/), a cross-platform replacement for [React Native's DrawerLayoutAndroid](https://reactnative.dev/docs/drawerlayoutandroid.html).\n\nThe view passed via `renderNavigationView` prop is the content rendered to the side of the screen, which can be pulled in or opened by the toggle button press. Here is where you can render header information (perhaps a company logo or logged in user avatar along with labels), menu items, logout or settings functionality and more.\n\n`DrawerLayout` also allows you to customize the behavior (open/close speed, overlay position), style and even has events to track the progress and states of the drawer transitioning. See more info at the [documentation](https://docs.swmansion.com/react-native-gesture-handler/docs/api/components/drawer-layout/).\n\nIgnite's navigation setup also comes with some very useful [navigation utilities](./navigationUtilities.ts.md) to help you with common tasks such as getting the current route name, handling the back button, and persisting navigation state.\n\n## A note about Expo Router\n\nWe are currently evaluating [Expo Router](https://docs.expo.dev/router/introduction/) to power Ignite's navigation system. It's a very promising project (built on React Navigation), but our philosophy is that nothing makes it into Ignite unless we've proven it in a full project (or two...or three...) first. In the meantime we've added an experimental option to switch to Expo Router when igniting your project. We'll update this section when we have more information.\n"
  },
  {
    "path": "docs/boilerplate/app/navigators/_category_.json",
    "content": "{\n  \"label\": \"navigators\",\n  \"position\": 50,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Navigation\"\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/app/navigators/navigationUtilities.ts.md",
    "content": "## `navigationUtilities.ts`\n\n### `getActiveRouteName`\n\nThis helper allows you to fetch the active route name from your navigator. It will recursively dive into nested routers. It takes the current navigation state (via `navigation.getState()`) and returns a string.\n\nExample:\n\n```tsx\n// nested navigators, 2-deep\nconst NestedStack = () => {\n  return (\n    <Stack.Navigator>\n      <Stack.Screen name=\"myScreen\" component={MyScreen} />\n    </Stack.Navigator>\n  )\n}\n\nconst AppStack = () => {\n  return (\n    <Stack.Navigator>\n      <Stack.Screen name=\"nestedNav\" component={NestedStack} />\n    </Stack.Navigator>\n  )\n}\n\n// getActiveRouteName usage\nfunction MyScreen({ navigation }) {\n  const routeName = getActiveRouteName(navigation.getState())\n  // => \"myScreen\"\n}\n```\n\n### `useBackButtonHandler`\n\nThis helper custom hook allows you to easily specify what routes you want to exit the app from, when the \"back\" button is pressed on Android. It has no effect on iOS.\n\nWe recommend using this in your root AppNavigator.\n\nExample:\n\n```tsx\nexport const AppNavigator = (props) => {\n  // What route names do we allow the back button to exit the app from?\n  const exitRoutes = [\"welcome\"]\n  useBackButtonHandler((routeName) => exitRoutes.includes(routeName))\n\n  // ...\n}\n```\n\n### `useNavigationPersistence`\n\nThis helper custom hook persists app navigation state between app loads. This is only enabled in dev by default, but can be enabled in production as well by editing the hook in `navigationUtilities.tsx`.\n\n```tsx\nimport * as storage from \"./utils/storage\"\n\nfunction App(props: AppProps) {\n  const persistence = useNavigationPersistence(storage, \"my-persistence-key\")\n  const { initialNavigationState, onNavigationStateChange, isRestored } = persistence\n\n  // wait for the navigation state to restore\n  // `null` will show the background color\n  // can replace with <LoadingScreen /> or similar if you want\n  if (!isRestored) return null\n\n  return (\n    <AppNavigator\n      // initial navigation state is fetched from storage\n      initialState={initialNavigationState}\n      // persist changes to storage\n      onStateChange={onNavigationStateChange}\n    />\n  )\n}\n```\n"
  },
  {
    "path": "docs/boilerplate/app/screens/Screens.md",
    "content": "# Screens folder\n\nThe `screens` folder contains the main screens of your app. Each screen is a file ending in `Screen.tsx`, such as `LoginScreen.tsx`. They are optionally contained in folders (but we recommend keeping it fairly flat).\n\nExplore the included screens (if you left the demo code in place) to see how they work.\n\nScreens are the central point of interaction within your app. They are responsible for rendering the UI / state, styling, handling user input, and initiating navigation to other screens.\n\nWe also tend to co-locate specific components for screens within the same folder. For example, if a login screen has a \"LoginForm\" component that is only used by that screen, we might put it in `app/screens/login/LoginForm.tsx` (alongside `LoginScreen.tsx`). If it's a component that is used by multiple screens, we'll put it in the `components` folder.\n"
  },
  {
    "path": "docs/boilerplate/app/screens/_category_.json",
    "content": "{\n  \"label\": \"screens\",\n  \"position\": 50,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Screens\"\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/app/services/Services.md",
    "content": "# Services folder\n\nThe `services` folder contains services, such as API clients.\n\n\"Services\" is a somewhat broad term, but we use it to refer to code that is responsible for a specific task, such as making API calls, interacting with the file system, or handling push notifications and so on.\n\nIgnite's boilerplate only comes with one service, the API client. However, you can add as many services as you like in this folder.\n\n## Backend API Integration\n\nMost apps need to communicate with a backend service of some sort. Some may have a REST API, some a GraphQL API, others might use Firebase/Firestore, Hasura, tRPC, Supabase, AWS/Amplify, or any number of different back end solutions.\n\nIgnite purposely does not make any major decisions about what backend system we expect you to use. As a consultancy, we've integrated apps with all kinds of back ends (ask us about the _Coldfusion_ backend we integrated with a few years ago!), and can't be locked into one solution.\n\nIgnite does come with a basic API setup which we'll describe here. Feel free to rip it out and use your own solution if this doesn't fit.\n\nWith that said, we've built large React Native apps using this pattern, and it works pretty well.\n\n## HTTP Client\n\nWhile React Native comes standard with a pretty good built-in `fetch` client library, it's not quite a smooth enough developer experience for us to recommend out of the box. So we include an HTTP client library called `apisauce`.\n\n### apisauce\n\nIgnite comes with [apisauce](https://github.com/infinitered/apisauce), which is a lightweight wrapper around the popular [Axios](https://axios-http.com/docs/intro) HTTP client library. We maintain this library at Infinite Red and it's a pretty battle-tested, solid HTTP library.\n\n### The Api class\n\nIn `./app/services/api`, you'll find the [Api class](./api.ts.md). This class is the place to add methods to call when you want to fetch data from your backend. Check out the file for examples of fetching data.\n\n### A note about React Query (aka TanStack Query)\n\nNote that we are currently exploring [TanStack Query](https://tanstack.com/query/) for use in Ignite. We need a few more projects under our belt before we can comfortably include it with Ignite (if we do at all). However, it's a popular solution, so it's worth mentioning here.\n"
  },
  {
    "path": "docs/boilerplate/app/services/_category_.json",
    "content": "{\n  \"label\": \"services\",\n  \"position\": 50,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Services\"\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/app/services/api.ts.md",
    "content": "# api.ts\n\nThis is the API service. It is a singleton class and contains the code for making API calls to your backend. You can use it like this:\n\n```typescript\nimport { api } from \"@/services/api\"\n\n// ...\nconst response = await api.getEpisodes()\n\nif (response.kind === \"ok\") {\n  // do something with response.episodes\n} else {\n  console.error(`Error fetching episodes: ${JSON.stringify(response)}`)\n}\n```\n\nYou can add more methods to this class to call other endpoints.\n\nThere are lots of other ways to handle API calls, such as using [React Query](https://tanstack.com/query/latest/), [SWR](https://swr.vercel.app/), or [Apollo Client](https://www.apollographql.com/docs/react/) and others. We've used all of these in production apps and they're all really good in different ways. We've chosen to use a simple, custom API client in this boilerplate to keep things flexible.\n"
  },
  {
    "path": "docs/boilerplate/app/theme/Theming.md",
    "content": "---\nsidebar_position: 70\n---\n\n# Theming Ignite Apps\n\nTheming involves creating a consistent look & feel across your application. It's a collection of style attributes and building blocks that are used everywhere.\n\nIf you're looking for customizing the look of an individual component, look at the [Styling](../../../concept/Styling.md) documentation.\n\nTheming involves a few different things: palettes, colors, animation timings, fonts, typography, and spacing. You can find everything that we use for theming in the `app/theme` folder. When we at Infinite Red receive a custom design, one of the first places we start is in this directory matching the values to the design. It's a great idea to match the design language used by the designers with the semantic names you will be providing in these files.\n\n## Colors & Palettes\n\nColors are defined in `app/theme/colors.ts` (and `colorsDark.ts` for the dark theme). We use a palette-based approach to colors, which means that we define the set of colors used in the app. We then use these colors to define semantic color names to be used throughout the app. This allows us to have a consistent color palette across the app, and also allows us to change the palette easily.\n\n[Colors & Palettes](./colors.ts.md)\n\n## Fonts & Typography\n\nFonts are defined in `app/theme/fonts.ts`. We use a similar approach to colors, defining a set of fonts and then using those fonts to define semantic font names to be used throughout the app. This allows us to have a consistent font usage across the app, and also allows us to change the fonts easily.\n\n[Fonts & Typography](./typography.ts.md)\n\n## Timings\n\nTimings are defined in `app/theme/timing.ts`. They can be used for consistent animation timings throughout the app.\n\n## Spacing\n\nSpacing is a first class citizen in Ignite. We use a spacing scale to define the spacing between elements in the app. This allows us to have a consistent spacing scale across the app, and also allows us to change the spacing easily. It is recommended to use the spacing scale for all spacing in the app if possible.\n\n[Spacing](./spacing.ts.md)\n\n## Multiple Themes (a.k.a \"Dark Mode\")\n\nThe Ignite boilerplate ships with color palette definitions and support for multiple themes! By default we define two themes, but you can easily add more using our generic theming system.\n\n:::tip\n\nHead on over to the [Ignite Cookbook](https://ignitecookbook.com/) to find recipes for how to integrate Ignite's theming system with other popular styling and component libraries!\n\n:::\n\nThe `useAppTheme` hook allows you to create dynamically-themed styles, right out of the box. Here's an example:\n\n```tsx\nimport { type ViewStyle, View } from 'react-native'\nimport { type ThemedStyle } from '@/theme/types'\nimport { ThemeProvider, useAppTheme } from '@/theme/context'\n\nconst $container: ThemedStyle<ViewStyle> = (theme) => ({\n  flex: 1,\n  backgroundColor: theme.colors.background,\n  justifyContent: \"center\",\n  alignItems: \"center\",\n})\nconst $normalStyle: ViewStyle = {\n  width: 100,\n  height: 100,\n}\n\n// Then use in a component like so:\nconst Component = () => {\n  const { themed } = useAppTheme()\n  return (\n    <View style={themed($container)}>\n      <View style={$normalStyle}>\n    </View>\n  )\n}\n\nconst App = () => {\n  return (\n    <ThemeProvider>\n      <Component />\n    </ThemeProvider>\n  )\n}\n```\n\nFor more information on the `useAppTheme()` hook, [check out the theme context documentation](./context.ts.md).\n\n## Switching Between Themes\n\nTheme support would be useless if there wasn't a built-in way to switch the theme at will. Ignite's theming system will automatically pick the theme that matches the user's system configuration (light mode or dark mode) but you can override this using the `\n\n```tsx\n// In your component:\nconst {\n  setThemeContextOverride, // Function to set the theme\n  themeContext, // The current theme context (\"light\" | \"darK\")\n} = useAppTheme()\n\n// Then hook it up to a user interaction:\nconst onThemeButtonPress = () => {\n  // This will toggle between light and dark mode.\n  setThemeContextOverride(themeContext === \"dark\" ? \"light\" : \"dark\")\n}\n\n// Or you can let them use their deice's system setting: light/dark\nconst resetThemeContextOverride = () => {\n  setThemeContextOverride(undefined)\n}\n```\n\nYou could also hook it up to a switch if that's more your style:\n\n```tsx\n// In your component:\nconst {\n  setThemeContextOverride, // Function to set the theme\n  themeContext, // The current theme context (\"light\" | \"darK\")\n} = useAppTheme()\n\n// Then implement the switching button:\n<Toggle\n  label=\"Dark Mode\"\n  variant=\"switch\"\n  value={themeContext === \"dark\"}\n  onValueChange={(value: boolean) =>\n    setThemeContextOverride(value ? \"dark\" : \"light\")}\n/>\n```\n\nOnce you have set an explicit theme override, the app will not respect the user's system setting, allowing you to lock the app to dark mode or light mode even when the user's system setting is different.\n\nTo have your app go back to respecting the user's device system setting, you can call `setThemeContextOverride(undefined)`.\n\n## Hooking up the navigation theme\n\nIgnite uses `react-navigation` so it's already hooked up for use with your `NavigationController`! The `navigationTheme` variable returned from `useAppTheme()` is a `react-navigation` theme object you can pass to the root `NavigationController`.\n\n```tsx\nconst { navigationTheme } = useAppTheme()\nreturn <NavigationContainer theme={navigationTheme} {...props} />\n```\n\n## Integrating other styling and component libraries\n\nThere are many component libraries that offer light/dark modes to their components. Here's an example of how to use `react-native-elements` with Ignite's theming system by extending their own `ThemeProvider`:\n\n```tsx\nimport { colorsDark, colorsLight } from \"@/theme/colors\"\nimport { customFontsToLoad } from \"@/theme/typography\"\nimport { createTheme as createRNEUITheme, ThemeProvider as RNEUIThemeProvider } from \"@rneui/themed\"\n\nexport const ThemedRNEUIProvider = ({ children }) => {\n  const { themeContext } = useAppTheme()\n  const themeColors = themeContext === \"light\" ? colorsLight : colorsDark\n  const RNEUITheme = createRNEUITheme({\n    mode: themeContext,\n    lightColors: {\n      primary: colorsLight.palette.secondary500,\n    },\n    darkColors: {\n      primary: colorsDark.palette.secondary500,\n    },\n    components: {\n      Text: {\n        style: {\n          color: themeColors.text,\n        },\n      },\n      // ...etc\n    },\n  })\n\n  return <RNEUIThemeProvider theme={RNEUITheme}>{children}</RNEUIThemeProvider>\n}\n```\n"
  },
  {
    "path": "docs/boilerplate/app/theme/_category_.json",
    "content": "{\n  \"label\": \"theme\",\n  \"position\": 50,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Theming\"\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/app/theme/colors.ts.md",
    "content": "---\ntitle: colors.ts\n---\n\n# Colors & Palettes\n\nIn `app/theme/colors.ts`, we define a palette of colors and the semantic names to be used in the app. The palette is meant to be a simple list of colors, and the semantic names are meant to be used throughout the app. This matches how designers often think of colors & palettes, and lets us match designs which define these easily.\n\nThe palette color names are meant to be semantically neutral names matching the color. For example, `neutral100` defines a neutral color, but has no specific semantic meaning for its use. If you find yourself using a color in multiple places for the same purpose (e.g. background, border, text), define a semantic color and replace the palette color usage with the semantic one. For example, if you are styling all your text field components with a border of `colors.accent100`, define a semantic color such as `textFieldBorder` that is set to `accent100`. You would then use `colors.textFieldBorder` in your components in place of the `colors.accent100` color.\n\n```tsx\n<TextField style={{ borderColor: colors.border }} />\n```\n"
  },
  {
    "path": "docs/boilerplate/app/theme/context.ts.md",
    "content": "---\nsidebar_position: 10\ntitle: context.ts\n---\n\n# theme/context.ts\n\n## `<ThemeProvider>`\n\nThe `<ThemeProvider>` component is a context provider that wraps your app and provides access to the theme and theming tools. It should be used at the root of your application, typically in `App.tsx`.\n\n```tsx\nimport { ThemeProvider } from \"@/theme/context\"\n\nconst App = () => {\n  return <ThemeProvider>{/* Your app components go here */}</ThemeProvider>\n}\n```\n\nAnything that needs access to the `useAppTheme()` hook should be a child of this provider.\n\n## useAppTheme\n\nThe `useAppTheme()` hook returns various properties and tools relating to theming your app. Generally, you'll only need a few properties from this hook, with the most important being `theme` and `themed`.\n\nExample usage:\n\n```tsx\nimport { View, type ViewStyle } from \"react-native\"\nimport { useAppTheme } from \"@/theme/context\"\n\nconst MyComponent = () => {\n  const {\n    // Any styles you create with the type ThemedStyle<T>\n    // must be wrapped with themed($styleName) before passing\n    // it along to the component's style property.\n    themed,\n    // This is the current theme object.\n    theme,\n    // themeContext is what theme you are actually using:\n    // \"light\" | \"dark\"\n    themeContext,\n  } = useAppTheme()\n\n  return (\n    <View style={themed($container)}>\n      <View style={$plainObjectStyle}>\n        {/* An Example of direct theme color usage in a component: */}\n        <View\n          style={{\n            backgroundColor: theme.colors.error,\n          }}\n        >\n          {/* This will output \"light\" or \"dark\" */}\n          <Text>{themeContext}</Text>\n        </View>\n      </View>\n    </View>\n  )\n}\n\n// This is an ignite ThemedStyle. It's just like a ViewStyle but\n// is wrapped with a function that will be called with a theme parameter.\nconst $container: ThemedStyle<ViewStyle> = (theme) => ({\n  // You can access theme named colors:\n  backgroundColor: theme.colors.background,\n  // Accessing the color palette is not generally recommended.\n  color: theme.colors.palette.angry500,\n  // Spacing can be changed on a per-theme basis as well.\n  paddingHorizontal: theme.spacing.small,\n})\n\n// We haven't abandoned plain style objects as the preferred way\n// to style your components, but it can't use dynamic themes.\nconst $plainObjectStyle: ViewStyle = {\n  marginBottom: 20,\n}\n```\n\n## Properties\n\n### `navigationTheme`\n\nA `react-navigation` [theme object](https://reactnavigation.org/docs/themes#built-in-themes). This is the same object you would pass to a `NavigationContainer` component.\n\n### `setThemeContextOverride`\n\nA function that allows you to override the theme context. This is useful for allowing users to switch between light and dark mode.\n\nThe default behavior is to use the system theme, but you can override this by calling `setThemeContextOverride(\"light\" | \"dark\")`.\n\nCalling `setThemeContextOverride(undefined)` will reset the theme to the user's system preference.\n\n### `theme`\n\nA `Theme` object that contains all the colors, spacing, and other theme-related properties of the current theme context. You can edit these values in the `app/theme` folder.\n\n### `themeContext`\n\nA string that represents the current theme context. This will almost always be either \"light\" or \"dark\".\n\n:::info\n\nWhen resetting the theme context to the system preference with `setThemeContextOverride(undefined)`, the `themeContext` will not be undefined. It will be the return value of `react-native`'s `useColorScheme()` hook.\n\n:::\n\n### `themed`\n\n`themed()` is a simple function with complex types. You can pass a plain style object to it, a `ThemedStyle` function, or an array of either.\n\n:::note\n\nWhen passing an array of styles or `ThemedStyle` functions to `themed()`, it will return a single style object with last properties overriding any properties previously set.\n\nIn the example below, the last `<View>` would be yellow even though `$themedStyle` specifies red because the background color property was modified by a later style in the array.\n\n:::\n\n```tsx\nconst $plainStyle: ViewStyle = {\n  padding: 10,\n  backgroundColor: \"black\",\n  width: 25,\n  height: 25,\n}\n\nconst $themedStyle: ThemedStyle<ViewStyle> = (theme) => ({\n  backgroundColor: theme.colors.errorBackground,\n  height: theme.spacing.xl,\n  width: theme.spacing.xl\n})\n\n<View style={$plainStyle} />\n// <View style={$themedStyle} /> // This won't work\n<View style={themed($themedStyle)} />\n// <View style={themed($plainStyle)} /> // You can, but why would you?\n<View style={themed([$themedStyle, $plainStyle])} />\n<View style={themed([\n  $themedStyle,\n  $plainStyle,\n  // A plain style object\n  { width: 7, backgroundColor: 'yellow'}\n])} />\n```\n\n:::warn\n\nMake sure you don't pass any `Animated` styles to `themed()`. It will not work as expected! Keep them in separate style array objects: `<Animated.View style={[$animatedStyle, themed($myThemedStyle)]}>`.\n\n:::\n"
  },
  {
    "path": "docs/boilerplate/app/theme/spacing.ts.md",
    "content": "---\ntitle: spacing.ts\n---\n\n# Spacing\n\nSpacing refers to the whitespace in between the elements in your app.\n\nSpacing should be consistent and thought of as a first class technique right alongside [colors](./colors.ts.md) and [typography](./typography.ts.md).\n\nAnytime you add margins, or padding, they should come from this spacing scale, with relatively few exceptions.\n\nSpacings are defined in `app/theme/spacing.ts`. The scale we use in Ignite is:\n\n```ts\nexport const spacing = {\n  micro: 2,\n  tiny: 4,\n  extraSmall: 8,\n  small: 12,\n  medium: 16,\n  large: 24,\n  extraLarge: 32,\n  huge: 48,\n  massive: 64,\n}\n```\n\nExample:\n\n```ts\nimport { spacing } from \"@/theme/spacing\"\n\n$containerStyle = {\n  margin: spacing.small,\n}\n```\n\nWhich type of scale you use is based on the design.\n\nIf you've got simpler app, you may only need 6 items. Or maybe you need lots of items.\n\nWhatever you choose, try to stick with your scale and not use custom values if possible, as consistent spacing will give your app a very polished look and feel.\n"
  },
  {
    "path": "docs/boilerplate/app/theme/typography.ts.md",
    "content": "---\ntitle: typography.ts\n---\n\n# Fonts & Typography\n\nFonts are defined in `app/theme/typography.ts`. We use a similar approach to [colors](./colors.ts.md), defining a set of fonts and then using those fonts to define semantic font names to be used throughout the app. This allows us to have a consistent font usage across the app, and also allows us to change the fonts easily.\n\n## Fonts\n\nWe define the fonts used in `app/theme/typography.ts`. The custom fonts are loaded using the `useFonts` hook from [`expo-fonts`](https://docs.expo.dev/guides/using-custom-fonts/) to load the fonts.\n\nTo add additional custom fonts to your project, obtain the proper OTF/TTF file(s) or install the desired Google Font package. Make the necessary additions to the `customFontsToLoad` object in `app/theme/typography.ts` and `fonts` object to reference the font family in the typography theming object.\n\n```tsx\nexport const customFontsToLoad = {\n  spaceGroteskLight,\n  spaceGroteskRegular,\n  spaceGroteskMedium,\n  spaceGroteskSemiBold,\n  spaceGroteskBold,\n  myCustomFont: require(\"@assets/fonts/custom-font.otf\"),\n}\n\nconst fonts = {\n  // ...\n  myCustomFont: {\n    normal: \"myCustomFont\",\n  },\n}\n```\n\nKeep in mind that when utilizing custom fonts, it is a better user experience to wait on rendering anything within the app until the fonts are loaded (this will prevent any text from changing in front of the user's eyes). This functionality is baked into Ignite for you! Check out `app/app.tsx` to see it in action.\n\n## Typography\n\nSince we use the [`Text`](../components/Text.md) component to encapsulate almost all text within an ignite app, the semantic names are essentially presets. As with all presets, they should only be created where there's a consistent pattern of usage across the app. To do this you'd add a new preset to the `Text` component with the associated styles. For one-off cases, it's recommended to use the [`size`](../components/Text.md#size) and [`weight`](../components/Text.md#weight) props on the `Text` component.\n"
  },
  {
    "path": "docs/boilerplate/app/utils/Utils.md",
    "content": "---\ntitle: utils\nsidebar_position: 60\n---\n\n# `utils` folder\n\nEvery app needs a junk drawer. Here you can find a library of utilities that are used often within your application. This could includes hooks, helper functions, and various tools.\n\n## Hooks\n\n:::tip\nWe sometimes create a separate `app/hooks` folder just for hooks. This is a matter of preference.\n:::\n\n### useSafeAreaInsetsStyle\n\nA hook can be used to create a safe-area-aware style object that can be passed directly to a View.\n\n```tsx\nconst $insetStyle = useSafeAreaInsetsStyle([\"top\"], \"padding\")\n\n<View style={$insetStyle} />\n```\n\n[Full `useSafeAreaInsetsStyle`](./useSafeAreaInsetsStyle.ts.md)\n\n### useHeader\n\nA hook that can be used to easily set the Header of a react-navigation screen from within the screen's component.\n\n```tsx\nfunction AccountScreen() {\n  useHeader({\n    rightTx: \"common.logOut\",\n    onRightPress: logout,\n  })\n\n  return <Screen />\n}\n```\n\n[Full `useHeader`](./useHeader.tsx.md)\n"
  },
  {
    "path": "docs/boilerplate/app/utils/_category_.json",
    "content": "{\n  \"label\": \"utils\",\n  \"position\": 50,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Utils\"\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/app/utils/useHeader.tsx.md",
    "content": "---\nsidebar_position: 30\n---\n\n# useHeader\n\nThe `useHeader()` hook that can be used to easily set the Header of a react-navigation screen from within the screen's component.\n\n```tsx\nfunction AccountScreen() {\n  useHeader({\n    rightTx: \"common.logOut\",\n    onRightPress: logout,\n  })\n\n  return <Screen />\n}\n```\n\n## Parameters\n\n### `headerProps: HeaderProps`\n\nThe first parameter is an object representing the props that will be passed directly to the [Header](../components/Header.md) component.\n\n```tsx\nuseHeader({\n  rightTx: \"common.logOut\",\n  leftTx: \"common.back\",\n  onRightPress: logout,\n  onLeftPress: goBack,\n})\n```\n\n### `deps: any[]`\n\nThe second parameter is a list of dependencies passed to the `useLayoutEffect` that will cause the Header to be updated. Use this to control a dynamic header.\n\n```tsx\nconst [count, setCount] = useState(1)\n\nuseEffect(() => {\n  setTimeout(() => setCount(count + 1), 1000)\n}, [count])\n\nuseHeader(\n  {\n    title: `Count: ${count}`,\n  },\n  [count],\n)\n```\n"
  },
  {
    "path": "docs/boilerplate/app/utils/useSafeAreaInsetsStyle.ts.md",
    "content": "---\nsidebar_position: 20\n---\n\n# useSafeAreaInsetsStyle\n\nThe `useSafeAreaInsetsStyle()` hook can be used to create a safe-area-aware style object that can be passed directly to a View.\n\n```tsx\n<View style={useSafeAreaInsetsStyle([\"top\"], \"padding\")} />\n```\n\n## Parameters\n\n### `safeAreaEdges: ExtendedEdge[]`\n\nThe first parameter is a list of edges that need to be safe-area-aware. In order for the hook to return an object with values, at least one edge needs to be provided. Default is `[]`.\n\n```tsx\nconst $insetsStyle = useSafeAreaInsetsStyle([\"top\", \"left\"])\n\nconsole.log($insetsStyle) // { paddingTop: 47, paddingStart: 0 }\n```\n\n### `property: \"padding\" | \"margin\"`\n\nThe second parameter specifies the property prefix that will be used to compose the style object. Default is `padding`.\n\n```tsx\nconst $insetsPaddingStyle = useSafeAreaInsetsStyle([\"bottom\"], \"padding\")\nconst $insetsMarginStyle = useSafeAreaInsetsStyle([\"bottom\"], \"margin\")\n\nconsole.log($insetsPaddingStyle) // { paddingBottom: 28 }\nconsole.log($insetsMarginStyle) // { marginBottom: 28 }\n```\n\n## Types\n\n### `ExtendedEdge`\n\nA safe-area edge:\n\n- top\n- bottom\n- left\n- right\n- start\n- end\n\nNote: \"start\" maps to the \"left\" value. \"end\" maps to \"right.\n"
  },
  {
    "path": "docs/boilerplate/app.json.md",
    "content": "---\ntitle: app.json / app.config.js\nsidebar_position: 60\n---\n\n# app.json / app.config.js\n\nThe app.json & app.config.js files are used to configure your React Native / Expo project.\n\nIgnite has already configured several things for us:\n\n- App Icons - configured for iOS, Android, and Web. Check out [App Icon Generators](../../concept/Generators#app-icon-generator) to update your App Icon.\n- Splash Screen colors and images - configured for iOS, Android, and Web. Check out [Splash Screen Generators](../../concept/Generators#splash-screen-generator) to update your Splash Screen.\n- Expo plugins for things like localization and splash screens\n\nSee [Expo's Documentation on App.json Configuration](https://docs.expo.dev/workflow/configuration/) for more details.\n"
  },
  {
    "path": "docs/boilerplate/assets.md",
    "content": "---\ntitle: assets\nsidebar_position: 20\n---\n\n# Assets Folder\n\nThe `assets` folder is for icons, images, fonts, and other static assets used in your app.\n\n### App Icons\n\nFor your App Icon, use [Ignite's App Icon Generator](../../concept/Generators/#app-icon-generator) to automatically generate image assets, which get put in `assets/images`.\n\n### Splash Screen\n\nTo update your Splash Screen, use [Ignite's Splash Screen Generator](../../concept/Generators/#splash-screen-generator) to generate images and update `assets/images`.\n"
  },
  {
    "path": "docs/boilerplate/eas.json.md",
    "content": "---\ntitle: eas.json\nsidebar_position: 70\n---\n\n# eas.json\n\n`eas.json` is the configuration file for [Expo Application Service (EAS)](https://docs.expo.dev/eas/). It allows you to create profiles for building and distributing your app.\n\nIgnite includes a few default build profiles for common scenarios, but you can customize or add your own profiles too!\n\n- `development` - internal debug build for testing on simulators\n- `development:device` - internal debug build for testing on physical devices\n- `preview` - internal production build for testing on simulators\n- `preview:device` - internal production build for testing on physical devices\n- `production` - default production profile intended for external distribution\n\nNote how profiles can share settings via `extends`:\n\n```json\n\"development\": {\n    \"extends\": \"production\",\n    \"distribution\": \"internal\",\n    \"android\": {\n        \"gradleCommand\": \":app:assembleDebug\"\n    },\n    \"ios\": {\n        \"buildConfiguration\": \"Debug\",\n        \"simulator\": true\n    }\n},\n\"development:device\": {\n    \"extends\": \"development\",\n    \"distribution\": \"internal\",\n    \"ios\": {\n        \"buildConfiguration\": \"Debug\",\n        \"simulator\": false\n    }\n},\n\"production\": {}\n```\n\nIn this example, `development:device` inherits the settings from `development`, but changes the `ios` setting to `simulator: false`. You can use `extends` to create a set of profiles to fit your needs without duplicating configuration.\n\nView [Expo's eas.json Documentation](https://docs.expo.dev/build/eas-json/) for more info.\n"
  },
  {
    "path": "docs/boilerplate/ignite.md",
    "content": "---\ntitle: ignite\nsidebar_position: 25\n---\n\n# Ignite Folder\n\nThe `ignite` directory contains an initial set of generator templates to help scaffold new screens, components, context, app icons, and more.\n\nGenerators are the true gem of Ignite! They can save you countless hours as you build your app - we strongly recommend you give it a try!\nLearn more about [Ignite Generators](../concept/Generators.md) and how to create your own [Ignite Generator Templates](../concept/Generator-Templates.md)\n"
  },
  {
    "path": "docs/boilerplate/index.tsx.md",
    "content": "---\ntitle: index.tsx\nsidebar_position: 65\n---\n\n# index.tsx\n\n`index.tsx` is the entry point for Expo / React Native itself. It is minimal on purpose - its only responsibility is to:\n\n- Register the root component with the `AppRegistry`\n- Sets up the environment properly for the native build\n"
  },
  {
    "path": "docs/boilerplate/ios.md",
    "content": "---\ntitle: ios\nsidebar_position: 30\n---\n\n# `ios` folder\n\nIf you choose the `manual` workflow option when spinning up a new app (or you run `pnpm run prebuild:clean`) you'll get an `ios` (and probably [`android`](./android.md)) folder in your project root. This folder contains your native iOS / Xcode project, which has been pre-configured to work with React Native.\n\nWe generally recommend using the [Expo CNG (continuous native generation)](../expo/CNG.md) workflow, but if you need to customize your native code manually, you can do so here.\n\nJust like any React Native project, you can open this folder in Xcode and run your app on a simulator or device. Learn more here: [https://reactnative.dev/docs/native-debugging#debugging-native-code](https://reactnative.dev/docs/native-debugging#debugging-native-code)\n"
  },
  {
    "path": "docs/boilerplate/maestro.md",
    "content": "---\ntitle: .maestro\nsidebar_position: 2\n---\n\n# Maestro\n\nIgnite's demo project includes a `.maestro` directory with a few example test flows. [Maestro](https://maestro.mobile.dev/) is Ignite's default end-to-end testing solution.\n\nIf you have Maestro setup, you can run your tests via\n\n```bash\nmaestro test .maestro/MyTestFile.yaml\n```\n\nThis command is also setup as a npm script in `package.json`, which you can customize to your liking:\n\n```bash\npnpm run test:maestro\n```\n\nYou can learn how to run and create Maestro tests by following the [Ignite Cookbook Recipe](https://ignitecookbook.com/docs/recipes/MaestroSetup) or by visiting [Maestro's Documentation](https://maestro.mobile.dev/)\n"
  },
  {
    "path": "docs/boilerplate/plugins/Plugins.md",
    "content": "## `plugins` Directory in Ignite Apps\n\nThe `plugins` directory is a dedicated space within the Ignite boilerplate for managing Expo Config Plugins. These plugins are used to customize the native configuration of your app without altering the native code directly.\n\n### Adding Custom Plugins\n\nTo add a custom plugin:\n\n1. **Create a Plugin**: In `plugins`, define your plugin in a TypeScript file, exporting a function that modifies the ExpoConfig.\n2. **Integrate the Plugin**: In `app.config.ts`, import your plugin and add it to the `plugins` array.\n\nExample:\n\n```typescript\n// In app.config.ts\nplugins: [...existingPlugins, require(\"./plugins/yourCustomPlugin\").yourCustomPlugin]\n```\n\n## Key Points\n\n- Config plugins extend app configuration, automating native module integration.\n- Create plugins in `plugins` and add them to `app.config.ts`.\n- For complex setups, refer to mods but use them with caution.\n\nFor detailed information on creating and using config plugins, refer to [Expo's Config Plugins documentation](https://docs.expo.dev/config-plugins/introduction/).\n"
  },
  {
    "path": "docs/boilerplate/plugins/_category_.json",
    "content": "{\n  \"label\": \"plugins\",\n  \"position\": 50,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Plugins\"\n  }\n}\n"
  },
  {
    "path": "docs/boilerplate/test/Test.md",
    "content": "# Test Folder\n\nIgnite includes support for writing [Jest](https://jestjs.io/) tests, which can be located anywhere in your codebase. But the initial Jest setup, mocking objects for testing, and any global scoped tests belong in the `test` directory.\n\n### i18n.test.ts\n\n`test/i18n.test.ts` is a handy test to check for any missing or mistyped translation keys in your app.\nIt searches the codebase for components using the `tx=\"\"` prop, or any `translate(\"\")` commands, and checks for a valid i18n key between the double quotes.\n\nThis approach isn't 100% perfect. If you are storing your key string in a variable because you are setting it conditionally, then it won't be picked up.\n"
  },
  {
    "path": "docs/boilerplate/test/_category_.json",
    "content": "{\n  \"label\": \"test\",\n  \"position\": 55,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Test\"\n  }\n}\n"
  },
  {
    "path": "docs/cli/Ignite-CLI.md",
    "content": "# Ignite CLI\n\nIgnite comes with a dynamic CLI that does more than just get you started with developing a new react-native mobile application! Check out the command list below for additional documentation on each.\n\n## Commands\n\n### Cache\n\n- `npx ignite-cli cache`\n- Alias: `npx ignite-cli c`\n\nThe [`new` command](#new) has a `useCache` flag that allows you to cache your dependencies to speed up future uses of `new`. By default, this flag is `false`. This command is provided to allow for interacting with the dependency cache file folder.\n\n#### Subcommands\n\n- `npx ignite-cli cache help` outputs help command to describe cache subcommands\n- `npx ignite-cli cache path` outputs the path of the cache folder on your system\n- `npx ignite-cli cache clear` deletes the cache folder on your system\n\n#### Options\n\n- `path` displays the path to the dependency cache\n- `clear` clears the dependency cache\n\n### Doctor\n\n- `npx ignite-cli doctor`\n\nChecks your development environment for dependencies and provides version information. This is especially helpful when reporting issues you're experiencing with Ignite. Below is a sample output:\n\n```\nSystem\n  platform           darwin\n  arch               arm64\n  cpu                10 cores     Apple M1 Pro\n  directory          ExpoPlist    /Users/irignite/code/ExpoPlist\n\nJavaScript (and globally-installed packages)\n  node                16.14.2      /Users/irignite/.nvm/versions/node/v16.14.2/bin/node\n  npm                 8.5.0        /Users/irignite/.nvm/versions/node/v16.14.2/bin/npm\n    corepack          0.10.0\n    eas-cli           2.5.1\n    expo-cli          6.0.6\n    gatsby-cli        4.21.0\n    ignite-cli        8.3.0\n    npm               8.5.0\n    vercel            28.4.12\n    yarn              1.22.19\n  yarn                1.22.19      /Users/irignite/.nvm/versions/node/v16.14.2/bin/yarn\n    create-expo-app   1.1.2\n  pnpm                -            not installed\n  expo                46.0.16      managed\n\nIgnite\n  ignite-cli         8.3.0        /Users/irignite/.nvm/versions/node/v16.14.2/bin/ignite\n  ignite src         src          /Users/irignite/code/ignite/src\n\nAndroid\n  java               11.0.14.1    /Users/irignite/.asdf/shims/java\n  android home       -            /Users/irignite/Library/Android/sdk\n\niOS\n  xcode              14.0.1\n  cocoapods          1.11.3       /opt/homebrew/bin/pod\n\nTools\n  git                git version 2.37.0 (Apple Git-136)   /usr/bin/git\n```\n\n### Generate\n\n- `npx ignite-cli generate`\n- Alias: `npx ignite-cli g`\n\nProvides generators to keep your code consistent while saving you time to scaffold new components and screens in an automated fashion.\n\nFor full documentation on this, head on over to the [Generators documentation](../concept/Generators.md).\n\n### Help\n\n- `npx ignite-cli help`\n- Alias: `npx ignite-cli h`\n\nProvides this list of commands and a small description of each command in your terminal.\n\n### New\n\n- `npx ignite-cli new PizzaApp`\n\nStarts the interactive prompt for generating a new Ignite project. Any options not provided at the time of command execution will be asked. You can accept all the defaults to the options passing in `--yes` and just get to coding!\n\n#### Options\n\n- `--bundle` string, provide a custom bundle identifier\n- `--debug` verbose logging throughout the project setup\n- `--git` create a new repository with an initial commit\n- `--installDeps` run the packager install script after project creation\n- `--overwrite` overwrite the target directory if it exists\n- `--targetPath` string, specify a target directory where the project should be created\n- `--removeDemo` will remove the boilerplate demo code after project creation\n- `--useCache` flag specifying to use dependency cache for quicker installs\n- `--no-timeout` flag to disable the timeout protection (useful for slow internet connections)\n- `--yes` accept all prompt defaults\n- `--workflow` string, one of `cng` or `manual` for project initialization\n- `--experimental` comma separated string, indicates experimental features (which may or may not be stable) to turn on during installation. **A CNG workflow is require for these flags** `--workflow=cng`\n  - `expo-router` converts the base project to use [Expo Router](https://docs.expo.dev/router/introduction/) from React Navigation (**note:** the demo application will be removed)\n  - `expo-canary` uses Expo's highly experimental canary release instead of the la test stable SDK\n  - `expo-beta` uses Expo's latest beta SDK available instead of the latest stable SDK\n  - Examples: `--experimental=expo-router` or `--experimental=expo-router,expo-beta`\n\n### Issue\n\n- `npx ignite-cli issue \"Mac M1 install trouble\"`\n- Alias: `npx ignite-cli i`\n\nFires up a [new issue for Ignite on GitHub](https://github.com/infinitered/ignite/issues/new/) prefilled with collected [doctor](#doctor) information. Simply describe your steps to help reproduce the issue (and provide any relevant code snippets or repository) and press submit!\n\n### Remove Demo Markup\n\n- `npx ignite-cli remove-demo-markup`\n- Alias: `npx ignite-cli rdm`\n\nRemoves all demo markup (comments only) from the generated boilerplate\n\n#### Options\n\n- `--dry-run` displays markup which would be removed without doing so\n\n### Remove Demo\n\n- `npx ignite-cli remove-demo`\n- Alias: `npx ignite-cli rd`, `npx ignite-cli remove-demos`\n\nRemoves all demo code (files, marked code blocks and lines) from the generated boilerplate\n\n#### Options\n\n- `--dry-run` displays files that would be modified without doing so\n\n### Rename\n\n- `npx ignite-cli rename`\n- Alias: `npx ignite-cli rn`\n\nRenames your current project to the desired new name. It'll also help switch the bundle identifier.\n\n### Update\n\n- `npx ignite-cli update`\n\nUpdates the generator templates that currently exist in the project. This can be used to grab the latest versions of the templates should the project have been ignited with a previous version.\n"
  },
  {
    "path": "docs/cli/Remove-Demo-Code.md",
    "content": "---\nsidebar_position: 170\n---\n\n# Remove Demo Code\n\nWhenever users use command line boilerplate tool like `ignite` or `create-react-app`, the first thing that many users do is delete all the demo code to start their project.\n\nInstead of going through the files yourself, or using find and replace, run `npx ignite-cli remove-demo`.\n\nAfter the command, the Ignite boilerplate will now have the smallest amount of demo code possible, while having all of Ignite's set up still at your fingertips.\n\n_Note: You can pass `--dry-run` to have the command tell you what it would remove/change._\n\n## How It Works\n\nWhen adding demo code to the boilerplate, use the following comments to have fine grained control over what gets stripped out. The goal is to be able to remove as much source code as possible while still having the generated Ignite app build correctly.\n\n### `// @demo remove-file`\n\nRemove the entire file.\n\n#### Example:\n\n```tsx\n// @demo remove-file\nexport * from \"./Text\"\nexport * from \"./Screen\"\n```\n\n### `// @demo remove-current-line`\n\nRemove the current line from the source code.\n\n#### Example:\n\n```tsx\nconst $style: ViewStyle = { padding: 10 } // @demo remove-current-line\n```\n\n### `// @demo remove-next-line`\n\nRemove the next line from the source code\n\n#### Example:\n\n```tsx\n// @demo remove-next-line\nimport { DemoScreen } from \"./demo/screen\"\n```\n\n### `// @demo remove-block-start && // @demo remove-block-end`\n\nRemove the entire block between these lines from the source code\n\n#### Example:\n\n```tsx\n// @demo remove-block-start\nexport function DemoDivider(props: DemoDividerProps) {\n  const { type = \"horizontal\", size = 10, style: $styleOverride } = props\n\n  return (\n    <View\n      style={[\n        $divider,\n        type === \"horizontal\" && { height: size },\n        type === \"vertical\" && { width: size },\n        $styleOverride,\n      ]}\n    />\n  )\n}\n// @demo remove-block-end\n```\n"
  },
  {
    "path": "docs/cli/Troubleshooting.md",
    "content": "---\nsidebar_position: 145\n---\n\n# Troubleshooting\n\nIf you run into problems, first search the issues in the [GitHub repository](https://github.com/infinitered/ignite/issues). If you don't find anything, you can come talk to our friendly and active developers in the Infinite Red Community Slack ([community.infinite.red](http://community.infinite.red)).\n\n## Troubleshooting setup\n\nWe recommend using `npx ignite-cli@latest [command]` to ensure you're using the latest & greatest Ignite.\n\n### Remove a previous global install\n\nYou might run into version conflicts or environment differences if you have Ignite installed globally.\nCheck if Ignite is installed globally on your machine with:\n\n```bash\nignite --info          # Should output cli information\nwhere ignite           # identify where it's installed\n```\n\nYou can uninstall previous versions of the cli with:\n\n```bash\nnpm uninstall --global ignite-cli\n# or\nyarn global remove ignite-cli\n#or\npnpm remove ignite-cli -g\n```\n"
  },
  {
    "path": "docs/cli/_category_.json",
    "content": "{\n  \"label\": \"CLI\",\n  \"position\": 9,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Ignite-CLI\"\n  }\n}\n"
  },
  {
    "path": "docs/concept/Concepts.md",
    "content": "---\nsidebar_position: 1\n---\n\n# Concepts &amp; Features\n\nIgnite has **very** strong opinions about what the structure, dependencies, and layout of a react-native app should be.\n\nFor anything that falls outside the scope of a default ignited app, we have \"cookbook\" recipes for modifying the app. Check our the Ignite Cookbook here: https://ignitecookbook.com/\n"
  },
  {
    "path": "docs/concept/Error-Boundary.md",
    "content": "# Error Boundary\n\nSometimes, things go wrong in an app. A request comes back with an unexpected status, users find ways to make invalid inputs, a whole host of other issues that we can't imagine when we are first writing code.\n\nIt is a good idea to have a fallback UI for critical screens or components when they unexpected throw an error.\n\nThat is why we provide an ErrorBoundary component in Ignite by default.\n\n## How to Handle Errors\n\n### Render Error UI\n\n```tsx\nconst ErrorMessage = () => <Text>Something went wrong!</Text>\n\nclass ErrorBoundary extends Component<Props, State> {\n  state = { error: null, errorInfo: null }\n\n  componentDidCatch(error: Error, errorInfo: ErrorInfo) {\n    this.setState({\n      error,\n      errorInfo,\n    })\n  }\n\n  render() {\n    return this.state.error ? <ErrorMessage /> : this.props.children\n  }\n}\n```\n\n`ErrorBoundary` components leverage the `componentDidCatch` method on Class Components to capture and allow us to store errors when children throw an exception.\n\nThis allows us to show alternative UI components when an error is thrown on child components.\n\n### Use Error Reporting Service\n\nInside `componentDidCatch` of an `ErrorBoundary` component is also a great place to report to monitoring services like BugSnag, Sentry, or Honeybadger, so you can be alerted when errors are happening for users.\n\nIgnite provides utilities in [`/app/utils/crashReporting.ts`](https://github.com/infinitered/ignite/blob/master/boilerplate/app/utils/crashReporting.ts) to integrate these services into your app.\n\n## Examples\n\n- See our [`ErrorBoundary` component](https://github.com/infinitered/ignite/blob/master/boilerplate/app/screens/ErrorScreen/ErrorBoundary.tsx) for error catching logic\n- See our [`ErrorScreen` component](https://github.com/infinitered/ignite/blob/master/boilerplate/app/screens/ErrorScreen/ErrorDetails.tsx) for error fallback UI\n- [reactjs.org docs on Error Boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)\n- [codepen example of Error Boundary usage](https://codepen.io/gaearon/pen/wqvxGa?editors=0010)\n"
  },
  {
    "path": "docs/concept/Generator-Templates.md",
    "content": "---\nsidebar_position: 111\n---\n\n# Generator Templates\n\nGenerator templates are written in [ejs](https://ejs.co/), which is a templating language using JavaScript.\n\nYou write the template however you want, and use `<%= foo %>` to run and output JavaScript.\n\nYou can also use control statements like \"if\" with `<% if (condition) { %>Stuff here<% } %>`.\n\n## Folder naming conventions\n\nTemplates are located in your app's `./ignite/templates` folder, and the name of the folder should match the name of the generator.\n\nFor example, if you want to run `npx ignite-cli generate header Pizza`, you'd put the header templates in the `./ignite/templates/header/` folder.\n\nAny files in that folder will be copied over & run through the generator with the `Pizza` name applied.\n\n## File naming conventions\n\nIf you use all upper-case `NAME` in your template filenames, that will be replaced by a pascal-case version of the name provided by the person running the generator.\n\nIt's best to just look at an example:\n\nLet's say you have a file called `NAMEScreen.ts`.\n\nIf they run `npx ignite-cli generate screen Pizza`, it'll name the file `PizzaScreen.ts`.\n\nIf you'd like to customize the filename you can provide a filename option in the front matter of the template like so:\n\n```\n---\nfilename: <%= props.camelCaseName %>.tsx\n---\n```\n\n## Props\n\nThere's a provided `props` object that contains the following properties:\n\n```\nprops.filename       // string, the name of the file being generated (e.g. \"UserModel.tsx\")\nprops.pascalCaseName // string, PascalCase version of the name that is passed in (e.g. \"UserModel\")\nprops.camelCaseName  // string, camelCase version of the name (e.g. \"userModel\")\nprops.kebabCaseName  // string, kebab-case version of the name (e.g. \"user-model\")\nprops.subdirectory   // string, the subdirectory path to the file being generated (e.g. \"my/sub/path/\")\n```\n\nExample of using these in a template:\n\n```ejs\ntype <%= props.pascalCaseName %>Props = { some: string }\nexport function <%= props.pascalCaseName %>(props: <%= props.pascalCaseName %>Props) {\n  return <Text>{props.some} in a <%= props.pascalCaseName %> component!</Text>\n}\n```\n\n## Front Matter\n\n\"Front matter\" is a way to specify meta-data about a template in the template itself. It's stripped out of the generated file. You delineate front matter by three dashes (`---`) above and below, and it has to be the very first thing in the template. The following front matter options are available:\n\n### destinationDir\n\nWe use this in Ignite to customize the destination of a given template. For example, in `./ignite/templates/navigator/*` we could have:\n\n```\n---\ndestinationDir: app/navigation\n---\nimport { StackNavigator } from \"react-navigation\"\n\n// ...\n```\n\nThis would copy files to `./app/navigation/*` instead of the default `./app/navigators/*`.\n\n### patch\n\nThis lets you patch another file, such as an index file. Example:\n\n```tsx\n---\npatch:\n  path: \"app/screens/index.ts\"\n  append: \"export * from \\\"./<%= props.kebabCaseName %>/<%= props.kebabCaseName %>-screen\\\"\\n\"\n---\n```\n\n## Notes\n\nFront matter is very powerful, but not necessarily super intuitive. If you have questions about it, ask in the [Ignite Slack community](https://community.infinite.red) or post a [Discussion](https://github.com/infinitered/ignite/discussions).\n"
  },
  {
    "path": "docs/concept/Generators.md",
    "content": "---\nsidebar_position: 110\n---\n\n# Generators\n\n_The true gem of Ignite._ When you spin up a new app with Ignite CLI, we copy in several generator templates into `./ignite/templates/*`. Generators help you scaffold your app very quickly, be it for a proof-of-concept, a demo, or a production app. Generators are there to save you time, keep your code consistent, and help you with the basic structure of your app.\n\n```\nnpx ignite-cli generate --list\n```\n\n...will give you information on what generators are present.\n\n## Built-in generators\n\n### Component generator\n\nThis is the generator you will be using most often.\n\n```\nnpx ignite-cli generate component MyAwesomeButton\n```\n\n- Creates the component/function\n\n### Screen generator\n\nGenerates a \"hooks enabled\" screen.\n\n```\nnpx ignite-cli generate screen Settings\n```\n\n### Navigator generator\n\nCreates a React Navigation navigator in the `app/navigators` folder.\n\n```\nnpx ignite-cli generate navigator OrderPizza\n```\n\nYou can learn more about navigators [in the Navigation docs](../boilerplate/app/navigators/Navigation.md).\n\n### App Icon generator\n\nApp icons are tricky - there are many different shapes and sizes, and many different configuration files and locations to update. So we include this generator to make it much easier on you!\n\nThis is a special kind of generator - \"special\" in that it modifies the native project folders with resized and transformed input image files found in the generator's template folder. Also, it only accepts predefined options for the second parameter: one of `ios`, `android`, `expo` or `all`.\n\nThe following files will be found in your templates folder (`ignite/templates/app-icon`) which can be customized:\n\n- `android-adaptive-background.png`:\n  - The generator will use this file to create all required adaptive launcher-icon background layers for Android 8.0 and above.\n  - Updates same directories as the legacy icon.\n\n- `android-adaptive-foreground.png`:\n  - The generator will use this file to create all required adaptive launcher-icon foreground layers for Android 8.0 and above.\n  - Updates same directories as the legacy icon.\n\n- `android-legacy.png`:\n  - The generator will use this file to create all required legacy launcher-icons for Android 7.1 and below.\n  - Automatically transforms the icon to add necessary padding and radius. Note, when creating your custom input file, do not include the padding or radius.\n  - (vanilla) Updates `./android/app/src/main/res/` including the `mipmap-anydpi-v26/ic_launcher.xml`.\n  - (expo) Updates `./assets/images/` including the root file `./app.json`.\n\n- `ios-universal.png`:\n  - The generator will use this file to create all required app-icons for iOS.\n  - (vanilla) Updates `./ios/**/Images.xcassets/AppIcon.appiconset/` including `Content.json`.\n  - (expo) Updates `./assets/images/` including the root file `./app.json`.\n\nWhen updating the template files, please note that names must stay the same as well as the size (1024x1024px). A Sketch template file can be [found here](https://github.com/infinitered/ignite/files/8576614/ignite-app-icon-template.zip) - just make your changes, hide the grids, then click File -> Export.\n\n```\nnpx ignite-cli generate app-icon ios\n```\n\nBy default, the generator will exit if the input-files in your templates folder match signatures with those of the default Ignite app-icons - this is done to encourage you to make actual changes to the icons before generating. However, if you want to override your application's app-icons with those of Ignite's, you can first reset your app-icon templates folder with `npx ignite-cli g app-icon --update` and then regenerate the app-icons with the `--skip-source-equality-validation` flag.\n\n### Splash Screen generator\n\nSimilar to app/launcher icons, the splash-screen is somewhat tricky to configure and manage due to platform (and OS version) differences. Therefore, splash-screens come preconfigured in the latest versions of Ignite boilerplate and a handy generator is provided to aid with customization.\n\nUnlike the app/launcher generator however, only a single input file is needed. This file, called `logo.png`, can be found and customized in the following templates folder: `ignite/templates/splash-screen`.\n\nThe generator requires a single parameter for the splash-screen's background color (in hex format).\n\n```\nnpx ignite-cli generate splash-screen FF0000\n// or\nnpx ignite-cli generate splash-screen \"#FF0000\"\n// or\nnpx ignite-cli generate splash-screen fff\n```\n\nThe generator will modify the `./assets/images/` and attempt to update `./app.json`. However, if your project is configured to use `app.config.js` or `app.config.ts`, the config changes will be output in the console for you to make them manually. You can read more about Expo's dynamic configuration [here](https://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs).\n\nLogo size transformations are predetermined based on platform. The defaults are meant to work in _most_ cases. However, you can adjust the logo transformation size according to your needs by using flags:\n\n```\nnpx ignite-cli generate splash-screen FF0000 --ios-size=150 --android-size=180\n```\n\nA few notes about sizes. iOS size has no upper limit, so be careful with the value. Android has an upper limit of `288` as defined in [Android docs](https://developer.android.com/guide/topics/ui/splash-screen#splash_screen_dimensions). For Expo (both Android and iOS), custom sizes will be observed; however, due to Expo's config requirements, the splash-screen assets are generated with padding and attempt to fill the screen.\n\nLastly, the splash-screen generator will exit if your input file has not been modified. The same source equality check, as the one on the app-icon generator, will encourage you to make customizations before using the generator (see the `--skip-source-equality-validation` section above).\n\n## CLI Options\n\n### `--case`\n\nThe default filename format is PascalCase (`--case auto` or `--case pascal`), based on the name you pass in to the generate command. For example:\n\n`npx ignite-cli@latest g screen Episodes` will generate `EpisodesScreen.tsx` in the case of the default generator template `NAMEScreen.tsx`.\n\nThis `--case` switch specifies the generated filenames (`NAME` in the filename of your template) will be how you pass it in. For example:\n\n`npx ignite-cli@latest g screen log-in` will generate the following outputs given their template name:\n\n| --case       | tpl filename       | generated filename |\n| ------------ | ------------------ | ------------------ |\n| auto, pascal | NAMEScreen.tsx.ejs | LogInScreen.tsx    |\n| camel        | NAMEScreen.tsx.ejs | logInScreen.tsx    |\n| snake        | NAMEScreen.tsx.ejs | log_in_screen.tsx  |\n| kebab        | NAMEScreen.tsx.ejs | log-in-screen.tsx  |\n| none         | NAMEScreen.tsx.ejs | log-in.tsx         |\n| auto, pascal | NAME.tsx.ejs       | LogIn.tsx          |\n| camel        | NAME.tsx.ejs       | logIn.tsx          |\n| snake        | NAME.tsx.ejs       | log_in.tsx         |\n| kebab        | NAME.tsx.ejs       | log-in.tsx         |\n| none         | NAME.tsx.ejs       | log-in.tsx         |\n\n### `--dir`\n\nSpecifies the output path for the generated files. This will override the default path of `app/` (Ignite's path where all app code lives at the time of this writing) and any `destinationDir:` front matter that exists. This is useful in the case of file-based routing navigation systems, such as [Expo Router](https://docs.expo.dev/router/introduction/).\n\n## Customizing generators\n\nYou should feel free to make the provided templates your own! Just update the files in the `./ignite/templates/*` folders, and any generated files will then use your updated files. Read more in the [Generator Templates](./Generator-Templates.md) documentation.\n\n## Making your own generators\n\nYour generators live in your app, in `./ignite/templates/*`. To make a new generator, go look at the ones that are there when you start your app. You'll see that they have `*.ejs` files (which get interpreted when you generate them).\n\nRead more about making your own generators in the [Generator Templates](./Generator-Templates.md) documentation.\n\n## Updating generators\n\nYou may want to update your generators to the latest version of Ignite.\n\nJust run `npx ignite-cli update <type>` or `npx ignite-cli update --all` from the root folder of your project to copy over the latest generators from Ignite to your project.\n\n⚠️ Note that this will remove any customizations you've made, so make sure to make a commit first so you can roll it back!\n\n## A Note About Windows\n\nIf you are noticing upon using the generator for a source file (such as a screen or model) that front matter is not removed from the newly created file, it could be that the End of Line Sequence is misconfigured. Ignite tries to take care of this on its own, but sometimes your machine will not have a proper CLI utility such as `unix2dos` installed (this usually comes with Git).\n\nIn this case, you can open VS Code (or another IDE) and convert the EOL characters for all `ejs` files in the `ignite/templates` directory. Then run the generator command again and it should create the new files properly.\n"
  },
  {
    "path": "docs/concept/Styling.md",
    "content": "---\nsidebar_position: 80\n---\n\n# Styling Ignite apps\n\nIgnite's approach to styling individual components is, like many other things in Ignite, straightforward and direct.\n\nIf you're looking to set app-wide styles such as fonts/typography or colors, check out the [Theming](../boilerplate/app/theme/Theming.md) documentation.\n\nWe don't use `StyleSheet.create()` as a general rule, as it doesn't provide any real benefits over bare objects and functions.\n\nWe instead use a strategy of bare JS objects and functions that take a theme parameter, colocated with our components (usually below the component in the file), prefixed with `$`, and typed with TypeScript:\n\n```tsx\nimport { View, type ViewStyle } from \"react-native\"\nimport { useAppTheme } from \"@/theme/context\"\n\nconst MyComponent = () => {\n  const { themed } = useAppTheme()\n  return (\n    <View style={themed($container)}>\n      <View style={$plainObjectStyle} />\n    </View>\n  )\n}\n\nconst $container: ThemedStyle<ViewStyle> = (theme) => ({\n  flex: 1,\n  backgroundColor: theme.colors.background,\n  paddingHorizontal: theme.spacing.small,\n})\n\nconst $plainObjectStyle: ViewStyle = {\n  marginBottom: 20,\n}\n```\n\nWe use [components with presets](../boilerplate/app/components/Components.md) to share styles across our whole app.\n\nWith this strategy, you can tell if a variable is a style when it has the `$` prefix. You can also spread in other styles to compose styles:\n\n```tsx\nconst $bold: TextStyle = {\n  fontWeight: \"bold\",\n}\nconst $larger: TextStyle = {\n  fontSize: 22,\n}\nconst $title: TextStyle = {\n  ...$bold,\n  ...$larger,\n}\n```\n\n## Sharing Styles via Presets\n\nMost of the [components](../boilerplate/app/components/Components.md) we include with Ignite include a `preset` property:\n\n```tsx\n<View preset=\"heading\" text=\"My Header\" />\n```\n\nPresets are defined in the component file itself, usually something like this:\n\n```tsx\ntype Presets = \"default\" | \"bold\" | \"heading\" | \"subheading\"\nconst $presets: Record<Presets, ThemedStyleArray<TextStyle>> = {\n  default: [$baseStyle],\n  bold: [$baseStyle, $fontWeightStyles.bold],\n  heading: [$baseStyle, $sizeStyles.xxl, $fontWeightStyles.bold],\n  subheading: [$baseStyle, $sizeStyles.lg, $fontWeightStyles.medium],\n}\n```\n\nThese presets are usually composed of other styles, using arrays (which React Native will properly merge).\n\nSo, let's say we want a button that is a destructive action. We might add a \"destructive\" preset to the Button component.\n\nThe preset might look like this:\n\n```tsx\nconst $warning: ThemedStyle<ViewStyle> = (theme) => ({\n  backgroundColor: theme.colors.alert,\n  color: \"white\",\n  padding: theme.spacing.lg,\n})\n\nconst $viewPresets: Record<Presets, ThemedStyle<ViewStyle>> = {\n  destructive: [$baseViewStyle, $warning],\n}\n```\n\nYou can then use it with your Button like this:\n\n```tsx\n<Button\n  text=\"Delete\"\n  // set the preset here\n  preset=\"destructive\"\n  onPress={() => thisItem.destroy()}\n/>\n```\n"
  },
  {
    "path": "docs/concept/Testing.md",
    "content": "---\nsidebar_position: 100\n---\n\n# Testing Ignite Apps\n\nAt Infinite Red, we want confidence that the code we are shipping isn't breaking the experience for our clients' users.\n\nOur philosophy is roughly based on the following idea from Guillermo Rauch:\n\n> Write tests. Not too many. Mostly integration.\n\nThis isn't a hard and fast rule, but it does express our approach fairly well.\n\n## Maestro Testing\n\nWe provide an [Ignite Cookbook recipe](https://ignitecookbook.com/docs/recipes/MaestroSetup) to help explain how to get started and run Maestro tests once you have ignited your app\n\n## Unit Testing\n\n> Unit tests cover the smallest parts of code, like individual functions or classes.\n\n-- <cite>[React Native docs](https://reactnative.dev/docs/testing-overview#unit-tests), under [CC By 4.0](https://creativecommons.org/licenses/by/4.0/)</cite>\n\n### Test Structure\n\nIn Ignite, we include unit tests for pure utility functions.\n\nIgnite uses Jest as our test runner. Jest tests are written using `it` or `test` statements, which take a describe of the test, and a function to execute the test code.\n\nThen, inside the test function, we can make \"assertions\", or what we expect a value to be using the `expect` function. We pass the value as the first argument to the `expect` function, then we use one of the \"matcher\" methods on `expect`, such as `.toBe` to describe what the value should match.\n\n[The React Native doc](https://reactnative.dev/docs/testing-overview#unit-tests) provide the following example for a unit test:\n\n```ts\nit(\"given a date in the past, colorForDueDate() returns red\", () => {\n  const input = colorForDueDate(\"2000-10-20\")\n  expect(input).toBe(\"red\")\n})\n```\n\nJest functions like `it`, `test`, `expect`, and more are loaded globally by the Jest test runner, so you don't need to import them.\n\n### Best Practices\n\n> When writing a test, do your best to make sure that your tests > include the following information:\n>\n> - Given - some precondition\n> - When - some action executed by the function that you’re testing\n> - Then - the expected outcome\n>   This is also known as AAA (Arrange, Act, Assert).\n\n-- <cite>[Structuring Tests in React Native Docs](https://reactnative.dev/docs/testing-overview#structuring-tests)</cite>\n\nYou can read more about how to best practices for creating tests in the [Structuring Tests](https://reactnative.dev/docs/testing-overview#structuring-tests) section of the React Native docs.\n\n### Writing Tests\n\nTo write your own tests, create a `.test.ts` file within the `app` or `test` directory.\n\nThen run `pnpm run test` to run all unit tests using Jest.\n\nWhen writing tests, you can also run Jest in watch mode by running `pnpm run test:watch`. This will start a long running Jest process, that re-runs your tests on save in your editor. This is useful for iterating on values and getting quick feedback about whether your changes were successful or not.\n\n### When to write Unit Tests\n\nThe most important question to ask when writing tests is \"what code should be unit tested?\" Not every line of code will benefit from a unit test. Typically, you'll want a unit test when you have code that can be run without external dependencies (like an API) that has some non-trivial logic.\n\n- **complicated regexes**: for many developers, regex's only make sense as you are writing them. Testing with a series of valid and invalid inputs can help ensure that they work as intended for future developers.\n- **nested if/else statements**: if you rely heavily on a function with a lot of if/else statements, it can be helpful to have tests to make sure that you can visit each condition. Often times when we have more than a handful of conditionals, it can become impossible to visit all of them without realizing it.\n- **validation functions**: often times we may write functions like `isJson()` to validate that a value is a specific shape. If critical parts of our code rely on the correctness of this function, we want to test that code!\n\n## Mocking\n\nHowever, often our code is not entirely written as pure functions. Most apps have side effects like making network requests, calling native modules, or reaching for global objects.\n\nIn integration tests, we can set up a suitable testing environment to handle these side effects.\n\nBut in unit tests, where we want to test an isolated piece of code, another option is to provide mocks for these external dependencies.\n\nJest provides a variety of mocking strategies for our code:\n\n### Mock Functions\n\nYou can create a mock callback function in Jest like so:\n\n```ts\n// take the input value, and add 42\nconst mockCallback = jest.fn((x) => 42 + x)\n```\n\nThis callback can be called like a normal function\n\n```ts\nconst added = [0, 1].map(mockCallback)\n```\n\nBut it also has a variety of properties added to it, such as `.mock`, which you can assert against later in tests.\n\n```ts\n// The mock function is called twice, once for each item in the array\nexpect(mockCallback.mock.calls.length).toBe(2)\n```\n\nThis particular example from the [official Jest docs](https://jestjs.io/docs/mock-functions#using-a-mock-function). You can read more about about [what other properties are available on the `.mock` property](https://jestjs.io/docs/mock-functions#mock-property) as well at the Jest docs.\n\n### Mock Modules\n\nTesting code that touches other libraries such as `axios` can be challenging, because we need to rely on the network for what gets returned from users.\n\n```tsx\nimport { View, Text } from \"react-native\"\nimport axios from \"axios\"\n\nexport const getUsers = () => axios.get(\"/users\").then((res) => res.data)\n```\n\nOne way to solve this, is by mocking the `axios` library to return a static list of users, so that we can reliably get the same information in our tests.\n\n```ts\nimport axios from \"axios\"\nimport { getUsers } from \"./users\"\n\njest.mock(\"axios\")\n\ntest(\"should fetch users\", () => {\n  const users = [{ name: \"Bob\" }]\n  const res = { data: users }\n\n  axios.get.mockImplementation(() => Promise.resolve(res))\n\n  getUsers().then((data) => {\n    expect(data).toEqual(users)\n  })\n})\n```\n\nThis example is derived from the the [Mocking Modules](https://jestjs.io/docs/mock-functions#mocking-modules) section of the Jest docs, where you can read about more sophisticated use cases.\n\n### React Native Modules\n\nIn addition to regular Javascript libraries, you can also mock out native modules in React Native. Using the following syntax\n\n```\njest.mock('react-native-video', () => 'Video');\n```\n\nThe first argument of `jest.mock` is the name of the module want to mock, but you can also pass a second argument to provide a function that returns the module.\n\nIn this example, this will return a default export.\n\nThis example is derived from [Testing React Native](https://jestjs.io/docs/tutorial-react-native#mock-native-modules-using-jestmock) section of the Jest docs, where you can read more.\n\n## Resources\n\n### Libraries\n\nThere are a variety of testing libraries available in React Native that you may find useful to add to your Ignite app\n\n- [React Native Testing Library](https://callstack.github.io/react-native-testing-library/) - port of @testing-library/react for React Native. Useful for unit testing components.\n- [Detox](https://wix.github.io/Detox/) and [Appium](https://appium.io/) - alternatives for integration testing to Maestro.\n\n### Relevant Reading\n\n- [React Native docs on Testing](https://reactnative.dev/docs/testing-overview)\n- [Testing React Native Apps with Jest](https://jestjs.io/docs/tutorial-react-native)\n- [Why Maestro?](https://maestro.mobile.dev/#why-maestro)\n- [Kent C. Dodds articles on Testing](https://kentcdodds.com/blog?q=test)\n"
  },
  {
    "path": "docs/concept/TypeScript.md",
    "content": "---\nsidebar_position: 140\n---\n\n# TypeScript\n\nWe find that TypeScript streamlines the developer experience by catching errors _before_ you hit refresh on that simulator, and prevents costly bugs by enforcing type safety.\n\nIn Ignite, TypeScript is fully set up, so if you know TS, all you need to do is start coding!\n\n## Resources\n\nIf you are new to TypeScript, here are some of our favorite resources:\n\n- [TypeScript in 5 minutes](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) for a quick read\n- [TypeScript in 50 minutes](https://youtu.be/WBPrJSw7yQA) for a longer watch\n- [Execute Program -- TypeScript course](https://www.executeprogram.com/course/typescript) -- free course by Gary Bernhardt\n- [TypeScript and VSCode](https://code.visualstudio.com/docs/typescript/typescript-tutorial) for awesome developer tools\n- [Official Docs](https://www.typescriptlang.org/docs/home.html)\n"
  },
  {
    "path": "docs/concept/Upgrades.md",
    "content": "## Upgrading\n\nIt's important to stay up-to-date with React Native updates. Luckily, we can bank on Expo having done this work for us. If you're letting Expo manage your native code for you, just run the following:\n\n```bash\nnpx expo install expo@latest\nnpx expo install --fix\n```\n\nAnd that's it! If you've added native dependencies outside the Expo ecosystem, you'll want to run prebuild again:\n\n```bash\nnpx expo prebuild --clean --no-install\n```\n\nAnd finally, if you're managing native code yourself, check out these tools:\n\n- [React Native Upgrade Helper](https://react-native-community.github.io/upgrade-helper/) great web based tool\n- [rn-diff-purge](https://github.com/react-native-community/rn-diff-purge)\n\nIt's less important to keep your Ignite app updated, but you might want to keep pace with Infinite Red's latest changes. To do that, [@nirre7](https://github.com/nirre7) built an amazing tool:\n\n- [ignite-diff-purge](https://github.com/nirre7/ignite-diff-purge) To help you see the diffs between different versions. The first diff is from 5.4.1 (ignite-bowser) and then continues with Ignite CLI boilerplate\n- [ignite-expo-diff-purge](https://github.com/nirre7/ignite-expo-diff-purge) To help you see the diffs between different versions. The first diff is from 5.4.1 (ignite-bowser with Expo) and then continues with Ignite CLI boilerplate with Expo\n- [ignite-bowser-diff-purge](https://github.com/nirre7/ignite-bowser-diff-purge) To help you see the diffs between different versions of the old [ignite-bowser](https://github.com/infinitered/ignite-bowser) boilerplate\n"
  },
  {
    "path": "docs/concept/_category_.json",
    "content": "{\n  \"label\": \"Concepts\",\n  \"position\": 3,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Concepts\"\n  }\n}\n"
  },
  {
    "path": "docs/contributing/Contributing-To-Ignite.md",
    "content": "---\nsidebar_position: 150\n---\n\n# Contributing to Ignite\n\n## Documentation\n\nWe include a `README.md`, `LICENSE`, and `CODE_OF_CONDUCT.md` in the root of the folder. You should read all three of them. The license is a standard MIT permissive license, and the code of conduct ensures that people are to treat each other with respect.\n\n## For contributors\n\n### Testing changes from your local copy of Ignite\n\nIf you want to test changes to the Ignite CLI that you have in a local git repo, that haven't been published to NPM, you can run the `bin/ignite` script via node.\n\n```sh\n# Generate a new Ignite app from your local copy of the Ignite CLI\nnode your-ignite-repo-clone/bin/ignite new PizzaApp\n\n# Use a built-in generator from your local copy\nnode your-ignite-repo-clone/bin/ignite generate component OrderButton\n```\n\nIn addition, we have automated tests you can run with `pnpm run test`.\n\n### Making PRs\n\nWe appreciate your contribution! To make it easier for us to review, please make sure to add a clear title and description that explains both what the change is, and why it's useful, if applicable.\n\nMaintainers may choose to rewrite this description for our changelog, or squash your commits when merging.\n\n## For maintainers\n\n### Merging PRs\n\nWhen merging PRs, we need maintainers to make sure that there's appropriately formatted commit message(s) for semantic-release to document changes for the release notes and pick the next-released version for Ignite.\n\nMake sure the merge commit is appropriately marked as a fix, feature, etc, and also maintain credit for the PR in release notes by keeping the default `(#PRNUMBER by @gituser)` in the title. See [Releasing Ignite](./Releasing-Ignite.md) for more info on how to format commit messages.\n\nYou have two choices when merging to Ignite:\n\n1. Merge with a merge commit, keeping the original commits. In this case, if the original commits are using the semantic-release conventions, those messages will be used for the changelog and release determinations.\n   1. If everything in the PR is sufficiently documented by those commit messages, you don't need to add anything to the commit message.\n   1. If there are no change entries, you should update the merge commit message when you merge to match the expected conventions.\n1. Alternatively, if you don't want to keep the original commits, you can use a squash merge. You will need to update the merge commit message to match semantic-release conventions as required by the particular change.\n\nNot every included commit in a PR needs to be formatted this way, only enough commits to ensure relevant changes have been documented.\n\nIn addition, if there's a feature branch for a future version of Ignite, you'll need to merge up `master` into this feature branch. See the next section for instructions.\n\n### Managing future version branches\n\nWhen we're working on a new release of Ignite, we'll create a feature branch for work that's exclusive to that version (e.g., `v10`).\n\nIf such a branch exists, whenever you merge a commit, you should also merge that change up from `master`->`next-version-branch`.\n\nTo do so, update your local branches, check out `next-version-branch`, and run `git merge --no-ff origin/master`. You do not need to customize this merge commit message.\n\nIt's a good idea to create a pull request for this merge-up if anyone else is working on Ignite along with you, so that they can be made aware of changes to `next-version-branch` ASAP. It also us to ensure tests are run before you update the branch.\n\n## Further reading\n\nCheck out [A Tour of the Ignite CLI Code Base](./Tour-of-Ignite.md) for more information about Ignite's structure and features.\n\nWe also have a guide to [Releasing Ignite](./Releasing-Ignite.md).\n"
  },
  {
    "path": "docs/contributing/Releasing-Ignite.md",
    "content": "---\nsidebar_position: 155\n---\n\n# Releasing Ignite\n\nIgnite is released via CI and [semantic release](https://semantic-release.gitbook.io/semantic-release/). When you merge a PR into `master`, you'll have the opportunity to edit the squash commit message, which determines what version level will be bumped as well as the automatically generated changelog that will be generated.\n\nWe like to include the PR author's name in the commit, as well, which will tag them in the release.\n\n## A note about semver\n\nIgnite, being a CLI/boilerplate and not a dependency, doesn't _strictly_ follow semver (there isn't really a public API being used, other than the CLI itself). We tend to use fixes and features as you'd expect, but major bumps tend to be more major features, stack updates, etc.\n\nSince you're unlikely to be directly depending on Ignite in your app, this shouldn't affect you.\n\n### bugfixes\n\n```bash\nfix(cli): Check for undefined in packager - fixes #1234 (#1300 by @jamonholmgren)\n```\n\nThis bumps the last number, aka the 3 in `1.2.3` would be come 4 (`1.2.4`). We call this a \"patch-level\" release.\n\nThe important part is the `fix:` ... the parenthesis are optional, and simply provide a bolded category for the release notes. Go look at recent releases to see how this is displayed.\n\n### features\n\n```bash\nfeat(tests): Added react-native-testing-library (#1300 by @joshuayoes)\n```\n\nThis will bump the middle number, aka the 2.3 in `1.2.3` would become 3.0 (`1.3.0`).\n\nThe important part is the `feat:` .. the parenthesis are optional, as above.\n\n### breaking changes\n\nFor breaking changes (bumping the first number, aka `2.0.0`), you need to include `BREAKING CHANGE:` in your commit message. Generally, the title of the commit will be something like `fix:` or `feat:` or `deps:` with `BREAKING CHANGE:` in the body.\n\nExample:\n\n```bash\nfeat(cli): Changes init to new in CLI (#1234 by @GantMan)\n\nBREAKING CHANGE: To spin up a new app, you'll use \"ignite new\" instead of \"ignite init\".\n```\n\n## Manual Beta Releases\n\nFor beta releases, the process is manual. Here are the steps:\n\n### 1. Ensure you have access to the npm package\n\nRun this to find your username:\n\n```bash\nnpm whoami\n```\n\nIn my case, it's `jamonholmgren`. Then run this to see if you have access:\n\n```bash\nnpm author list ignite-cli\n```\n\nIf your name is not included, you'll need to be added to the Infinite Red team:\n\n[https://www.npmjs.com/settings/infinitered/members](https://www.npmjs.com/settings/infinitered/members)\n\nThen run the author list command again to ensure you are listed.\n\n### 2. Build the CLI\n\nRun these commands from the Ignite root to clean & build the CLI:\n\n```bash\npnpm run clean\npnpm run build\n```\n\n### 3. Update the version manually\n\nUpdate the version to what you want in the root `package.json`. Note that you will NOT be checking this into git. Just set it before we release and then reset back to where it was afterward.\n\n```json\n{\n  \"name\": \"ignite-cli\",\n  \"version\": \"9.5.0-beta.1\",\n  \"description\": \"Infinite Red's hottest boilerplate for React Native.\",\n  \"bin\": {\n    \"ignite\": \"bin/ignite\",\n    \"ignite-cli\": \"bin/ignite\"\n  }\n  // ...\n}\n```\n\n### 4. Release!\n\nYou'll want to use `npm` for this, not `yarn`. I don't remember why.\n\n```bash\nnpm publish --tag=next\n```\n\nThis will publish a new version and set the @next tag to it. Note that `--tag=latest` is the same as not providing a tag and will publish as the main package version.\n\nCheck that the size of the newly published package is on the order of 3MB or less. If it's much more than that, you forgot to run `pnpm run clean`.\n\n### 5. Test\n\nIf you do it as listed above, you'll be able to test the new beta version using something like `npx ignite-cli@next ...`.\n\n### Rolling back a release\n\nYou can quickly roll back an erroneous release with:\n\n```bash\nnpm unpublish ignite-cli@1.2.3\n```\n\nYou have to do this [fairly quickly](https://docs.npmjs.com/policies/unpublish), as it won't work after a while.\n\nNote that that version number is \"burned\", even if unpublished -- you'll have to increment the version and try again.\n"
  },
  {
    "path": "docs/contributing/Tour-of-Ignite.md",
    "content": "---\nsidebar_position: 151\n---\n\n# A Tour of the Ignite CLI Code Base\n\nIf you're interested in contributing to Ignite and want to know more about how the code base is set up, this is a great place to start!\n\nIgnite is the result of over five years and well over 2,000 commits. We've put our blood, sweat, and tears into this boilerplate and given it away for free for many years. It's a great source of pride within Infinite Red and we spend a lot of time maintaining it.\n\nWithout further ado, let's get into it.\n\n## TypeScript, ESLint, and Prettier\n\nWe use TypeScript extensively throughout Ignite CLI and Ignite's boilerplate (more on this later). So, in the root folder _and_ in the boilerplate folder, you'll find a `tsconfig.json` file that configures how we use TypeScript in the project.\n\nWhen we build and publish the CLI, we compile the TypeScript source into JavaScript that will run with a reasonably recent version of Node.js on the command line.\n\nWe also use ESLint. The configuration for this is in the respective `package.json` files. Wherever possible, we try to keep our configuration in `package.json` rather than creating a new file in the root. ESLint is set up how Infinite Red prefers to write code.\n\nPrettier is also used in this project. You won't find terminal semicolons, generally speaking. You'll be fine, don't panic.\n\n## Documentation\n\nThe `docs` folder contains all of our documentation, including this file. They're all written in Markdown for a better/simpler developer authoring experience.\n\nThe docs are rolled up and published to https://docs.infinite.red by the [ir-docs project](https://github.com/infinitered/ir-docs). To learn how to see your documentation changes locally before they're published, see [the guide in ir-docs](https://github.com/infinitered/ir-docs?tab=readme-ov-file#testing-docs-locally).\n\n## CircleCI and GitHub\n\nThere are a couple folders at the root, `.circleci` and `.github`. These contain configuration for both services. Feel free to take a look.\n\n## Automatic Releases\n\nWe use `semantic-release`, an excellent package that allows for automatically releasing new versions of Ignite based on commit messages. You can read more about how Infinite Red uses semantic-release in [this document](https://github.com/infinitered/open-source/blob/master/Continuous-Deployment-Setup-NPM.md).\n\n## Manual Beta Releases\n\nIf you need to manually release a beta version, [the steps are documented here](./Releasing-Ignite.md).\n\n## Gluegun\n\nIgnite's CLI (`ignite-cli` on npm) is powered by [Gluegun](https://github.com/infinitered/gluegun). Gluegun is another Infinite Red library that makes building a full-featured command line interface (CLI) much easier.\n\n## bin\n\nThis folder contains one file, `ignite`. This file is what is initially run when you run `npx ignite-cli`. It figures out if you're running in production or dev mode and loads the appropriate file from there. It uses `ts-node` for dev mode, which allows you to test changes without having to rebuild your TypeScript every time.\n\n## src\n\nIn this folder are a couple folders that Gluegun uses, `commands` and `tools`. There's also a `cli.ts` and `types.ts` file.\n\n`cli.ts` is where the whole thing kicks off (called from the `./bin/ignite` JS executable).\n\n`types.ts` holds the project's primary types, and most of the project's source files import their types from this centralized place.\n\n#### src/commands\n\nThis contains the various CLI commands.\n\nFor example, `npx ignite-cli new` would run the `src/commands/new.ts` file ([link](https://github.com/infinitered/ignite/blob/master/src/commands/new.ts)).\n\nThe code in these files tends to rely heavily on functions contained in `./src/tools`. It acts more as a coordinator between the command-line options that are passed in and then calls out to the appropriate tools to actually, you know, do the thing that the user wants to do. It handles user input and also (for the most part) communicating back to the user what happened.\n\n#### src/tools\n\nThis folder contains functionality that is useful for Ignite to spin up new React Native apps and also inspect your environment, validate inputs, and whatnot.\n\nSome of this functionality, such as the `packager.ts`, could probably be moved upstream into Gluegun. If you're reading this, perhaps you could help us with this effort! (Don't forget to update this section when you do!)\n\nThe rest is key functionality that is needed for Ignite CLI to do its job. If you're fixing bugs, chances are you'll be in this folder mucking around.\n\n## test\n\nBack in the root, there's a `test` folder. This contains Jest tests for Ignite CLI.\n\nWe rely heavily on integration tests, which is why Ignite CLI's test suite is kinda slow. We mainly spin up a new Ignite app (in a temporary location) and then inspect the textual output from the result of the command as well as look at folders and files that it generates to ensure that it's actually doing what we want it to do.\n\nWe also run the default tests in a generated Ignite app, which further ensures that the CLI is generating a valid Ignite app.\n\n## boilerplate\n\nIn the root is another folder called `boilerplate`. This used to be called `Ignite Bowser` and was originally located here: https://github.com/infinitered/ignite-bowser [deprecated].\n\nHowever, we now include the boilerplate in the main CLI for convenience (as of version 6.0). We used to support multiple boilerplates with Ignite CLI, but that was a fairly underutilized feature, so this made sense for maintainability. You can read more here: https://shift.infinite.red/introducing-ignite-4-0-flame-1dfc891f9966.\n\nInside the boilerplate is a functioning React Native app! That's right, you can actually _run_ the boilerplate app when you clone Ignite down to your machine. Just `cd` into the `boilerplate` folder, run `pnpm install` and `npx pod-install`, and then `npx react-native run-ios` or `npx react-native run-android`.\n\nThis is one of the best changes from the previous system, as you can now work on the boilerplate in realtime and not have to make changes, spin up a new app, test, repeat, which was such a slowwww process before.\n\nWe won't go into the boilerplate itself, here. You can instead check out the [Folder Structure](../../boilerplate) documentation.\n\nHappy contributing!\n"
  },
  {
    "path": "docs/contributing/_category_.json",
    "content": "{\n  \"label\": \"Contributing\",\n  \"position\": 99,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Contributing-To-Ignite\"\n  }\n}\n"
  },
  {
    "path": "docs/expo/CNG.md",
    "content": "---\nsidebar_position: 30\n---\n\n# Expo Continuous Native Generation (CNG)\n\nWhen you want native platform code integrations, we recommend using the Expo CNG workflow with Ignite. This is one of the options when you spin up a new Ignite app.\n\nCNG (and Expo Prebuild) works well for almost any app. It's a great middle ground between Expo Go and the \"do it yourself\" (manual) workflow.\n\n1. You don't have to work with Gradle and CocoaPods directly\n2. Upgrades are much, much easier, since you're not working around your native code customizations\n3. There are config plugins for many popular libraries, and custom config plugins are possible to write\n\nYou can learn more about Expo CNG in [Expo's docs](https://docs.expo.dev/workflow/continuous-native-generation/).\n"
  },
  {
    "path": "docs/expo/DIY.md",
    "content": "---\nsidebar_position: 40\n---\n\n# Manual Workflow\n\nIf you don't want to use [Expo CNG](./CNG.md), you can manage the native code yourself via the manual workflow (`--workflow manual`). This is one of the options when you spin up a new Ignite app.\n\n:::tip\nImplementation detail that you probably didn't need to know: it's still a \"CNG\" workflow under the hood, but we generate the native ios and android folders for you -- and then you never run CNG again. Instead, you customize those native projects directly yourself. Hence, the manual part (formerly \"DIY\").\n:::\n\n## Why would I want to use the manual workflow?\n\nWe generally don't recommend it. It's mostly available for developers who are not interested in using Expo CNG / Prebuild and would rather maintain their own native code directly.\n"
  },
  {
    "path": "docs/expo/EAS.md",
    "content": "---\nsidebar_position: 50\n---\n\n# Expo Application Services (EAS)\n\nEAS is our favorite way to build and deploy Ignite apps. It's a cloud build service that allows you to build your app in the cloud, without Xcode or Android Studio.\n\nYou can learn more about EAS here: [https://expo.dev/eas](https://expo.dev/eas)\n\nIgnite apps come with an `eas.json` configuration file that looks something like this:\n\n```json\n{\n  \"cli\": {\n    \"version\": \">= 3.15.1\"\n  },\n  \"build\": {\n    \"development\": {\n      \"extends\": \"production\",\n      \"distribution\": \"internal\",\n      \"android\": {\n        \"gradleCommand\": \":app:assembleDebug\"\n      },\n      \"ios\": {\n        \"buildConfiguration\": \"Debug\",\n        \"simulator\": true\n      }\n    },\n    \"development:device\": {\n      \"extends\": \"development\",\n      \"distribution\": \"internal\",\n      \"ios\": {\n        \"buildConfiguration\": \"Debug\",\n        \"simulator\": false\n      }\n    },\n    \"preview\": {\n      \"extends\": \"production\",\n      \"distribution\": \"internal\",\n      \"ios\": { \"simulator\": true },\n      \"android\": { \"buildType\": \"apk\" }\n    },\n    \"preview:device\": {\n      \"extends\": \"preview\",\n      \"ios\": { \"simulator\": false }\n    },\n    \"production\": {}\n  },\n  \"submit\": {\n    \"production\": {}\n  }\n}\n```\n\nIt's preconfigured to work with Ignite apps, but you can customize it to your liking.\n"
  },
  {
    "path": "docs/expo/Expo-and-Ignite.md",
    "content": "---\nsidebar_position: 10\n---\n\n# Expo and Ignite\n\n![ignite+expo](https://miro.medium.com/max/1400/1*Ii4JuTWmVLeVBcqFyX3v5g.jpeg)\n\n> Expo is the easiest, nicest experience for Ignite apps. And I am convinced Ignite (especially version 9) is the nicest boilerplate for Expo apps you can possibly get. - Jamon Holmgren\n\nIf you're not familiar with [Expo](https://expo.dev), it's an open-source platform for making universal native apps for Android, iOS, and the web with [React Native](https://reactnative.dev). It's essentially a series of layers built around React Native. Here's a partial list of what Expo is:\n\n- [Expo Go](https://expo.dev/client) -- an app for quickly previewing React Native apps without building them\n- [EAS](https://eas.dev) -- a build & distribution service for mobile apps, especially RN\n- A _lot_ of high quality, well-maintained [React Native libraries](https://github.com/expo/expo)\n- Several integrated services, such as push notifications, over-the-air updates, [and much more](https://docs.expo.dev/)\n\n## Overview\n\nIn previous versions of Ignite (versions 6 and 7), you could pass in a `--expo` flag to make the resulting generated app \"Expo-ready\". In version 8 (code-named \"Maverick\") we made the boilerplate \"Expo-ready\" by default -- but without locking you into using Expo Go or Expo's services if you don't want to.\n\nIn version 9 (code-named \"Exp[ress]o\") we let Expo drive the native template initially. If you want to take over the native template and maintain all native code yourself, you are free to do so! However, if you want to opt-in to [Continuous Native Generation](https://docs.expo.dev/workflow/continuous-native-generation/) you can modify/extend the native template via Expo's [Config Plugins](https://docs.expo.dev/guides/config-plugins/). The Ignite template includes a Config Plugin that adds in a bug fix for `expo-splash-screen` when on Android 12.\n\nNow in version 10 (or Ignite X), we no longer support the option for the Expo Go during setup. This is due to Expo's recommendation for building and distributing production applications. You can read more [here](https://docs.expo.dev/develop/tools/#expo-go). This allowed for us to bring in some better dependencies to the boilerplate, which would not have been supported under Expo's ecosystem (such as `react-native-mmkv`). You can of course convert your Ignite project back to being [Expo Go compatible](https://ignitecookbook.com/docs/recipes/SwitchBetweenExpoGoCNG).\n\n```\n# Spin up a new app\nnpx ignite-cli new PizzaApp --yes\ncd PizzaApp\npnpm run ios\npnpm run android\n```\n\n### How it works\n\nIgnite comes with the lightweight `expo` package pre-installed and configured, which enables use of any of Expo's great third-party libraries, such as `expo-device`, `expo-font`, `expo-splash-screen`, and others -- even if you aren't running it in Expo Go or a \"managed app\".\n"
  },
  {
    "path": "docs/expo/_category.json",
    "content": "{\n  \"label\": \"Expo and Ignite\",\n  \"position\": 5,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"Expo-and-Ignite\"\n  }\n}\n"
  },
  {
    "path": "jest.config.js",
    "content": "/** @type {import('@jest/types').Config.ProjectConfig} */\nmodule.exports = {\n  testPathIgnorePatterns: [\"/node_modules/\", \"/boilerplate/\"],\n  testEnvironment: \"node\",\n  transform: {\n    \"^.+\\\\.ts$\": \"ts-jest\",\n  },\n  moduleFileExtensions: [\"ts\", \"tsx\", \"js\", \"jsx\"],\n  prettierPath: null,\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"ignite-cli\",\n  \"version\": \"11.5.0\",\n  \"description\": \"Infinite Red's hottest boilerplate for React Native.\",\n  \"bin\": {\n    \"ignite\": \"bin/ignite\",\n    \"ignite-cli\": \"bin/ignite\"\n  },\n  \"preferGlobal\": true,\n  \"license\": \"MIT\",\n  \"repository\": \"infinitered/ignite\",\n  \"homepage\": \"https://github.com/infinitered/ignite\",\n  \"files\": [\n    \"boilerplate\",\n    \"build\",\n    \"bin\",\n    \"README.md\",\n    \"template.config.js\"\n  ],\n  \"author\": {\n    \"name\": \"Infinite Red\",\n    \"email\": \"npm@infinite.red\",\n    \"url\": \"https://github.com/infinitered/ignite\"\n  },\n  \"scripts\": {\n    \"compile\": \"tsc -p .\",\n    \"typecheck\": \"tsc -p . --noEmit\",\n    \"build\": \"pnpm run clean && pnpm run compile && pnpm run build:assets\",\n    \"build:assets\": \"cp ./boilerplate/.gitignore ./boilerplate/.gitignore.template && cp ./src/assets/*.txt ./build/assets/\",\n    \"build:watch\": \"pnpm run build && pnpm run compile --watch\",\n    \"format\": \"prettier '**/*{.js,.ts,.tsx,.json,.md}'\",\n    \"format:write\": \"pnpm run format --write\",\n    \"format:check\": \"pnpm run format --check\",\n    \"lint\": \"eslint 'src/**' 'test/**'\",\n    \"depcruise\": \"depcruise src --config .dependency-cruiser.js\",\n    \"depcruise:graph\": \"depcruise src --include-only \\\"^src\\\" --config .dependency-cruiser.js --output-type dot | dot -T svg > ignite-cli-dependency-graph.svg && dot -T png ignite-cli-dependency-graph.svg -o ignite-cli-dependency-graph.png\",\n    \"test\": \"TS_JEST_DISABLE_VER_CHECKER=true jest\",\n    \"watch\": \"jest --watch\",\n    \"watch:debug\": \"pnpm run watch --runInBand --verbose\",\n    \"coverage\": \"jest --coverage\",\n    \"ci:publish\": \"pnpm run build && pnpm run semantic-release && pnpm run clean\",\n    \"semantic-release\": \"semantic-release\",\n    \"clean\": \"rm -drf ./build ./boilerplate/.gitignore.template ./boilerplate/node_modules ./boilerplate/ios/build/ ./boilerplate/ios/Pods/ ./boilerplate/android/app/build ./boilerplate/ios/.xcode.env.local\",\n    \"ignite-cli:dev\": \"node bin/ignite\",\n    \"ignite-cli:prod\": \"wrap () { node bin/ignite \\\"$@\\\" --compiled-build | cat; }; wrap\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  },\n  \"dependencies\": {\n    \"cross-spawn\": \"^7.0.3\",\n    \"deepmerge-json\": \"^1.1.0\",\n    \"ejs\": \"^3.1.8\",\n    \"gluegun\": \"5.1.6\",\n    \"sharp\": \"0.32.6\",\n    \"yaml\": \"^2.6.1\"\n  },\n  \"devDependencies\": {\n    \"@semantic-release/git\": \"^9.0.0\",\n    \"@types/jest\": \"^27.0.1\",\n    \"@types/node\": \"16.6.1\",\n    \"@types/react\": \"~19.0.10\",\n    \"@typescript-eslint/eslint-plugin\": \"7.18.0\",\n    \"@typescript-eslint/parser\": \"7.18.0\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"dependency-cruiser\": \"^17.0.2\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-config-expo\": \"~9.2.0\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"eslint-plugin-import\": \"^2.31.0\",\n    \"eslint-plugin-prettier\": \"^5.2.1\",\n    \"eslint-plugin-react-native\": \"^4.1.0\",\n    \"eslint-plugin-reactotron\": \"^0.1.7\",\n    \"husky\": \"^7.0.1\",\n    \"jest\": \"~29.7.0\",\n    \"prettier\": \"^3.3.3\",\n    \"react\": \"19.0.0\",\n    \"semantic-release\": \"^17.4.2\",\n    \"tempy\": \"^1.0.1\",\n    \"ts-jest\": \"^29.1.1\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"~5.8.3\"\n  },\n  \"release\": {\n    \"plugins\": [\n      \"@semantic-release/commit-analyzer\",\n      \"@semantic-release/release-notes-generator\",\n      \"@semantic-release/npm\",\n      \"@semantic-release/github\",\n      [\n        \"@semantic-release/git\",\n        {\n          \"assets\": \"package.json\",\n          \"message\": \"chore(release): ${nextRelease.version} [skip ci]\\n\\n${nextRelease.notes}\"\n        }\n      ]\n    ]\n  },\n  \"pnpm\": {\n    \"onlyBuiltDependencies\": [\n      \"sharp\"\n    ]\n  },\n  \"packageManager\": \"pnpm@10.9.0\"\n}\n"
  },
  {
    "path": "src/assets/index.ts",
    "content": "import * as fs from \"fs\"\nimport * as path from \"path\"\n\nexport type AssetName = \"logo.ascii.txt\" | \"logo-sm.ascii.txt\"\n\nexport const asset = {\n  get: (name: AssetName) => fs.readFileSync(path.join(__dirname, name), \"ascii\"),\n} as const\n"
  },
  {
    "path": "src/assets/logo-sm.ascii.txt",
    "content": "\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m \u001b[31m,\u001b[0m\u001b[31m;\u001b[0m\u001b[31m'\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m. \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m.\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m. \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\n\u001b[37m \u001b[0m \u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m'\u001b[0m \u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m;\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m.\u001b[31m;\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\n\u001b[37m \u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m,\u001b[0m.\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m'\u001b[0m\u001b[31m'\u001b[0m \u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m,\u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m.\u001b[0m\n.\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31m;\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m,\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m.\u001b[37m \u001b[0m\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m \u001b[37m \u001b[0m \u001b[31m;\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m..\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m. \u001b[37m \u001b[0m \u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m.\u001b[0m \u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m \n\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m \u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m.\u001b[0m .\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m   \u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m.\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31mc\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m \u001b[37m \u001b[0m\n\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m'\u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m.\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m.\u001b[37m \u001b[0m .\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m'\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m'\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m  \n\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m'\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m.\u001b[31m;\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m...\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m .\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31m,\u001b[0m\u001b[31m'\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m \n\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m.\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m'\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m.\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31m;\u001b[0m\u001b[31m:\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m.\u001b[0m .\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m.\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m.\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m;\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31m;\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m:\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m.\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31m;\u001b[0m\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m:\u001b[0m\u001b[31m;\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m:\u001b[0m\u001b[31m,\u001b[0m\u001b[31m'\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m.\u001b[31m.\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m;\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m  \u001b[37m \u001b[0m \u001b[37m \u001b[0m  \u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[31m:\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m \u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m;\u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31mc\u001b[0m\u001b[31mc\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n   \u001b[37m \u001b[0m\u001b[37m \u001b[0m.\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m;\u001b[0m.\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n"
  },
  {
    "path": "src/assets/logo.ascii.txt",
    "content": "\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m;\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m.\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m .\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m  \u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\n  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m.\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m.\u001b[31m'\u001b[0m\u001b[31m;\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m,\u001b[0m \n\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m   .\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m .\u001b[31m'\u001b[0m\u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m.\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m.\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m:\u001b[0m\u001b[31m;\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\n\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[37m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m;\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m,\u001b[0m\u001b[31m,\u001b[0m\u001b[31m'\u001b[0m \u001b[37m \u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m.\u001b[0m\n\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m'\u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[37m.\u001b[0m\u001b[31m;\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m'\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m  \u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m'\u001b[0m \u001b[37m \u001b[0m\n\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m;\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m,\u001b[0m\u001b[31m'\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[37m \u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m \u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m:\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m.\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m \u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m.\u001b[0m.\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m;\u001b[0m\u001b[31m:\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m.\u001b[37m \u001b[0m \u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m. \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m.\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m'\u001b[0m\u001b[37m \u001b[0m.\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m \u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m \u001b[37m \u001b[0m \u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31mc\u001b[0m\u001b[31m'\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31m;\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31m;\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m  \u001b[37m \u001b[0m\n\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m \u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m'\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m \u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m  \u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\n\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m,\u001b[0m\u001b[31m,\u001b[0m\u001b[31m:\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m.\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m .\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31m;\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31m,\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31m;\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m.\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m'\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m  \u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m \u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31m:\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m \u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31m;\u001b[0m\u001b[31m:\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m;\u001b[0m  \u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31m:\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m,\u001b[0m\u001b[31m;\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m'\u001b[0m.\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\n\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[37m \u001b[0m    \u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[31m'\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m'\u001b[0m\u001b[31m,\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m;\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m   \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m.\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m:\u001b[0m\u001b[31m,\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m:\u001b[0m\u001b[31m,\u001b[0m\u001b[31m'\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m\u001b[37m.\u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m \u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[31mc\u001b[0m\u001b[31m:\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m:\u001b[0m\u001b[31m;\u001b[0m\u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m,\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m;\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[31m;\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m.\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n \u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m. \u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m.\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31m,\u001b[0m\u001b[31m.\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[31m:\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m,\u001b[0m\u001b[31m,\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m,\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m  \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[31m.\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31mc\u001b[0m\u001b[31m'\u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m .\u001b[31m'\u001b[0m\u001b[31mc\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31ml\u001b[0m\u001b[31m:\u001b[0m\u001b[31m.\u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m \u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\u001b[37m \u001b[0m\n"
  },
  {
    "path": "src/cli.ts",
    "content": "import { build } from \"gluegun\"\n\n// Create the cli and kick it off\nasync function run(argv) {\n  // create a CLI runtime\n  const cli = build()\n    .brand(\"ignite-cli\")\n    .exclude([\"semver\", \"http\", \"template\"])\n    .src(__dirname)\n    .defaultCommand(require(\"./commands/help\"))\n    .create()\n\n  return cli.run(argv)\n}\n\nmodule.exports = { run }\n"
  },
  {
    "path": "src/commands/cache.ts",
    "content": "import { GluegunToolbox } from \"gluegun\"\n\nimport { cache } from \"../tools/cache\"\nimport { command, heading, igniteHeading, p } from \"../tools/pretty\"\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst path = (_toolbox: GluegunToolbox) => {\n  console.log(cache.rootdir())\n}\n\nconst clear = (toolbox: GluegunToolbox) => {\n  const { print } = toolbox\n  const spinner = print.spin(`Removing the dependency cache at '${cache.rootdir()}'`)\n  cache.clear()\n  spinner.succeed(`Removed the dependency cache at '${cache.rootdir()}'`)\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst help = (_toolbox: GluegunToolbox) => {\n  igniteHeading()\n  heading(\"ignite cache\")\n  p()\n  p(\"ignite cache is a parent command for interacting with the dependency cache\")\n  p(\"ignite uses on your machine to speed up the packager installation process.\")\n  p()\n  heading(\"Subcommands:\")\n  p()\n  const width = 10\n  command({ m: \"help\", width }, \"Display this message\", [\"ignite cache help\"])\n  p()\n  command({ m: \"path\", width }, \"Return the path of the dependency cache on your computer\", [\n    \"ignite cache path\",\n  ])\n  p()\n  command({ m: \"clear\", width }, \"Clear the dependency cache\", [\"ignite cache clear\"])\n  igniteHeading()\n}\n\nconst subcommands = {\n  help,\n  path,\n  clear,\n} as const\ntype SubCommand = (typeof subcommands)[keyof typeof subcommands]\n\nmodule.exports = {\n  alias: [\"c\"],\n  description: \"Commands related to the dependency cache for Ignite\",\n  run: async (toolbox: GluegunToolbox) => {\n    const { parameters } = toolbox\n\n    const subcommand: SubCommand | undefined = subcommands[parameters.first]\n\n    if (subcommand === undefined) {\n      help(toolbox)\n      return\n    }\n\n    subcommand(toolbox)\n  },\n}\n"
  },
  {
    "path": "src/commands/deprecated.ts",
    "content": "import { p, heading, command, direction } from \"../tools/pretty\"\nimport { GluegunToolbox } from \"../types\"\n\nexport default {\n  alias: [\"add\", \"remove\", \"spork\"],\n  run: async (toolbox: GluegunToolbox) => {\n    const { parameters } = toolbox\n\n    const [, , ...fullCommand] = parameters.raw\n    const commandArgs = fullCommand.join(\" \")\n\n    p()\n    switch (fullCommand[0]) {\n      case \"add\":\n      case \"remove\":\n      case \"plugin\":\n        heading(\"Ignite CLI version 4+ no longer supports plugins.\")\n        p(\"You can read more about it here:\")\n        p(\"https://shift.infinite.red/some-url-here-716aa5f9310e\")\n        p()\n        direction(\"To use the old plugins, run the old CLI:\")\n        p()\n        command(`npx ignite-cli@3 ${commandArgs}`)\n        break\n      case \"spork\":\n        heading(\"Ignite CLI version 4+ no longer supports the spork command.\")\n        p(`All generators are now \"sporked\" by default.`)\n        p()\n        p(\"You can read more about it here:\")\n        p(\"https://shift.infinite.red/some-url-here-716aa5f9310e\")\n        p()\n        direction(`To learn more, run`)\n        command(\"npx ignite-cli generate\")\n        break\n      default:\n        heading(\"That command is deprecated in Ignite CLI version 4+\")\n        p()\n        direction(\"To run the old CLI:\")\n        p()\n        command(`npx ignite-cli@3 ${commandArgs}`)\n    }\n  },\n}\n"
  },
  {
    "path": "src/commands/doctor.ts",
    "content": "/**\n * This command checks the current dev environment to see if their machine is set up\n * to run Ignite properly. This needs some TLC, as it is mostly designed\n * for the old Ignite CLI and Bowser. Ignite v4 (\"flame\") is a combination of the two.\n */\nimport { GluegunToolbox } from \"gluegun\"\nimport * as os from \"os\"\n\nimport { packager } from \"../tools/packager\"\n\nconst isWindows = process.platform === \"win32\"\nconst isMac = process.platform === \"darwin\"\n\nmodule.exports = {\n  description: \"Checks your dev environment for dependencies.\",\n  run: async function (toolbox: GluegunToolbox) {\n    // fistful of features\n    const {\n      filesystem: { separator },\n      system: { run, which },\n      print: { colors, info, table },\n      strings: { padEnd },\n      meta,\n    } = toolbox\n\n    // display helpers\n    const column1 = (label, length = 16) => padEnd(label || \"\", length)\n    const column2 = (label) => colors.yellow(padEnd(label || \"-\", 10))\n    const column3 = (label) => colors.muted(label)\n\n    // -=-=-=- system -=-=-=-\n    const platform = process.platform\n    const arch = os.arch()\n    const cpus = os.cpus() || []\n    const firstCpu = cpus[0] || { model: undefined }\n    const cpu = `${firstCpu.model}`\n    const cores = `${cpus.length} cores`\n    const directory = `${process.cwd()}`\n\n    info(colors.cyan(\"System\"))\n    table([\n      [column1(\"platform\"), column2(platform), column3(\"\")],\n      [column1(\"arch\"), column2(arch), column3(\"\")],\n      [column1(\"cpu\"), column2(cores), column3(cpu)],\n      [column1(\"directory\"), column2(directory.split(separator).pop()), column3(directory)],\n    ])\n\n    // -=-=-=- javascript -=-=-=-\n    const nodePath = which(\"node\")\n    const nodeVersion = (await run(\"node --version\", { trim: true })).replace(\"v\", \"\")\n    const npmPath = which(\"npm\")\n    const npmVersion = npmPath && (await run(\"npm --version\", { trim: true }))\n    const yarnPath = which(\"yarn\")\n    const yarnVersion = yarnPath && (await run(\"yarn --version\", { trim: true }))\n    const yarnMajorVersion = parseInt(yarnVersion.split(\".\")[0], 10)\n    let pnpmPath\n    let pnpmVersion\n    if (yarnMajorVersion === 1) {\n      pnpmPath = which(\"pnpm\")\n      pnpmVersion = pnpmPath && (await run(\"pnpm --version\", { trim: true }))\n    }\n    const bunPath = which(\"bun\")\n    const bunVersion = bunPath && (await run(\"bun --version\", { trim: true }))\n\n    const nodeInfo = [column1(\"node\"), column2(nodeVersion), column3(nodePath)]\n    const npmInfo = [column1(\"npm\"), column2(npmVersion), column3(npmPath || \"not installed\")]\n    const yarnInfo = [column1(\"yarn\"), column2(yarnVersion), column3(yarnPath || \"not installed\")]\n    const pnpmInfo = [column1(\"pnpm\"), column2(pnpmVersion), column3(pnpmPath || \"not installed\")]\n    const bunInfo = [column1(\"bun\"), column2(bunVersion), column3(bunPath || \"not installed\")]\n\n    async function packageInfo(packagerName: \"npm\" | \"yarn\" | \"pnpm\" | \"bun\") {\n      return (await packager.list({ packagerName, global: true })).map((nameAndVersion) => [\n        column1(\"  \" + nameAndVersion[0]),\n        column2(nameAndVersion[1]),\n        column3(\"\"),\n      ])\n    }\n    const npmPackages = npmPath ? await packageInfo(\"npm\") : []\n    const yarnPackages = yarnPath && yarnMajorVersion === 1 ? await packageInfo(\"yarn\") : []\n    // TODO: list pnpm global packages in doctor output\n    const pnpmPackages = pnpmPath\n      ? [[column1(\"  \"), column2(\"<no pnpm global package info available>\"), column3(\"\")]]\n      : []\n    const haveGlobalPackages = npmPackages.length > 0 || yarnPackages.length > 0\n\n    info(\"\")\n    info(colors.cyan(`JavaScript${haveGlobalPackages ? \" (and globally-installed packages)\" : \"\"}`))\n    table([\n      nodeInfo,\n      npmInfo,\n      ...npmPackages,\n      yarnInfo,\n      ...yarnPackages,\n      pnpmInfo,\n      ...pnpmPackages,\n      bunInfo,\n    ])\n\n    // -=-=-=- ignite -=-=-=-\n    const ignitePath = which(\"ignite\")\n    const igniteSrcPath = `${meta.src}`\n    const igniteVersion = meta.version()\n\n    info(\"\")\n    info(colors.cyan(\"Ignite\"))\n    const igniteTable = []\n    igniteTable.push([column1(\"ignite-cli\"), column2(igniteVersion), column3(ignitePath)])\n    igniteTable.push([\n      column1(\"ignite src\"),\n      column2(igniteSrcPath.split(separator).pop()),\n      column3(igniteSrcPath),\n    ])\n    table(igniteTable)\n\n    // -=-=-=- android -=-=-=-\n    const androidPath = process.env.ANDROID_HOME\n    let javaPath = which(\"java\")\n    const javaVersionCmd = \"java -version 2>&1\"\n    let javaVersion\n\n    try {\n      javaVersion = javaPath && (await run(javaVersionCmd))?.match(/\"(.*)\"/)?.slice(-1)[0]\n    } catch (_) {\n      javaVersion = \"-\"\n      javaPath = \"not installed\"\n    }\n\n    info(\"\")\n    info(colors.cyan(\"Android\"))\n    table([\n      [column1(\"java\"), column2(javaVersion), column3(javaPath)],\n      [column1(\"android home\"), column2(\"-\"), column3(androidPath)],\n    ])\n\n    // -=-=-=- iOS -=-=-=-\n    if (isMac) {\n      const xcodePath = which(\"xcodebuild\")\n      const xcodeVersion =\n        xcodePath && (await run(\"xcodebuild -version\", { trim: true })).split(/\\s/)[1]\n\n      info(\"\")\n      info(colors.cyan(\"iOS\"))\n      table([[column1(\"xcode\"), column2(xcodeVersion)]])\n\n      const cocoaPodsPath = which(\"pod\") || \"\"\n      const cocoaPodsVersion = cocoaPodsPath\n        ? await run(\"pod --version\", { trim: true })\n        : \"Not installed\"\n      table([[column1(\"cocoapods\"), column2(cocoaPodsVersion), column3(cocoaPodsPath)]])\n    }\n\n    // -=-=-=- windows -=-=-=-\n    // TODO: what can we check on Windows?\n    if (isWindows) {\n      // info('')\n      // info(colors.cyan('Windows'))\n      // table([])\n    }\n\n    // -=-=-=- tools -=-=-=-\n    info(\"\")\n    info(colors.cyan(\"Tools\"))\n    const gitPath = which(\"git\")\n    const gitVersion = gitPath && (await run(\"git --version\", { trim: true }))\n    const gitInfo = [column1(\"git\"), column2(gitVersion), column3(gitPath || \"not installed\")]\n    table([gitInfo])\n  },\n}\n"
  },
  {
    "path": "src/commands/generate/app-icon.ts",
    "content": "import { GluegunToolbox } from \"gluegun\"\n\nimport { generateAppIcons, runGenerator, validateAppIconGenerator } from \"../../tools/generators\"\nimport { heading, p, warning } from \"../../tools/pretty\"\n\nmodule.exports = {\n  alias: [\"launcher-icon\"],\n  description: \"Generates app-icons from templates\",\n  run: async (toolbox: GluegunToolbox) => {\n    const generator = toolbox.parameters.command?.toLowerCase()\n    runGenerator(toolbox, generate, generator)\n  },\n}\n\nasync function generate(toolbox: GluegunToolbox) {\n  const { parameters } = toolbox\n\n  const { isValid, messages } = await validateAppIconGenerator(\"expo\", parameters.options)\n\n  if (!isValid) {\n    messages.forEach((message) => warning(message))\n    return\n  }\n\n  const isSuccessful = await generateAppIcons(\"expo\")\n\n  if (isSuccessful) {\n    heading(`App icons generated!`)\n    p(\n      \"Uninstall the application from your simulator/emulator and re-build your app to see the changes!\",\n    )\n  }\n}\n"
  },
  {
    "path": "src/commands/generate/splash-screen.ts",
    "content": "import { GluegunToolbox } from \"gluegun\"\n\nimport {\n  generateSplashScreen,\n  runGenerator,\n  validateSplashScreenGenerator,\n} from \"../../tools/generators\"\nimport { command, heading, p, warning } from \"../../tools/pretty\"\n\nmodule.exports = {\n  alias: [\"splash\"],\n  description: \"Generates splash-screen from templates\",\n  run: async (toolbox: GluegunToolbox) => {\n    const generator = toolbox.parameters.command?.toLowerCase()\n    runGenerator(toolbox, generate, generator)\n  },\n}\n\nasync function generate(toolbox: GluegunToolbox) {\n  const { parameters } = toolbox\n\n  // what generator are we running?\n  const generator = parameters.command.toLowerCase()\n\n  // color to use for the splash screen\n  let backgroundColor = parameters.first\n\n  // get optional android/ios logo sizes\n  // eslint-disable-next-line prefer-const\n  let { androidSize = 180, iosSize = 212, ...options } = parameters.options || {}\n\n  if (!backgroundColor) {\n    warning(\n      `⚠️  Please specify the background color of the screen that will be used to generate the splash screen.`,\n    )\n\n    p()\n    command(`npx ignite-cli g ${generator} \"#191015\" [--android-size=180 --ios-size=212]`)\n    return\n  }\n\n  // force coercion of size to number if a user quotes it\n  // force coercion of background to string if the hex color is numerical; add \"#\" if it's not there\n  androidSize = Number(androidSize)\n  iosSize = Number(iosSize)\n  backgroundColor = String(backgroundColor).startsWith(\"#\")\n    ? backgroundColor\n    : `#${backgroundColor}`\n\n  const { isValid, messages } = await validateSplashScreenGenerator(\n    { androidSize, iosSize, backgroundColor },\n    options,\n  )\n\n  if (!isValid) {\n    messages.forEach((message) => warning(message))\n    return\n  }\n\n  const isSuccessful = await generateSplashScreen({ androidSize, iosSize, backgroundColor })\n\n  if (isSuccessful) {\n    heading(`Splash screen generated!`)\n    p(\n      \"Uninstall the application from your simulator/emulator, run `prebuild:clean` and re-build your app to see the changes!\",\n    )\n    p(\n      \"Note: (vanilla Android) The splash-screen will not appear if you launch your app via the terminal or Android Studio. Kill the app and launch it normally by tapping on the launcher icon. https://stackoverflow.com/a/69831106\",\n    )\n    p(\n      \"Note: (vanilla iOS) You might notice the splash-screen logo change size. This happens in debug/development mode. Try building the app for release.\",\n    )\n  }\n}\n"
  },
  {
    "path": "src/commands/generate.ts",
    "content": "import { GluegunToolbox } from \"gluegun\"\n\nimport { Options } from \"./new\"\nimport { boolFlag } from \"../tools/flag\"\nimport { frontMatterDirectoryDir, generateFromTemplate, runGenerator } from \"../tools/generators\"\nimport { command, heading, p, prettyPrompt, warning } from \"../tools/pretty\"\n\nconst SUB_DIR_DELIMITER = \"/\"\n\nmodule.exports = {\n  alias: [\"g\", \"generator\", \"generators\"],\n  description: \"Generates components and other features from templates\",\n  run: async (toolbox: GluegunToolbox) => {\n    const generator = toolbox.parameters.first?.toLowerCase()\n    runGenerator(toolbox, generate, generator)\n  },\n}\n\nasync function generate(toolbox: GluegunToolbox) {\n  const { parameters, strings, filesystem, prompt } = toolbox\n\n  // what generator are we running?\n  const generator = parameters.first.toLowerCase()\n\n  // check if we should override front matter dir or default dir\n  let dir = parameters.options.dir ?? parameters.third\n\n  // we need a name for this component\n  let name = parameters.second\n  if (!name) {\n    warning(`⚠️  Please specify a name for your ${generator}:`)\n    p()\n    command(`npx ignite-cli g ${generator} MyName`)\n    return\n  }\n\n  // parse any subdirectories from the specified name\n  let subdirectory = \"\"\n  if (name.indexOf(SUB_DIR_DELIMITER) > -1) {\n    const lastSlashIndex = name.lastIndexOf(SUB_DIR_DELIMITER)\n    subdirectory = name.substring(0, lastSlashIndex + 1)\n    name = name.substring(lastSlashIndex + 1)\n  }\n\n  // avoid the my-component-component phenomenon\n  const pascalGenerator = strings.pascalCase(generator)\n  let pascalName = strings.pascalCase(name)\n  if (pascalName.endsWith(pascalGenerator)) {\n    p(`Stripping ${pascalGenerator} from end of name`)\n    p(\n      `Note that you don't need to add ${pascalGenerator} to the end of the name -- we'll do it for you!`,\n    )\n    pascalName = pascalName.slice(0, -1 * pascalGenerator.length)\n    command(`npx ignite-cli generate ${generator} ${pascalName}`)\n  }\n  /**\n   * Check if the project uses Expo Router as a dependency in package.json,\n   * denoting an Expo Router app.\n   */\n  if (generator === \"route\") {\n    const packageJson = filesystem.read(\"package.json\", \"json\")\n    const isExpoRouterApp = !!packageJson?.dependencies?.[\"expo-router\"]\n\n    const isSrcAppStructure = filesystem.exists(\"src/app\") === \"dir\"\n    const isAppStructure = filesystem.exists(\"app\") === \"dir\"\n    const defaultRouterDir = isSrcAppStructure ? \"src/app\" : isAppStructure ? \"app\" : null\n\n    if (isExpoRouterApp) {\n      const directoryDirSetInFrontMatter = frontMatterDirectoryDir(\"route\")\n      p(directoryDirSetInFrontMatter)\n\n      if (directoryDirSetInFrontMatter || dir) {\n        heading(\n          `It looks like you're working in a project using Expo Router, determined directory for route from ${dir ? \"override\" : \"template front matter\"}`,\n        )\n        dir = dir || directoryDirSetInFrontMatter\n      } else {\n        const result = await prompt.ask({\n          type: \"input\",\n          name: \"dir\",\n          message: `It looks like you're working in a project using Expo Router, please enter the desired directory${defaultRouterDir ? ` (e.g., ${defaultRouterDir})` : \"\"}:`,\n          initial: defaultRouterDir,\n        })\n\n        if (result.dir) {\n          // Validate the directory\n          const isValidDir = filesystem.exists(result.dir) === \"dir\"\n          if (isValidDir) {\n            dir = result.dir\n          } else {\n            const createDirResult = await prompt.ask({\n              type: \"confirm\",\n              name: \"createDir\",\n              message: `⚠️  Directory ${result.dir} does not exist. Would you like to create it?`,\n              initial: true,\n              format: prettyPrompt.format.boolean,\n            })\n\n            if (createDirResult.createDir) {\n              filesystem.dir(result.dir)\n              dir = result.dir\n            } else {\n              warning(`⚠️ Placing screen in ${result.dir} root.`)\n              p()\n              dir = result.dir\n            }\n          }\n        }\n      }\n    }\n  }\n\n  // okay, let's do it!\n  p()\n  const options: Options = parameters.options\n  const defaultOverwrite = false\n  const overwrite = !options.overwrite ? defaultOverwrite : boolFlag(options.overwrite)\n  const { written, overwritten, exists } = await generateFromTemplate(generator, {\n    name: pascalName,\n    originalName: name,\n    overwrite,\n    subdirectory,\n    dir,\n    case: parameters.options.case,\n  })\n\n  heading(`Generated new files:`)\n\n  if (exists.length > 0) {\n    if (written.length > 0) {\n      written.forEach((f) => p(f))\n    } else p(`<none>`)\n    p()\n    heading(`Skipped these files because they already exist:`)\n    exists.forEach((f) => p(f))\n    p()\n    heading(\"To overwrite these files, run the command again with the `--overwrite` flag\")\n  } else if (overwritten.length > 0) {\n    overwritten.forEach((f) => p(f))\n  } else {\n    written.forEach((f) => p(f))\n  }\n}\n"
  },
  {
    "path": "src/commands/help.ts",
    "content": "import { GluegunToolbox } from \"gluegun\"\n\nimport { showGeneratorHelp } from \"../tools/generators\"\nimport { p, command, heading, direction, link } from \"../tools/pretty\"\n\nmodule.exports = {\n  dashed: true,\n  alias: [\"h\"],\n  description: \"Displays Ignite CLI help\",\n  run: async (toolbox: GluegunToolbox) => {\n    const { meta, parameters } = toolbox\n\n    p()\n\n    // specific help -- generators\n    if (\n      parameters.second &&\n      (parameters.second === \"g\" || parameters.second.startsWith(\"generat\"))\n    ) {\n      return showGeneratorHelp(toolbox)\n    }\n\n    heading(`Welcome to Ignite ${meta.version()}!`)\n    p()\n    p(\"Ignite is a CLI that helps you spin up a new React Native app using a\")\n    p(\"battle-tested tech stack.\")\n    p()\n    heading(\"Commands\")\n    p()\n    command(\"new             \", \"Creates a new React Native app\", [\n      \"npx ignite-cli new MyApp\",\n      \"npx ignite-cli new MyApp --bundle com.mycompany.myapp\",\n    ])\n    p()\n    command(\"generate (g)    \", \"Generates components and other app features\", [\n      \"npx ignite-cli generate --hello\",\n      \"npx ignite-cli generate component Hello\",\n      \"npx ignite-cli generate model User\",\n      \"npx ignite-cli generate screen Login\",\n      \"npx ignite-cli generate component Hello --dir src/components\",\n    ])\n    p()\n    command(\n      \"doctor          \",\n      \"Checks your environment & displays versions of installed dependencies\",\n      [\"npx ignite-cli doctor\"],\n    )\n    p()\n    command(\"rename          \", \"Renames your React Native project (experimental)\", [\n      \"npx ignite-cli rename NewName com.mycompany.newname\",\n    ])\n    p()\n    command(\n      \"remove-demo (rd)\",\n      \"Removes demo code from the project (add --dry-run to list changes but not execute)\",\n      [\"npx ignite-cli remove-demo\", \"npx ignite-cli remove-demo --dry-run\"],\n    )\n    p()\n    command(\n      \"remove-demo-markup (rdm)\",\n      \"Removes @demo markup from the project (add --dry-run to list changes but not execute)\",\n      [\"npx ignite-cli remove-demo-markup\", \"npx ignite-cli remove-demo-markup --dry-run\"],\n    )\n    p()\n    direction(\n      `See the documentation: ${link(\"https://github.com/infinitered/ignite/tree/master/docs\")}`,\n    )\n    p()\n    direction(\n      `If you need additional help, join our Slack at ${link(\"http://community.infinite.red\")}`,\n    )\n    p()\n  },\n}\n"
  },
  {
    "path": "src/commands/ignite.ts",
    "content": "import { GluegunToolbox } from \"gluegun\"\n\nmodule.exports = {\n  description: \"🔥 The Ignite CLI 🔥\",\n  run: async (toolbox: GluegunToolbox) => {\n    const {\n      parameters: { first },\n      print: { error },\n    } = toolbox\n\n    if (first !== undefined) {\n      error(`ignite-cli '${first}' is not a command`)\n    } else {\n      return require(\"./help\").run(toolbox)\n    }\n  },\n}\n"
  },
  {
    "path": "src/commands/issue.ts",
    "content": "/**\n * This command runs ignite doctor and launches the user's browser\n * to a new GitHub issue populated with the provided title and doctor command  output\n */\nimport { GluegunToolbox } from \"gluegun\"\n\nimport {\n  command,\n  p,\n  warning,\n  startSpinner,\n  stopSpinner,\n  prettyPrompt,\n  prefix,\n} from \"../tools/pretty\"\nimport { stripANSI } from \"../tools/strip-ansi\"\n\nconst isMac = process.platform === \"darwin\"\n\nconst START_CMD = isMac ? \"open\" : \"start\"\n\nmodule.exports = {\n  alias: [\"i\"],\n  description: \"Opens a new GitHub issue for Ignite.\",\n  run: async function (toolbox: GluegunToolbox) {\n    const {\n      filesystem,\n      system,\n      print: { colors },\n      parameters,\n      prompt,\n    } = toolbox\n\n    const { yellow, cyan } = colors\n\n    // title for issue\n    let title = parameters.first\n\n    if (!title) {\n      warning(`⚠️  Please specify a title for your issue:`)\n      p()\n      command(`ignite i \"The issue title here\"`)\n      return\n    }\n\n    // do title quote check\n    if (parameters.array.length > 1 && !parameters.first.startsWith('\"')) {\n      const suggestedTitle = parameters.array.join(\" \")\n\n      const titleResponse = await prompt.ask<{ title: boolean }>(() => ({\n        type: \"confirm\",\n        name: \"title\",\n        message: `Received title \"${title}\" but without quotes, did you mean \"${suggestedTitle}\"?`,\n        initial: true,\n        format: prettyPrompt.format.boolean,\n        prefix,\n      }))\n\n      if (titleResponse.title) {\n        title = suggestedTitle\n      }\n    }\n\n    // get the `ignite doctor` output\n    let doctorInfo = \"\"\n    startSpinner(\" Gathering system and project details\")\n    try {\n      const IGNITE = \"node \" + filesystem.path(__dirname, \"..\", \"..\", \"bin\", \"ignite\")\n\n      const doctorANSI = await system.run(`${IGNITE} doctor`)\n      doctorInfo = stripANSI(doctorANSI)\n    } catch (e) {\n      p(yellow(\"Unable to gather system and project details.\"))\n    }\n    stopSpinner(\" Gathering system and project details\", \"🛠️\")\n\n    // open up GitHub issue form\n    const header = `\\n### Describe the bug\\n\\n<!--🚨🚨🚨 Fill out bug description here, please provide reproducible steps, repo/code snippets and any other helpful information if available! 🚨🚨🚨-->\\n\\n### Additional info\\nnpx ignite-cli doctor`\n    const body = `${header}\\n\\n\\`\\`\\`\\n${doctorInfo}\\`\\`\\``\n\n    const url = new URL(\"https://github.com/infinitered/ignite/issues/new\")\n    const params = new URLSearchParams(url.search)\n    params.append(\"title\", title)\n    params.append(\"body\", body)\n\n    try {\n      p(cyan(\"Starting GitHub issue, thanks for alerting us!\"))\n      await system.run(`${START_CMD} \"${url}?${params.toString()}\"`)\n    } catch (e) {\n      p(yellow(\"Unable to start GitHub issue.\"))\n    }\n\n    process.exit(0)\n  },\n}\n"
  },
  {
    "path": "src/commands/new.ts",
    "content": "import { EOL } from \"os\"\n\nimport { cache } from \"../tools/cache\"\nimport { demoDependenciesToRemove, findDemoPatches } from \"../tools/demo\"\nimport { boolFlag } from \"../tools/flag\"\nimport { packager, PackagerName } from \"../tools/packager\"\nimport {\n  p,\n  startSpinner,\n  stopSpinner,\n  clearSpinners,\n  ascii,\n  em,\n  link,\n  ir,\n  prefix,\n  prettyPrompt,\n  pkgColor,\n  hr,\n  INDENT,\n  stopLastSpinner,\n} from \"../tools/pretty\"\nimport {\n  isAndroidInstalled,\n  copyBoilerplate,\n  renameReactNativeApp,\n  replaceMaestroBundleIds,\n  refactorExpoRouterReactotronCmds,\n  updateExpoRouterSrcDir,\n  updateExpoRouterPackageJson,\n  cleanupExpoRouterConversion,\n  updatePackagerCommandsInReadme,\n  createGeneratorTemplate,\n  EXPO_ROUTER_SCREEN_TEMPLATE,\n  EXPO_ROUTER_ROUTE_TEMPLATE,\n} from \"../tools/react-native\"\nimport type { ValidationsExports } from \"../tools/validations\"\nimport { GluegunToolbox } from \"../types\"\n\ntype Workflow = \"cng\" | \"manual\"\n\nexport interface Options {\n  /**\n   * custom bundle identifier for iOS and Android\n   *\n   * Input Source: `prompt.ask` | `parameter.option`\n   * @default `com.${name}`\n   * @example 'com.pizzaapp'\n   */\n  bundle?: string\n  /**\n   * Log raw parameters for debugging, run formatting script not quietly\n   *\n   * Input Source: `parameter.option`\n   * @default false\n   */\n  debug?: boolean\n  /**\n   * Create new git repository and create an initial commit with boilerplate changes\n   *\n   * Input Source: `prompt.ask` | `parameter.option`\n   * @default true\n   */\n  git?: boolean\n  /**\n   * Whether or not to run packager install script after project is created\n   *\n   * Input Source: `prompt.ask` | `parameter.option`\n   * @default true\n   */\n  installDeps?: boolean\n  /**\n   * Remove existing directory otherwise throw if exists\n   *\n   * Input Source: `prompt.ask` | `parameter.option`\n   * @default false\n   */\n  overwrite?: boolean\n  /**\n   * Package manager to install dependencies with\n   *\n   * Input Source: `prompt.ask`| `parameter.option`\n   */\n  packager?: \"npm\" | \"yarn\" | \"pnpm\" | \"bun\"\n  /**\n   * The target directory where the project will be created.\n   *\n   * Input Source: `prompt.ask` | `parameter.option`\n   * @default `${cwd}/${projectName}`\n   */\n  targetPath?: string\n  /**\n   * Whether or not to remove the boilerplate demo code\n   *\n   * Input Source: `prompt.ask` | `parameter.option`\n   * @default false\n   */\n  removeDemo?: boolean\n  /**\n   * Whether or not to use the dependency cache to speed up installs\n   * Input Source: `parameter.option`\n   * @default true\n   */\n  useCache?: boolean\n  /**\n   * alias for `yes`\n   *\n   * Whether or not to accept the default options for all prompts\n   *\n   * Input Source: `parameter.option`\n   * @default false\n   */\n  y?: boolean\n  /**\n   * Whether or not to accept the default options for all prompts\n   * Input Source: `parameter.option`\n   * @default false\n   */\n  yes?: boolean\n  /**\n   * Whether or not to opt into specific experimental features\n   */\n  experimental?: string\n  /**\n   * Expo workflow to determine if we need to generate native directories\n   * and include them in .gitignore or not\n   *\n   * Input Source: `prompt.ask`| `parameter.option`\n   * @default cng\n   */\n  workflow?: Workflow\n  /**\n   * Whether or not to timeout if the app creation takes too long\n   * Input Source: `parameter.option`\n   * @default false\n   */\n  noTimeout?: boolean\n  /**\n   * Set by CLI parser when --no-timeout is passed (negation of timeout)\n   * Input Source: `parameter.option`\n   */\n  timeout?: boolean\n\n  /**\n   * Deprecated Props:\n   */\n\n  /**\n   * Whether or not to enable the New Architecture\n   *\n   * Input Source: `prompt.ask` | `parameter.option`\n   * @deprecated flag left in for backwards compatibility, warn them to use old Ignite\n   * @default true\n   */\n  newArch?: boolean\n}\n\nmodule.exports = {\n  run: async (toolbox: GluegunToolbox) => {\n    // #region Toolbox\n    const { print, filesystem, system, meta, parameters, strings, prompt } = toolbox\n    const { kebabCase } = strings\n    const { exists, path, removeAsync, copy, read, write, homedir } = filesystem\n    const { info, colors, warning } = print\n    const { gray, cyan, yellow, white, red, underline } = colors\n    const options: Options = parameters.options\n\n    const yname = boolFlag(options.y) || boolFlag(options.yes)\n    const timeoutFlag = boolFlag(options.timeout) ?? true\n    const noTimeout = boolFlag(options.noTimeout) ?? !timeoutFlag\n    const getDefault = (option: unknown) => yname && option === undefined\n\n    const CMD_INDENT = \"  \"\n    // Add just a _little_ more spacing to match with spinners and heading\n    const p2 = (m = \"\") => p(` ${m}`)\n    const command = (cmd: string) => p2(white(CMD_INDENT + cmd))\n\n    // Absolute maximum that an app can take after inputs\n    // is 10 minutes ... otherwise we've hung up somewhere and need to exit.\n    const MAX_APP_CREATION_TIME = 10 * 60 * 1000\n    const timeoutExit = () => {\n      p()\n      p(yellow(\"Error: App creation timed out.\"))\n      if (!debug) p(gray(\"Run again with --debug to see what's going on.\"))\n      process.exit(1)\n    }\n\n    // #endregion\n\n    // debug?\n    const debug = boolFlag(options.debug)\n    const log = <T = unknown>(m: T): T => {\n      debug && info(` ${m}`)\n      return m\n    }\n\n    // log raw parameters for debugging\n    log(`ignite command: ${parameters.argv.join(\" \")}`)\n    // #endregion\n\n    // #region Project Name\n    // retrieve project name from toolbox\n    p()\n    const { validateProjectName } = require(\"../tools/validations\") as ValidationsExports\n    const projectName = await validateProjectName(toolbox)\n    const projectNameKebab = kebabCase(projectName)\n    // #endregion\n\n    // New Architecture\n    if (options.newArch !== undefined && boolFlag(options.newArch) === false) {\n      p()\n      p(yellow(`Ignited apps have the new architecture turned on by default.`))\n      p(gray(`If you need to ignite an app with the legacy architecture please use Ignite v10:`))\n      p()\n      p(cyan(`npx ignite-cli@10 new ${projectName} --new-arch=false`))\n      process.exit(1)\n    }\n    // #endregion\n\n    // #region Bundle Identifier\n    const defaultBundleIdentifier = `com.${strings.pascalCase(projectName).toLowerCase()}`\n    let bundleIdentifier = getDefault(options.bundle) ? defaultBundleIdentifier : options.bundle\n\n    if (bundleIdentifier === undefined) {\n      const bundleIdentifierResponse = await prompt.ask(() => ({\n        type: \"input\",\n        name: \"bundleIdentifier\",\n        message: \"What bundle identifier?\",\n        initial: defaultBundleIdentifier,\n        prefix,\n      }))\n\n      bundleIdentifier = bundleIdentifierResponse.bundleIdentifier\n    }\n\n    const { validateBundleIdentifier } = require(\"../tools/validations\") as ValidationsExports\n    validateBundleIdentifier(toolbox, bundleIdentifier)\n\n    // #endregion\n\n    // #region Project Path\n    const defaultTargetPath = path(projectName)\n    let targetPath = getDefault(options.targetPath) ? defaultTargetPath : options.targetPath\n    if (targetPath === undefined) {\n      const targetPathResponse = await prompt.ask(() => ({\n        type: \"input\",\n        name: \"targetPath\",\n        message: \"Where do you want to start your project?\",\n        initial: defaultTargetPath,\n        prefix,\n      }))\n\n      targetPath = targetPathResponse.targetPath\n    }\n\n    const handleHomePrefix = (p: string | undefined) =>\n      p?.startsWith(\"~\") ? p.replace(\"~\", homedir()) : p\n    targetPath = path(handleHomePrefix(targetPath))\n\n    // #endregion\n\n    // #region Prompt Overwrite\n    // if they pass in --overwrite, remove existing directory otherwise throw if exists\n    const defaultOverwrite = false\n    let overwrite = getDefault(options.overwrite) ? defaultOverwrite : boolFlag(options.overwrite)\n\n    if (exists(targetPath) && overwrite === undefined) {\n      const overwriteResponse = await prompt.ask<{ overwrite: boolean }>(() => ({\n        type: \"confirm\",\n        name: \"overwrite\",\n        message: `Directory ${targetPath} already exists. Do you want to overwrite it?`,\n        initial: defaultOverwrite,\n        format: prettyPrompt.format.boolean,\n        prefix,\n      }))\n      overwrite = overwriteResponse.overwrite\n    }\n\n    if (exists(targetPath) && overwrite === false) {\n      const alreadyExists = `Error: There's already a folder at ${targetPath}. To force overwriting that folder, run with --overwrite or say yes.`\n      p()\n      p(yellow(alreadyExists))\n      process.exit(1)\n    }\n    // #endregion\n\n    // #region Prompt for Workflow type - CNG or manual\n    const defaultWorkflow = \"cng\"\n    let workflow = getDefault(options.workflow) ? defaultWorkflow : options.workflow\n    if (workflow === undefined) {\n      const useExpoResponse = await prompt.ask<{ workflow: Workflow }>(() => ({\n        type: \"select\",\n        name: \"workflow\",\n        message: \"How do you want to manage Native code?\",\n        choices: [\n          {\n            name: \"cng\",\n            message: \"Via Expo's Continuous Native Generation - Recommended [Default]\",\n            value: \"cng\",\n          },\n          {\n            name: \"manual\",\n            message: \"Manual - commits android/ios directories\",\n            value: \"manual\",\n          },\n        ],\n        initial: \"cng\",\n        prefix,\n      }))\n      workflow = useExpoResponse.workflow\n    }\n    log(`workflow: ${workflow}`)\n    // #endregion\n\n    // #region Prompt Git Option\n    const defaultGit = true\n    let git = getDefault(options.git) ? defaultGit : boolFlag(options.git)\n\n    if (git === undefined) {\n      const gitResponse = await prompt.ask<{ git: boolean }>(() => ({\n        type: \"confirm\",\n        name: \"git\",\n        message: \"Do you want to initialize a git repository?\",\n        initial: defaultGit,\n        format: prettyPrompt.format.boolean,\n        prefix,\n      }))\n      git = gitResponse.git\n    }\n    // #endregion\n\n    // #region Packager\n    // check if a packager is provided, or detect one\n    // we pass in expo because we can't use pnpm if we're using expo\n\n    const availablePackagers = packager.availablePackagers()\n    log(`availablePackagers: ${availablePackagers}`)\n    const defaultPackagerName = availablePackagers.includes(\"pnpm\") ? \"pnpm\" : \"npm\"\n    let packagerName = getDefault(options.packager) ? defaultPackagerName : options.packager\n\n    const validatePackagerName = (input: unknown): input is PackagerName =>\n      typeof input === \"string\" && [\"npm\", \"yarn\", \"pnpm\", \"bun\"].includes(input)\n\n    if (packagerName !== undefined && validatePackagerName(packagerName) === false) {\n      p()\n      p(\n        yellow(\n          `Error: Invalid packager: \"${packagerName}\". Valid packagers are npm, yarn, pnpm, bun.`,\n        ),\n      )\n      process.exit(1)\n    }\n\n    if (packagerName !== undefined && availablePackagers.includes(packagerName) === false) {\n      p()\n      p(yellow(`Error: selected \"${packagerName}\" but packager was not available on system`))\n      process.exit(1)\n    }\n\n    if (packagerName === undefined) {\n      const initial = availablePackagers.findIndex((p) => p === defaultPackagerName)\n      const NOT_FOUND = -1\n\n      if (initial === NOT_FOUND) {\n        p()\n        p(yellow(`Error: Default packager \"${defaultPackagerName}\" was not available on system`))\n        process.exit(1)\n      }\n\n      const packagerNameResponse = await prompt.ask<{ packagerName: PackagerName }>(() => ({\n        type: \"select\",\n        name: \"packagerName\",\n        message: \"Which package manager do you want to use? (Note: we recommend pnpm)\",\n        choices: availablePackagers,\n        initial,\n        prefix,\n      }))\n      packagerName = packagerNameResponse.packagerName\n    }\n\n    const packagerOptions = { packagerName }\n\n    const isWindows = process.platform === \"win32\"\n    const ignitePath = path(`${meta.src}`, \"..\")\n    const boilerplatePath = path(ignitePath, \"boilerplate\")\n    const boilerplate = (...pathParts: string[]) => path(boilerplatePath, ...pathParts)\n    log(`ignitePath: ${ignitePath}`)\n    log(`boilerplatePath: ${boilerplatePath}`)\n\n    const defaultInstallDeps = true\n    let installDeps = getDefault(options.installDeps)\n      ? defaultInstallDeps\n      : boolFlag(options.installDeps)\n    if (installDeps === undefined) {\n      const installDepsResponse = await prompt.ask<{ installDeps: boolean }>(() => ({\n        type: \"confirm\",\n        name: \"installDeps\",\n        message: \"Do you want to install dependencies?\",\n        initial: defaultInstallDeps,\n        format: prettyPrompt.format.boolean,\n        prefix,\n      }))\n      installDeps = installDepsResponse.installDeps\n    }\n    // #endregion\n\n    // #region Experimental Features parsing\n    let expoVersion\n    let expoRouter\n    const experimentalFlags = options.experimental?.split(\",\") ?? []\n    log(`experimentalFlags: ${experimentalFlags}`)\n    let removeDemo = boolFlag(options.removeDemo)\n\n    experimentalFlags.forEach((flag) => {\n      if (flag.indexOf(\"expo-\") > -1) {\n        if (flag !== \"expo-router\") {\n          expoVersion = flag.substring(5)\n        } else {\n          // user wants to convert to expo-router\n          // force demo code removal for easier conversion\n          // maybe one day convert the demo app\n          expoRouter = true\n\n          if (!removeDemo) {\n            p()\n            p(\n              yellow(\n                `Enabling Expo Router will currently remove the demo application. To continue with the demo app, check out the recipe with full instructions: https://ignitecookbook.com/docs/recipes/ExpoRouter`,\n              ),\n            )\n            p(yellow(`Setting --remove-demo=true`))\n            removeDemo = true\n          }\n        }\n      }\n    })\n    // #endregion\n\n    // #region Prompt to enable experimental features\n\n    // Expo Router\n    const defaultExpoRouter = false\n    let experimentalExpoRouter = getDefault(expoRouter) ? defaultExpoRouter : boolFlag(expoRouter)\n    if (experimentalExpoRouter === undefined) {\n      const expoRouterResponse = await prompt.ask<{ experimentalExpoRouter: boolean }>(() => ({\n        type: \"confirm\",\n        name: \"experimentalExpoRouter\",\n        message:\n          \"[Experimental] Expo Router for navigation? (This will remove the demo application)\",\n        initial: defaultExpoRouter,\n        format: prettyPrompt.format.boolean,\n        prefix,\n      }))\n      experimentalExpoRouter = expoRouterResponse.experimentalExpoRouter\n\n      // update experimental flags if needed for buildCliCommand output\n      if (experimentalExpoRouter && !experimentalFlags.includes(\"expo-router\")) {\n        experimentalFlags.push(\"expo-router\")\n      }\n    }\n\n    // #region Prompt to Remove Demo code\n    const defaultRemoveDemo = experimentalExpoRouter\n    if (defaultRemoveDemo) {\n      p(yellow(`Warning: the demo application will be removed.`))\n    }\n    removeDemo = getDefault(options.removeDemo) ? defaultRemoveDemo : boolFlag(options.removeDemo)\n\n    if (!defaultRemoveDemo && removeDemo === undefined) {\n      const removeDemoResponse = await prompt.ask<{ removeDemo: boolean }>(() => ({\n        type: \"confirm\",\n        name: \"removeDemo\",\n        message:\n          \"Remove demo code? We recommend leaving it in if it's your first time using Ignite\",\n        initial: defaultRemoveDemo,\n        format: prettyPrompt.format.boolean,\n        prefix,\n      }))\n      removeDemo = removeDemoResponse.removeDemo\n    } else {\n      removeDemo = defaultRemoveDemo\n    }\n    // #endregion\n\n    // #region Debug\n    // start tracking performance\n    const perfStart = new Date().getTime()\n\n    // add a timeout to make sure we don't hang on any errors\n    const timeoutHandle = !noTimeout && setTimeout(timeoutExit, MAX_APP_CREATION_TIME)\n\n    // #region Print Welcome\n    // welcome everybody!\n    try {\n      const terminalWidth = process.stdout.columns ?? 80\n      const logo =\n        terminalWidth > 80 ? () => ascii(\"logo.ascii.txt\") : () => ascii(\"logo-sm.ascii.txt\")\n      p()\n      p()\n      p()\n      p()\n      logo()\n      p()\n      p()\n\n      const pkg = pkgColor(packagerName)\n      const igniteVersion = meta.version()\n      p(` █ Creating ${em(projectName)} using ${em(`Ignite ${igniteVersion}`)}`)\n      p(` █ Powered by ${ir(\" ∞ Infinite Red \")} (${link(\"https://infinite.red\")})`)\n      p(` █ Package Manager: ${pkg(print.colors.bold(packagerName))}`)\n      p(` █ Bundle identifier: ${em(bundleIdentifier)}`)\n      p(` █ Path: ${underline(targetPath)}`)\n      hr()\n      p()\n      // #endregion\n\n      // #region Validate Project Path\n      const { validateProjectPath } = require(\"../tools/validations\") as ValidationsExports\n      validateProjectPath(targetPath, toolbox)\n      // #endregion\n\n      // #region Overwrite\n      if (exists(targetPath) === \"dir\" && overwrite === true) {\n        const msg = ` Tossing that old app like it's hot`\n        startSpinner(msg)\n        await removeAsync(targetPath)\n        stopSpinner(msg, \"🗑️\")\n      }\n      // #endregion\n\n      // #region Copy Boilerplate Files\n      startSpinner(\" 3D-printing a new React Native app\")\n      await copyBoilerplate(toolbox, {\n        boilerplatePath,\n        targetPath,\n        excluded: [\n          \".vscode\",\n          \"node_modules\",\n          \"yarn.lock\",\n          \"bun.lockb\",\n          \"bun.lock\",\n          \"package-lock.json\",\n        ],\n        overwrite,\n      })\n      stopSpinner(\" 3D-printing a new React Native app\", \"🖨\")\n      // copy the .gitignore if it wasn't copied over\n      // Release Ignite installs have the boilerplate's .gitignore in .gitignore.template\n      // (see https://github.com/npm/npm/issues/3763); development Ignite still\n      // has it in .gitignore. Copy it from one or the other.\n      const boilerplateIgnorePath = exists(boilerplate(\".gitignore.template\"))\n        ? boilerplate(\".gitignore.template\")\n        : boilerplate(\".gitignore\")\n      const targetIgnorePath = log(path(targetPath, \".gitignore\"))\n      copy(log(boilerplateIgnorePath), targetIgnorePath, { overwrite: true })\n\n      // adjust the README.md with proper packager run commands\n      const readmePath = path(targetPath, \"README.md\")\n      updatePackagerCommandsInReadme(readmePath, packagerName)\n\n      if (exists(targetIgnorePath) === false) {\n        warning(`  Unable to copy ${boilerplateIgnorePath} to ${targetIgnorePath}`)\n      } else if (workflow === \"manual\") {\n        // if we're using the manual workflow, we need to remove the android and ios lines from the gitignore\n        let gitIgnoreContents = read(targetIgnorePath)\n        gitIgnoreContents = gitIgnoreContents.replace(\"/android\", \"\").replace(\"/ios\", \"\")\n\n        write(targetIgnorePath, gitIgnoreContents)\n      }\n\n      // note the original directory\n      const cwd = log(process.cwd())\n\n      // jump into the project to do additional tasks\n      process.chdir(targetPath)\n      // #endregion\n\n      // #region Handle package.json\n      // Update package.json:\n      // - Replacing app name: We do it on the unparsed file content\n      //   since that's easier than updating individual values\n      //   in the parsed structure, then we parse that as JSON.\n      let packageJsonRaw = read(\"package.json\")\n      packageJsonRaw = packageJsonRaw\n        .replace(/HelloWorld/g, projectName)\n        .replace(/hello-world/g, projectNameKebab)\n      const packageJsonParsed = JSON.parse(packageJsonRaw)\n\n      // add in expo-router package\n      if (experimentalExpoRouter) {\n        // find \"expo-localization\" line and append \"expo-router\" line after it\n        packageJsonRaw = packageJsonRaw.replace(\n          /\"expo-localization\": \".*\",/g,\n          `\"expo-localization\": \"${packageJsonParsed.dependencies[\"expo-localization\"]}\",${EOL}    \"expo-router\":  \"~55.0.4\",`,\n        )\n\n        // replace \"main\" entry point from App.js to \"expo-router/entry\"\n        packageJsonRaw = packageJsonRaw.replace(/\"main\": \".*\",/g, `\"main\": \"expo-router/entry\",`)\n      }\n\n      // If we need native dirs, change up start scripts from Expo Go variation to expo run:platform.\n      packageJsonRaw = packageJsonRaw\n        .replace(/start --android/g, \"run:android\")\n        .replace(/start --ios/g, \"run:ios\")\n\n      // If using canary build, update the expo dependency to the canary version\n      if (expoVersion) {\n        const expoDistTagOutput = await system.run(\"npm view expo dist-tags --json\")\n        // filter for canary/beta and get last item in array\n        const tagVersion = JSON.parse(expoDistTagOutput)[expoVersion]\n        log(`overriding expo version to: ${tagVersion}`)\n        // find line with \"expo\": and replace entire line with tagVersion\n        packageJsonRaw = packageJsonRaw.replace(/\"expo\": \".*\"/g, `\"expo\": \"${tagVersion}\"`)\n      }\n\n      // If we're removing the demo code, clean up some dependencies that are no longer needed\n      if (removeDemo) {\n        log(`Removing demo dependencies... ${demoDependenciesToRemove.join(\", \")}`)\n        packageJsonRaw = findAndRemoveDependencies(packageJsonRaw, demoDependenciesToRemove)\n        const patchesToRemove = findDemoPatches()\n        log(`Removing demo patches... ${patchesToRemove}`)\n        patchesToRemove.forEach((patch) => filesystem.cwd(\"./patches\").remove(patch))\n      }\n\n      // Then write it back out.\n      const packageJson = JSON.parse(packageJsonRaw)\n      write(\"./package.json\", packageJson)\n      // #endregion\n\n      // #region Run Packager Install\n      // pnpm/yarn/npm/bun install it\n\n      // fix .npmrc if using pnpm\n      let installFlags = \"\"\n      if (packagerName === \"pnpm\") {\n        // append `node-linker=hoisted` to .npmrc\n        const npmrcPath = path(targetPath, \".npmrc\")\n        const npmrcContents = read(npmrcPath)\n        write(npmrcPath, `${npmrcContents}${EOL}node-linker=hoisted${EOL}`)\n      } else if (packagerName === \"yarn\") {\n        const yarnVersion = await packager.run(\"-v\", { packagerName })\n        const yarnMajorVersion = parseInt(yarnVersion.split(\".\")[0], 10)\n\n        // if yarn version > 1 fix .yarnrc.yml\n        if (yarnMajorVersion > 1) {\n          if (process.env.CI === \"true\") {\n            installFlags = \" --no-immutable\"\n          }\n          log(`yarn v${yarnMajorVersion} found... fixing .yarnrc.yml...`)\n          // append `nodeLinker: node-modules` to .yarnrc.yml\n          const yarnrcPath = path(targetPath, \".yarnrc.yml\")\n          const yarnrcContents = read(yarnrcPath)\n          write(yarnrcPath, `${yarnrcContents ?? \"\"}${EOL}nodeLinker: node-modules${EOL}`)\n          // also create a blank yarn.lock file to avoid workspaces issue\n          write(path(targetPath, \"yarn.lock\"), \"\")\n          // update the `packagerManager` field in `package.json\n          await system.run(`yarn set version ${yarnVersion}`, { onProgress: log })\n        } else {\n          warning(\n            `We do not recommend using yarn v1 due to security and performance reasons. \\nIf you do use yarn, we recommend using yarn v4 and making sure enableScripts is set to false in your .yarnrc.yml file.`,\n          )\n        }\n      }\n\n      // check if there is a dependency cache using a hash of the package.json\n      const boilerplatePackageJsonHash = cache.hash(read(path(boilerplatePath, \"package.json\")))\n      const cachePath = path(cache.rootdir(), boilerplatePackageJsonHash, packagerName)\n      const cacheExists = exists(cachePath) === \"dir\"\n\n      log(`${!cacheExists ? \"expected \" : \"\"}cachePath: ${cachePath}`)\n      log(`cacheExists: ${cacheExists}`)\n\n      // use-cache defaults to `false` for now; if we make it robust enough,\n      // we can enable it by default in the future.\n      const defaultUseCache = false\n      const useCache = options.useCache === undefined ? defaultUseCache : boolFlag(options.useCache)\n\n      const shouldUseCache = installDeps && cacheExists && useCache\n      if (shouldUseCache) {\n        const msg = `Grabbing those ${packagerName} dependencies from the back`\n        startSpinner(msg)\n        await cache.copy({\n          fromRootDir: cachePath,\n          toRootDir: targetPath,\n          packagerName,\n        })\n        stopSpinner(msg, \"📦\")\n      }\n\n      // #region Rename App\n      // rename the app using Ignite\n      const renameSpinnerMsg = `Getting those last few details perfect`\n      startSpinner(renameSpinnerMsg)\n\n      const boilerplateBundleIdentifier = \"com.helloworld\"\n      await renameReactNativeApp(\n        toolbox,\n        \"HelloWorld\",\n        projectName,\n        boilerplateBundleIdentifier,\n        bundleIdentifier,\n      )\n\n      await replaceMaestroBundleIds(toolbox, boilerplateBundleIdentifier, bundleIdentifier)\n\n      stopSpinner(renameSpinnerMsg, \"🎨\")\n      // #endregion\n\n      const shouldFreshInstallDeps = installDeps && shouldUseCache === false\n      if (shouldFreshInstallDeps) {\n        const unboxingMessage = `Installing ${packagerName} dependencies (wow these are heavy)`\n        startSpinner(unboxingMessage)\n\n        // do base install\n        const installCmd = packager.installCmd({ packagerName })\n        await system.run(`${installCmd}${installFlags}`, { onProgress: log })\n        // now that expo is installed, we can run their install --fix for best Expo SDK compatibility\n        // for right now, we don't do this in CI because it returns a non-zero exit code\n        // see https://docs.expo.dev/more/expo-cli/#version-validation\n        if (process.env.CI !== \"true\") {\n          log(\"Running `npx expo install --fix...`\")\n          await system.run(`npx expo install --fix`, { onProgress: log })\n        }\n\n        stopSpinner(unboxingMessage, \"🧶\")\n      }\n\n      // remove the gitignore template\n      await removeAsync(\".gitignore.template\")\n      // #endregion\n\n      // #region Cache dependencies\n      if (shouldFreshInstallDeps && cacheExists === false && useCache) {\n        const msg = `Saving ${packagerName} dependencies for next time`\n        startSpinner(msg)\n        log(targetPath)\n        await cache.copy({\n          fromRootDir: targetPath,\n          toRootDir: cachePath,\n          packagerName,\n        })\n        stopSpinner(msg, \"📦\")\n      }\n      // #endregion\n\n      // #region Configure app.json\n      // Enable New Architecture if requested (must happen before prebuild)\n      startSpinner(\" Configuring app.json\")\n      try {\n        const appJsonRaw = read(\"app.json\")\n        const appJson = JSON.parse(appJsonRaw)\n\n        // Inject ignite version to app.json\n        appJson.extra.ignite.version = igniteVersion\n\n        if (experimentalExpoRouter) {\n          appJson.experiments.typedRoutes = true\n          appJson.plugins.push(\"expo-router\")\n        }\n\n        write(\"./app.json\", appJson)\n      } catch (e) {\n        log(e)\n        p(yellow(\"Unable to configure app.json.\"))\n      }\n      stopSpinner(\" Configuring app.json\", \"⚙️\")\n      // #endregion\n\n      // #region Run Prebuild\n      // we can't run this option if we didn't install deps\n      if (installDeps === true) {\n        // Check if we need to run prebuild to generate native dirs based on workflow\n        // Prebuild also handles the packager install\n        const prebuildMessage = ` Generating native template via Expo Prebuild`\n        startSpinner(prebuildMessage)\n        await packager.run(\"prebuild:clean\", { ...packagerOptions, onProgress: log })\n        stopSpinner(prebuildMessage, \"🛠️\")\n      }\n      // #endregion\n\n      // #region Remove Demo code\n      const removeDemoPart = removeDemo === true ? \"code\" : \"markup\"\n      startSpinner(` Removing fancy demo ${removeDemoPart}`)\n      try {\n        const IGNITE = \"node \" + filesystem.path(__dirname, \"..\", \"..\", \"bin\", \"ignite\")\n        const CMD = removeDemo === true ? \"remove-demo\" : \"remove-demo-markup\"\n\n        log(`Ignite bin path: ${IGNITE}`)\n        await system.run(`${IGNITE} ${CMD} \"${targetPath}\"`, { onProgress: log })\n      } catch (e) {\n        log(e)\n        p(yellow(`Unable to remove demo ${removeDemoPart}.`))\n      }\n      stopSpinner(` Removing fancy demo ${removeDemoPart}`, \"🛠️\")\n      // #endregion\n\n      // #region Expo Router edits\n      if (experimentalExpoRouter) {\n        const expoRouterMsg = \" Recalibrating compass with Expo Router\"\n        startSpinner(expoRouterMsg)\n\n        /**\n         * Instructions mostly adapted from https://ignitecookbook.com/docs/recipes/ExpoRouter\n         * 1. Move all files from app/ to src/\n         * 2. Update code refs to app/ with src/\n         * 3. Refactor Reactotron commands to use `router` instead of refs to react navigation\n         * 4. Create screen and route generator templates that makes sense for Expo Router\n         * 5. Clean up - move ErrorBoundary to proper spot and remove unused files\n         */\n        filesystem\n          .cwd(targetPath)\n          .find(\"app\")\n          .forEach((file) => filesystem.cwd(targetPath).move(file, file.replace(\"app\", \"src\")))\n\n        updateExpoRouterSrcDir(toolbox)\n        updateExpoRouterPackageJson(toolbox)\n        refactorExpoRouterReactotronCmds(toolbox)\n        const screenTplPath = filesystem.path(\n          targetPath,\n          \"ignite/templates/screen/NAMEScreen.tsx.ejs\",\n        )\n        const routerTplPath = filesystem.path(targetPath, \"ignite/templates/route/NAME.tsx.ejs\")\n        createGeneratorTemplate(toolbox, screenTplPath, EXPO_ROUTER_SCREEN_TEMPLATE)\n        createGeneratorTemplate(toolbox, routerTplPath, EXPO_ROUTER_ROUTE_TEMPLATE)\n        cleanupExpoRouterConversion(toolbox, targetPath)\n\n        stopSpinner(expoRouterMsg, \"🧭\")\n      } else {\n        // remove src/ dir since not using expo-router\n        filesystem.cwd(targetPath).remove(\"src\")\n      }\n      // #endregion\n\n      // #region Run Format\n      const formattingMessage = `Cleaning up`\n      startSpinner(formattingMessage)\n      if (installDeps === true) {\n        // Make sure all our modifications are formatted nicely\n        await packager.run(\"lint\", { ...packagerOptions })\n      } else {\n        // if our linting configuration is not installed, try format\n        // using prettier to make sure it's reasonably close, but this will skip\n        // eslint issues\n        await system.run(`npx prettier@3.3.3 --write .`, {\n          trim: true,\n          cwd: targetPath,\n        })\n      }\n      stopSpinner(formattingMessage, \"🧽\")\n      // #endregion\n\n      // #region Format generator templates EOL for Windows\n      let warnAboutEOL = false\n      if (isWindows) {\n        try {\n          const templates = filesystem.find(`${targetPath}/ignite/templates`, {\n            directories: false,\n            files: true,\n            matching: \"*.ejs\",\n          })\n\n          log(`templates to change EOL: ${templates}`)\n          templates.map(async (file) => {\n            log(`Converting EOL for ${file}`)\n            let template = read(file)\n            template = template.replace(/\\n/g, \"\\r\\n\")\n            write(file, template)\n          })\n        } catch {\n          warnAboutEOL = true\n        }\n      }\n      // #endregion\n\n      // #region Create Git Repository and Initial Commit\n      // commit any changes\n      if (git === true) {\n        startSpinner(\" Backing everything up in source control\")\n        try {\n          // The separate commands works on Windows, but not Mac OS\n          if (isWindows) {\n            await system.run(log(\"git init\"))\n            await system.run(log(\"git add -A\"))\n            await system.run(log(`git commit -m \"New Ignite ${meta.version()} app`))\n          } else {\n            await system.run(\n              log(`\n              \\\\rm -rf ./.git\n              git init;\n              git add -A;\n              git commit -m \"New Ignite ${meta.version()} app\";\n            `),\n            )\n          }\n        } catch (e) {\n          p(\n            yellow(\n              \"Unable to commit the initial changes. Please check your git username and email.\",\n            ),\n          )\n        }\n        stopSpinner(\" Backing everything up in source control\", \"🗄\")\n      }\n\n      // back to the original directory\n      process.chdir(log(cwd))\n      // #endregion\n\n      // #region Print Finish\n      // clean up any spinners we forgot to clear\n      p()\n      hr()\n      p()\n      clearSpinners()\n\n      // we're done! round performance stats to .xx digits\n      const perfDuration = Math.round((new Date().getTime() - perfStart) / 10) / 100\n\n      // no need to timeout, we're done!\n      if (timeoutHandle) clearTimeout(timeoutHandle)\n\n      p2(`Ignited ${em(`${projectName}`)} in ${gray(`${perfDuration}s`)}  🚀 `)\n      p2()\n      const cliCommand = buildCliCommand({\n        flags: {\n          bundle: bundleIdentifier,\n          debug,\n          git,\n          installDeps,\n          overwrite,\n          packager: packagerName,\n          targetPath,\n          removeDemo,\n          experimental: experimentalFlags.length > 0 ? experimentalFlags.join(\",\") : undefined,\n          workflow,\n          useCache,\n          y: yname,\n          yes: yname,\n          noTimeout,\n        },\n        projectName,\n        toolbox,\n      })\n\n      p2(`For next time, here are the Ignite options you picked:`)\n\n      // create a multi-line string of the command, where each --flag is on it's own line\n      const prettyCliCommand = cliCommand\n        .split(\" \")\n        .map((c) => (c === projectName || c?.startsWith(\"--\") ? `${c} \\\\${EOL}` : c)) // add a line break after the project name and each flag\n        .map((c, i, a) => (i === a.length - 1 ? c.replace(`\\\\${EOL}`, \"\") : c)) // remove the line break after the last flag\n        .map((c) => (c.startsWith(\"--\") ? INDENT + CMD_INDENT + CMD_INDENT + c : c)) // add whitespace to the flags so it looks nice\n        .join(\" \")\n\n      command(`${prettyCliCommand}`)\n      p2()\n\n      if (!isAndroidInstalled(toolbox)) {\n        hr()\n        p2()\n        p2(\"To run in Android, make sure you've followed the latest\")\n        p2(`react-native setup instructions. You reference them at:`)\n        p2(`${link(\"https://reactnative.dev/docs/environment-setup\")}`)\n        p2()\n      }\n\n      if (warnAboutEOL) {\n        hr()\n        p2()\n        p2(yellow(`Generator templates could not be converted to Windows EOL.`))\n        p2(yellow(`You may want to update these manually with your code editor, more info at:`))\n        p2(`${link(\"https://docs.infinite.red/ignite-cli/concept/Generators/\")}`)\n        p2()\n      }\n\n      hr()\n      p2()\n      p2(\"Need additional help?\")\n      p2()\n      p2(`Join our Slack community at ${link(\"http://community.infinite.red.\")}`)\n      p2()\n\n      hr()\n      p2()\n      p2(\"Now get cooking! 🍽\")\n      command(`cd ${targetPath}`)\n      if (!installDeps) command(packager.installCmd({ packagerName }))\n\n      const isMac = process.platform === \"darwin\"\n      if (isMac) {\n        command(`${packager.runCmd(\"ios\", packagerOptions)}`)\n        p2(\"Or Android via\")\n        command(`${packager.runCmd(\"android\", packagerOptions)}`)\n      } else {\n        command(`${packager.runCmd(\"android\", packagerOptions)}`)\n      }\n      p2()\n      p2()\n      // #endregion\n\n      // this is a hack to prevent the process from hanging\n      // if there are any tasks left in the event loop\n      // like I/O operations to process.stdout and process.stderr\n      // see https://github.com/infinitered/ignite/issues/2084\n      process.exit(0)\n    } catch (e) {\n      stopLastSpinner(\"❌\")\n      p2(red(`\\nThe following error occurred:`))\n      p2()\n      p2(red(e.toString()))\n\n      p2()\n      p2(\"Consider opening an issue with the following information at:\")\n      p2(\n        `${link(\n          \"https://github.com/infinitered/ignite/issues/new?template=bug_report.yml&labels=bug\",\n        )}`,\n      )\n      p2()\n\n      startSpinner(\" Gathering system and project details\")\n      try {\n        const IGNITE = \"node \" + filesystem.path(__dirname, \"..\", \"..\", \"bin\", \"ignite\")\n        const doctorResults = await system.run(`${IGNITE} doctor`)\n        p(`\\n\\n${doctorResults}`)\n      } catch (e) {\n        p(yellow(\"Unable to gather system and project details.\"))\n      }\n      clearSpinners()\n      process.exit(1)\n    }\n  },\n}\n\nfunction buildCliCommand(args: {\n  flags: Required<Omit<Options, \"newArch\" | \"timeout\">>\n  toolbox: GluegunToolbox\n  projectName: string\n}): string {\n  const { flags, toolbox, projectName } = args\n  const { strings } = toolbox\n  const { kebabCase } = strings\n\n  type Flag = keyof typeof flags\n  type FlagEntry = [key: Flag, value: Options[Flag]]\n\n  const privateFlags: Flag[] = [\"debug\", \"useCache\", \"y\", \"yes\"]\n\n  const stringFlag = ([key, value]: FlagEntry) => `--${kebabCase(key)}=${value}`\n  const booleanFlag = ([key, value]: FlagEntry) =>\n    value ? `--${kebabCase(key)}` : `--${kebabCase(key)}=${value}`\n\n  const cliCommand = `npx ignite-cli new ${projectName} ${(Object.entries(flags) as FlagEntry[])\n    .filter(([key]) => privateFlags.includes(key) === false)\n    .filter(([, value]) => value !== undefined)\n    .map(([key, value]) =>\n      typeof value === \"boolean\" ? booleanFlag([key, value]) : stringFlag([key, value]),\n    )\n    .join(\" \")}`\n\n  return cliCommand\n}\n\n// This function takes a package.json file as a string and removes the dependencies\n// supplied in dependenciesToRemove and returns the updated package.json as a string.\nexport function findAndRemoveDependencies(\n  packageJsonRaw: string,\n  dependenciesToRemove: string[],\n): string {\n  let updatedPackageJson = packageJsonRaw\n\n  dependenciesToRemove.forEach((depName) => {\n    const regex = new RegExp(`\"${depName}\"\\\\s*:\\\\s*\"[^\"]+\",?`, \"g\")\n    updatedPackageJson = updatedPackageJson.replace(regex, \"\")\n  })\n\n  return updatedPackageJson\n}\n"
  },
  {
    "path": "src/commands/remove-demo-markup.ts",
    "content": "import { GluegunToolbox } from \"gluegun\"\n\nimport { DEMO_MARKUP_PREFIX } from \"../tools/demo\"\nimport { boolFlag } from \"../tools/flag\"\nimport { findFiles, updateFiles } from \"../tools/markup\"\nimport { p, warning } from \"../tools/pretty\"\n\nmodule.exports = {\n  alias: [\"rdm\"],\n  description:\n    \"Remove all demo markup from generated boilerplate. Add --dry-run to see what would be removed.\",\n  run: async (toolbox: GluegunToolbox) => {\n    const { parameters } = toolbox\n\n    const CWD = process.cwd()\n    const TARGET_DIR = parameters.first ?? CWD\n    const dryRun = boolFlag(parameters.options.dryRun) ?? false\n\n    p()\n    p(`Removing demo markup from '${TARGET_DIR}'${dryRun ? \" (dry run)\" : \"\"}`)\n\n    const filePaths = findFiles(TARGET_DIR)\n\n    // Go through every file path and handle the operation for each demo comment\n    const demoCommentResults = await updateFiles({\n      filePaths,\n      markupPrefix: DEMO_MARKUP_PREFIX,\n      dryRun,\n      removeMarkupOnly: true,\n    })\n\n    // Handle the results of the demo comment operations\n    demoCommentResults\n      // Sort the results by the path in alphabetical order\n      .sort((a, b) => {\n        if (a.status === \"fulfilled\" && b.status === \"fulfilled\") {\n          return a.value.path.localeCompare(b.value.path)\n        }\n        return 0\n      })\n      .forEach((result) => {\n        // Log any rejected results as warnings\n        if (result.status === \"rejected\") {\n          warning(result.reason)\n          return\n        }\n\n        // Log any fulfilled results that have comments\n        const { path, comments } = result.value\n        if (comments.length > 0) {\n          p(`Found ${comments.map((c) => `'${c}'`).join(\", \")} in ${path}`)\n        }\n      })\n\n    p(`Done removing demo markup from '${TARGET_DIR}'${dryRun ? \" (dry run)\" : \"\"}`)\n  },\n}\n"
  },
  {
    "path": "src/commands/remove-demo.ts",
    "content": "import { GluegunToolbox } from \"gluegun\"\nimport * as pathlib from \"path\"\n\nimport { DEMO_MARKUP_PREFIX } from \"../tools/demo\"\nimport { boolFlag } from \"../tools/flag\"\nimport { findFiles, removeEmptyDirs, updateFiles } from \"../tools/markup\"\nimport { p, warning } from \"../tools/pretty\"\n\nconst MATCHING_GLOBS = [\n  \"!**/.yarn{,/**}\",\n  \"!**/.DS_Store\",\n  \"!**/.expo{,/**}\",\n  \"!**/.git{,/**}\",\n  \"!**/.vscode{,/**}\",\n  \"!**/node_modules{,/**}\",\n  \"!**/ios/build{,/**}\",\n  \"!**/ios/Pods{,/**}\",\n  \"!**/ios/*.xcworkspace{,/**}\",\n  \"!**/ios/*.xcodeproj{,/**}\",\n  \"!**/android/build{,/**}\",\n  \"!**/android/app/build{,/**}\",\n  \"!**/android/.gradle\",\n]\n\nmodule.exports = {\n  alias: [\"rd\", \"remove-demos\"],\n  description:\n    \"Remove demo code from generated boilerplate. Add --dry-run to see what would be removed.\",\n  run: async (toolbox: GluegunToolbox) => {\n    const { parameters, filesystem } = toolbox\n\n    const CWD = process.cwd()\n    const TARGET_DIR = parameters.first ?? CWD\n    const dryRun = boolFlag(parameters.options.dryRun) ?? false\n\n    p()\n    p(`Removing demo code from '${TARGET_DIR}'${dryRun ? \" (dry run)\" : \"\"}`)\n\n    const filePaths = findFiles(TARGET_DIR)\n\n    // Go through every file path and handle the operation for each demo comment\n    const demoCommentResults = await updateFiles({\n      filePaths,\n      markupPrefix: DEMO_MARKUP_PREFIX,\n      dryRun,\n    })\n\n    // Handle the results of the demo comment operations\n    demoCommentResults\n      // Sort the results by the path in alphabetical order\n      .sort((a, b) => {\n        if (a.status === \"fulfilled\" && b.status === \"fulfilled\") {\n          return a.value.path.localeCompare(b.value.path)\n        }\n        return 0\n      })\n      .forEach((result) => {\n        // Log any rejected results as warnings\n        if (result.status === \"rejected\") {\n          warning(result.reason)\n          return\n        }\n\n        // Log any fulfilled results that have comments\n        const { path, comments } = result.value\n        if (comments.length > 0) {\n          p(`Found ${comments.map((c) => `'${c}'`).join(\", \")} in ${path}`)\n        }\n      })\n\n    function removeDemoAssets() {\n      const demoPaths = filesystem\n        .cwd(TARGET_DIR)\n        .find({\n          matching: [...MATCHING_GLOBS, \"**/demo\"],\n          recursive: true,\n          files: false,\n          directories: true,\n        })\n        .map((path) => pathlib.join(TARGET_DIR, path))\n      demoPaths.forEach((path) => {\n        if (!dryRun) filesystem.remove(path)\n        p(`Removed demo directory '${path}'`)\n      })\n    }\n\n    // first pass\n    const emptyDirsRemoved = removeEmptyDirs({ targetDir: TARGET_DIR, dryRun })\n    emptyDirsRemoved.forEach((path) => {\n      p(`Removed empty directory '${path}'`)\n    })\n\n    removeDemoAssets()\n\n    p(`Done removing demo code from '${TARGET_DIR}'${dryRun ? \" (dry run)\" : \"\"}`)\n  },\n}\n"
  },
  {
    "path": "src/commands/rename.ts",
    "content": "import { GluegunToolbox } from \"gluegun\"\n\nimport { direction, heading, p, warning } from \"../tools/pretty\"\nimport { renameReactNativeApp } from \"../tools/react-native\"\n\nmodule.exports = {\n  alias: [\"rn\"],\n  description: \"Renames a React Native and/or Ignite app\",\n  run: async (toolbox: GluegunToolbox) => {\n    const { parameters, prompt, filesystem, print } = toolbox\n    const { colors, info } = print\n    const { red, green } = colors\n\n    let newName = parameters.first\n    let newBundleIdentifier = parameters.options.bundle\n\n    const appJsonPath = `${process.cwd()}/app.json`\n    // first check if we are in the folder with the app.json file\n    if (!filesystem.exists(appJsonPath)) {\n      warning(\"You must be in the root of a React Native project to rename it.\")\n      warning(\"(We look for an app.json file to verify this.)\")\n      return\n    }\n\n    // next, get the current name\n    const appJson = require(appJsonPath)\n    const oldName = appJson.name\n    if (!oldName) {\n      warning(\"Couldn't find the current name in app.json.\")\n      return\n    }\n\n    // then, get the package name from Android or ios if it DNE\n    // we do this via app.json now as android/ios dirs may not exist to find AndroidManifest/gradle files\n    // in the case of a managed or custom workflow setup, so ignite v9+ compatible\n    const oldBundleIdentifier = appJson.android.package ?? appJson.ios.bundleIdentifier\n    if (!oldBundleIdentifier) {\n      warning(\"Couldn't find the current name in app.json.\")\n      return\n    }\n\n    // name and bundle validations\n    // check the name\n    if (!newName) {\n      // ask for a name\n      const result = await prompt.ask({\n        type: \"input\",\n        name: \"newName\",\n        message: `What would you like to rename your app to? Currently: ${oldName}`,\n      })\n      newName = result.newName\n    }\n\n    if (!newName) {\n      // no name, no go\n      warning(\"No name provided, nothing to do.\")\n      return\n    }\n\n    // check the package name\n    if (!newBundleIdentifier) {\n      // ask for a name\n      const result = await prompt.ask({\n        type: \"input\",\n        name: \"newBundleIdentifier\",\n        message: `What would you like to rename your package to? Currently: ${oldBundleIdentifier}`,\n      })\n      newBundleIdentifier = result.newBundleIdentifier\n    }\n\n    if (!newBundleIdentifier) {\n      // no name, no go\n      warning(\"No package name provided, nothing to do.\")\n      return\n    }\n\n    // rename the app\n\n    if (oldName === newName) {\n      warning(\"The current name and the new name are the same.\")\n      return\n    }\n\n    await renameReactNativeApp(toolbox, oldName, newName, oldBundleIdentifier, newBundleIdentifier)\n\n    heading(`Ignite successfully renamed your app from ${red(oldName)} to ${green(newName)}!`)\n    p()\n    heading(`Caveats:`)\n    p()\n    info(`    * Ignite's rename feature is not 100% perfect in all cases.`)\n    info(`      Carefully examine the diff it produces with \\`git diff\\` before committing.`)\n    info(`    * Additionally, you'll want to re-run \\`pod install\\` and`)\n    info(`      rebuild your app and caches. We don't make any changes to those files or folders.`)\n    p()\n    direction(`Next: run \\`git diff\\` to see the changes we made.`)\n    direction(`To reset everything: run \\`git reset --hard && git clean -fd\\``)\n    warning(`(${red(\"careful\")}: this will remove all other changes you haven't committed!)`)\n  },\n}\n"
  },
  {
    "path": "src/commands/update.ts",
    "content": "import { GluegunToolbox } from \"gluegun\"\n\nimport { showGeneratorHelp, updateGenerators } from \"../tools/generators\"\nimport { p, warning } from \"../tools/pretty\"\n\nmodule.exports = {\n  description: \"Update generator templates\",\n  run: async (toolbox: GluegunToolbox) => {\n    const { parameters } = toolbox\n    p()\n    if (parameters.options.all || parameters.first) {\n      updateGenerators(toolbox)\n    } else {\n      warning(`⚠️  Update what?`)\n      p()\n      showGeneratorHelp(toolbox)\n    }\n  },\n}\n"
  },
  {
    "path": "src/tools/__snapshots__/markup.test.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`markup remove should remove all comments in AppNavigator 1`] = `\n\"\n/**\n * The app navigator (formerly \"AppNavigator\" and \"MainNavigator\") is used for the primary\n * navigation flows of your app.\n * Generally speaking, it will contain an auth flow (registration, login, forgot password)\n * and a \"main\" flow which the user will use once logged in.\n */\nimport { ComponentProps } from \"react\"\nimport {\n  NavigationContainer,\n} from \"@react-navigation/native\"\nimport { createNativeStackNavigator, NativeStackScreenProps } from \"@react-navigation/native-stack\"\n\nimport Config from \"@/config\"\nimport { ErrorBoundary } from \"@/screens/ErrorScreen/ErrorBoundary\"\nimport { WelcomeScreen } from \"@/screens/WelcomeScreen\"\nimport { useAppTheme } from \"@/theme/context\"\n\nimport { navigationRef, useBackButtonHandler } from \"./navigationUtilities\"\n\n/**\n * This type allows TypeScript to know what routes are defined in this navigator\n * as well as what properties (if any) they might take when navigating to them.\n *\n * For more information, see this documentation:\n *   https://reactnavigation.org/docs/params/\n *   https://reactnavigation.org/docs/typescript#type-checking-the-navigator\n *   https://reactnavigation.org/docs/typescript/#organizing-types\n */\nexport type AppStackParamList = {\n  Welcome: undefined\n  // 🔥 Your screens go here\n  // IGNITE_GENERATOR_ANCHOR_APP_STACK_PARAM_LIST\n}\n\n/**\n * This is a list of all the route names that will exit the app if the back button\n * is pressed while in that screen. Only affects Android.\n */\nconst exitRoutes = Config.exitRoutes\n\nexport type AppStackScreenProps<T extends keyof AppStackParamList> = NativeStackScreenProps<\n  AppStackParamList,\n  T\n>\n\n// Documentation: https://reactnavigation.org/docs/stack-navigator/\nconst Stack = createNativeStackNavigator<AppStackParamList>()\n\nconst AppStack = () => {\n  const {\n    theme: { colors },\n  } = useAppTheme()\n\n  return (\n    <Stack.Navigator\n      screenOptions={{\n        headerShown: false,\n        navigationBarColor: colors.background,\n        contentStyle: {\n          backgroundColor: colors.background,\n        },\n      }}\n    >\n          <Stack.Screen name=\"Welcome\" component={WelcomeScreen} />\n      {/** 🔥 Your screens go here */}\n      {/* IGNITE_GENERATOR_ANCHOR_APP_STACK_SCREENS */}\n    </Stack.Navigator>\n  )\n}\n\nexport interface NavigationProps\n  extends Partial<ComponentProps<typeof NavigationContainer<AppStackParamList>>> {}\n\nexport const AppNavigator = (props: NavigationProps) => {\n  const { navigationTheme } = useAppTheme()\n\n  useBackButtonHandler((routeName) => exitRoutes.includes(routeName))\n\n  return (\n    <NavigationContainer ref={navigationRef} theme={navigationTheme} {...props}>\n      <ErrorBoundary catchErrors={Config.catchErrors}>\n        <AppStack />\n      </ErrorBoundary>\n    </NavigationContainer>\n  )\n}\n\"\n`;\n\nexports[`markup remove should remove all comments in WelcomeScreen 1`] = `\n\"\nimport { FC } from \"react\"\nimport { Image, ImageStyle, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Screen } from \"@/components/Screen\"\nimport { Text } from \"@/components/Text\"\nimport { isRTL } from \"@/i18n\"\nimport type { AppStackScreenProps } from \"@/navigators/AppNavigator\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\nimport { useSafeAreaInsetsStyle } from \"@/utils/useSafeAreaInsetsStyle\"\n\nconst welcomeLogo = require(\"@assets/images/logo.png\")\nconst welcomeFace = require(\"@assets/images/welcome-face.png\")\n\ninterface WelcomeScreenProps extends AppStackScreenProps<\"Welcome\"> {}\n\nexport const WelcomeScreen: FC<WelcomeScreenProps> = (\n) => {\n  const { themed, theme } = useAppTheme()\n\n  const $bottomContainerInsets = useSafeAreaInsetsStyle([\"bottom\"])\n\n  return (\n    <Screen preset=\"fixed\" contentContainerStyle={$styles.flex1}>\n      <View style={themed($topContainer)}>\n        <Image style={themed($welcomeLogo)} source={welcomeLogo} resizeMode=\"contain\" />\n        <Text\n          testID=\"welcome-heading\"\n          style={themed($welcomeHeading)}\n          tx=\"welcomeScreen:readyForLaunch\"\n          preset=\"heading\"\n        />\n        <Text tx=\"welcomeScreen:exciting\" preset=\"subheading\" />\n        <Image\n          style={$welcomeFace}\n          source={welcomeFace}\n          resizeMode=\"contain\"\n          tintColor={theme.colors.palette.neutral900}\n        />\n      </View>\n\n      <View style={themed([$bottomContainer, $bottomContainerInsets])}>\n        <Text tx=\"welcomeScreen:postscript\" size=\"md\" />\n      </View>\n    </Screen>\n  )\n}\n\nconst $topContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  flexShrink: 1,\n  flexGrow: 1,\n  flexBasis: \"57%\",\n  justifyContent: \"center\",\n  paddingHorizontal: spacing.lg,\n})\n\nconst $bottomContainer: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({\n  flexShrink: 1,\n  flexGrow: 0,\n  flexBasis: \"43%\",\n  backgroundColor: colors.palette.neutral100,\n  borderTopLeftRadius: 16,\n  borderTopRightRadius: 16,\n  paddingHorizontal: spacing.lg,\n  justifyContent: \"space-around\",\n})\n\nconst $welcomeLogo: ThemedStyle<ImageStyle> = ({ spacing }) => ({\n  height: 88,\n  width: \"100%\",\n  marginBottom: spacing.xxl,\n})\n\nconst $welcomeFace: ImageStyle = {\n  height: 169,\n  width: 269,\n  position: \"absolute\",\n  bottom: -47,\n  right: -80,\n  transform: [{ scaleX: isRTL ? -1 : 1 }],\n}\n\nconst $welcomeHeading: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginBottom: spacing.md,\n})\n\"\n`;\n\nexports[`markup removeBlock should remove comments and lines between \"# @test remove-block-start\" and \"# @test remove-block-end\" 1`] = `\n\"\n\n        ---\n        - runFlow: Login.yaml\n        - tapOn: \"Podcast, tab, 3 of 4\"\n        - assertVisible: \"React Native Radio episodes\"\n        \"\n`;\n\nexports[`markup removeBlock should remove comments and lines between \"/* @test remove-block-start */\" and \"/* @test remove-block-end */\" 1`] = `\n\"\n          export * from \"./WelcomeScreen\"\n          export * from \"./LoginScreen\"\n          export * from \"./ErrorScreen/ErrorBoundary\"\n          // export other screens here\n        \"\n`;\n\nexports[`markup removeBlock should remove comments and lines between \"// @test remove-block-start\" and \"// @test remove-block-end\" 1`] = `\n\"\n          export * from \"./WelcomeScreen\"\n          export * from \"./LoginScreen\"\n          export * from \"./ErrorScreen/ErrorBoundary\"\n          // export other screens here\n        \"\n`;\n\nexports[`markup removeCurrentLine should remove line with \"# @test remove-current-line\" comment 1`] = `\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      env:\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      \"\n`;\n\nexports[`markup removeCurrentLine should remove line with \"/* @test remove-current-line */\" comment 1`] = `\n\"\n        import { StyleProp, View, ViewStyle } from \"react-native\"\n\n        interface DemoDividerProps {\n            type?: \"vertical\" | \"horizontal\"\n            size?: number\n            style?: StyleProp<ViewStyle>\n        }\n\n        export function DemoDivider(props: DemoDividerProps) {\n            const { type = \"horizontal\", size = 10, style: $styleOverride } = props\n\n            return (\n              <View\n                  style={[\n                    $divider,\n                    type === \"horizontal\" && { height: size },\n                    type === \"vertical\" && { width: size },\n                  ]}\n              />\n            )\n        }\n\n        const $divider: ViewStyle = {\n            flexGrow: 0,\n        }\n      \"\n`;\n\nexports[`markup removeCurrentLine should remove line with \"// @test remove-current-line\" comment 1`] = `\n\"\n        import { StyleProp, View, ViewStyle } from \"react-native\"\n\n        interface DemoDividerProps {\n            type?: \"vertical\" | \"horizontal\"\n            size?: number\n            style?: StyleProp<ViewStyle>\n        }\n\n        export function DemoDivider(props: DemoDividerProps) {\n            const { type = \"horizontal\", size = 10, style: $styleOverride } = props\n\n            return (\n              <View\n                  style={[\n                    $divider,\n                    type === \"horizontal\" && { height: size },\n                    type === \"vertical\" && { width: size },\n                    $styleOverride,\n                  ]}\n              />\n            )\n        }\n\n        const $divider: ViewStyle = {\n            flexGrow: 0,\n        }\n      \"\n`;\n\nexports[`markup removeNextLine should not modify other lines other than \"// @test remove-next-line and line after\" 1`] = `\n\"export * from \"./DemoIcon\"\nexport * from \"./DemoTextField\"\nexport * from \"./DemoButton\"\nexport * from \"./DemoListItem\"\nexport * from \"./DemoHeader\"\nexport * from \"./DemoText\"\"\n`;\n\nexports[`markup removeNextLine should remove comment and next line after \"# @test remove-next-line\" 1`] = `\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        env:\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        \"\n`;\n\nexports[`markup removeNextLine should remove comment and next line after \"/* @test remove-next-line */\" 1`] = `\n\"\n          export * from \"./WelcomeScreen\"\n          export * from \"./LoginScreen\"\n        \"\n`;\n\nexports[`markup removeNextLine should remove comment and next line after \"// @test remove-next-line\" 1`] = `\n\"\n          export * from \"./WelcomeScreen\"\n          export * from \"./LoginScreen\"\n        \"\n`;\n"
  },
  {
    "path": "src/tools/cache.ts",
    "content": "import * as crypto from \"crypto\"\nimport { filesystem } from \"gluegun\"\n\nimport type { PackagerName } from \"./packager\"\n\nconst lockFile = {\n  yarn: \"yarn.lock\",\n  pnpm: \"pnpm-lock.yaml\",\n  npm: \"package-lock.json\",\n  bun: \"bun.lockb\",\n} as const\n\nconst MAC: NodeJS.Platform = \"darwin\"\nconst WINDOWS: NodeJS.Platform = \"win32\"\nconst LINUX: NodeJS.Platform = \"linux\"\nconst cachePath = {\n  [MAC]: \"Library/Caches\",\n  [WINDOWS]: \"AppData/Local/Temp\",\n  [LINUX]: \".cache\",\n} as const\n\nconst { path, dir, homedir } = filesystem\n\nfunction hash(str: string) {\n  return crypto.createHash(\"md5\").update(str).digest(\"hex\")\n}\n\ninterface TargetsOptions {\n  rootDir: string\n  packagerName: PackagerName\n  platform: NodeJS.Platform | undefined\n}\nconst targets = ({ rootDir, packagerName, platform }: TargetsOptions) => {\n  const cachePaths = [\n    { type: \"dir\", path: path(rootDir, \"node_modules\") },\n    { type: \"file\", path: path(rootDir, lockFile[packagerName]) },\n  ] as { type: string; path: string; platform?: NodeJS.Platform[] }[]\n  return cachePaths.filter((target) =>\n    target?.platform ? target.platform.includes(platform) : true,\n  )\n}\n\ninterface CopyOptions {\n  fromRootDir: string\n  toRootDir: string\n  packagerName: PackagerName\n  platform?: NodeJS.Platform\n}\nfunction copy(options: CopyOptions) {\n  const { fromRootDir, toRootDir, packagerName, platform = process.platform } = options\n\n  const fromTargets = targets({ rootDir: fromRootDir, packagerName, platform })\n  const toTargets = targets({ rootDir: toRootDir, packagerName, platform })\n\n  return Promise.all(\n    fromTargets.map((from, index) => {\n      const to = toTargets[index]\n      if (from.type === \"dir\") {\n        dir(from.path)\n      }\n\n      return filesystem.copyAsync(from.path, to.path, { overwrite: true })\n    }),\n  )\n}\n\n// Root directory path of ignite dependency cache\nfunction rootdir(platform: NodeJS.Platform = process.platform) {\n  const folder = cachePath[platform] ?? cachePath[LINUX]\n  return path(homedir(), folder, \"ignite\")\n}\n\nfunction clear() {\n  filesystem.remove(rootdir())\n}\n\n/** Tool for managing cache of dependencies related to the ignite boilerplate */\nexport const cache = {\n  copy,\n  targets,\n  hash,\n  rootdir,\n  clear,\n} as const\n"
  },
  {
    "path": "src/tools/demo.ts",
    "content": "import { filesystem } from \"gluegun\"\n\nexport const DEMO_MARKUP_PREFIX = \"@demo\"\n\nexport const demoDependenciesToRemove = [\"expo-application\"]\n\nexport function findDemoPatches(): string[] {\n  const patchesPath = filesystem.path(\"./patches\")\n\n  // Return empty array if patches directory doesn't exist\n  if (!filesystem.exists(patchesPath)) return []\n\n  const globs = demoDependenciesToRemove.map((dep) => `${dep}*.patch`)\n  const filePaths = filesystem.cwd(\"./patches\").find({\n    matching: globs,\n  })\n  return filePaths\n}\n"
  },
  {
    "path": "src/tools/filesystem-ext.ts",
    "content": "import { filesystem } from \"gluegun\"\nimport * as pathlib from \"path\"\n\n/**\n * A lot like gluegun's filesystem.subdirectories(), but gets files too.\n *\n * This should probably go in Gluegun.\n *\n * Right about right here: https://github.com/infinitered/gluegun/blob/master/src/toolbox/filesystem-tools.ts#L52\n */\nexport function children(path: string, isRelative = false, matching = \"*\"): string[] {\n  const dirs = filesystem.cwd(path).find({\n    matching,\n    directories: true,\n    recursive: false,\n    files: true,\n  })\n  if (isRelative) {\n    return dirs\n  } else {\n    return dirs.map((dir) => pathlib.join(path, dir))\n  }\n}\n"
  },
  {
    "path": "src/tools/flag.ts",
    "content": "/**\n * Utility for converting 'true' and 'false' strings to booleans.\n *\n * Both `Boolean('true')` and `Boolean('false')` return `true`, because any string is a truthy value.\n * `!!'true'` and `!!'false'` also return `true`, for the same reason.\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean\n *\n * @param value The value to check.\n * @returns `true` for 'true', `false` for 'false', `Boolean` result otherwise.\n */\nexport function bool(value: unknown): boolean {\n  if (value === \"false\") return false\n  return Boolean(value)\n}\n\n/**\n * Utility for converting 'true' and 'false' strings to booleans,\n * while preserving `undefined` values as `undefined` instead of `false`.\n */\nexport function boolFlag(option: unknown): boolean | undefined {\n  if (option === undefined) return undefined\n  return bool(option)\n}\n"
  },
  {
    "path": "src/tools/generators.ts",
    "content": "import * as ejs from \"ejs\"\nimport { filesystem, GluegunToolbox, GluegunPatchingPatchOptions, patching, strings } from \"gluegun\"\nimport * as sharp from \"sharp\"\nimport * as YAML from \"yaml\"\n\nimport { command, direction, heading, igniteHeading, link, p, warning } from \"./pretty\"\n\nconst NEW_LINE = filesystem.eol\n\ntype Options = {\n  skipSourceEqualityValidation?: boolean\n  [key: string]: any\n}\n\nexport function runGenerator(\n  toolbox: GluegunToolbox,\n  generateFunc: (toolbox: GluegunToolbox) => Promise<void>,\n  generator?: string,\n) {\n  const { parameters } = toolbox\n\n  p()\n  if (parameters.options.help || parameters.options.list) {\n    // show help or list generators\n    showGeneratorHelp(toolbox)\n  } else if (parameters.options.update) {\n    // update with fresh generators\n    updateGenerators(toolbox)\n  } else {\n    if (generator) {\n      const isValid = validateGenerator(generator)\n      if (!isValid) return\n    } else {\n      // catch-all, just show help\n      showGeneratorHelp(toolbox)\n      return\n    }\n\n    generateFunc(toolbox)\n  }\n}\n\nfunction validateGenerator(generator?: string) {\n  const generators = installedGenerators()\n\n  if (!generators.includes(generator)) {\n    warning(`⚠️  Generator \"${generator}\" isn't installed.`)\n    p()\n\n    if (availableGenerators().includes(generator)) {\n      direction(\"Install the generator with:\")\n      p()\n      command(`npx ignite-cli generate ${generator} --update`)\n      p()\n      direction(\"... and then try again!\")\n    } else {\n      direction(\"Check your spelling and try again\")\n    }\n\n    return false\n  }\n\n  return true\n}\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function showGeneratorHelp(toolbox: GluegunToolbox) {\n  igniteHeading()\n  heading(\"Ignite Generators\")\n  p()\n  p(\"When you create a new app with Ignite CLI, it will install several generator\")\n  p(\"templates in the project folder under the `ignite/templates` folder.\")\n  p()\n  heading(\"Commands\")\n  p()\n  command(\"--list  \", \"List installed generators\", [\"npx ignite-cli --list\"])\n  command(\n    \"--update\",\n    \"Update installed generators. You can also use the 'npx ignite-cli update X' format\",\n    [\n      \"npx ignite-cli --update\",\n      `npx ignite-cli model --update`,\n      `npx ignite-cli update model`,\n      `npx ignite-cli update --all`,\n    ],\n  )\n  warning(\"          ⚠️  this erases any customizations you've made!\")\n  p()\n  heading(\"Options\")\n  p()\n  command(\"--dir\", \"Override front matter or default path for generated files\", [\n    \"npx ignite-cli g model Episodes --dir src/context\",\n  ])\n  command(\"--case\", \"Formats the generated filename\", [\n    \"npx ignite-cli g model episode --case=auto\",\n    \"npx ignite-cli g model episode --case=pascal\",\n    \"npx ignite-cli g model episode --case=kebab\",\n    \"npx ignite-cli g model episode --case=snake\",\n    \"npx ignite-cli g model episode --case=none\",\n  ])\n  p()\n  heading(\"Installed generators\")\n  p()\n  showGenerators()\n}\n\nfunction showGenerators() {\n  if (!isIgniteProject()) {\n    warning(\"⚠️  Not in an Ignite project root. Go to your Ignite project root to see generators.\")\n    return\n  }\n\n  const generators = installedGenerators()\n  const longestGen = generators.reduce((c, g) => Math.max(c, g.length), 0)\n  generators.forEach((g) => {\n    if (g === \"app-icon\") {\n      // specialty app-icon generator\n      command(g.padEnd(longestGen), `generates app-icons`, [\n        `npx ignite-cli ${g} all|ios|android|expo`,\n      ])\n    } else if (g === \"splash-screen\") {\n      // specialty splash-screen generator\n      command(g.padEnd(longestGen), `generates splash-screen`, [\n        `npx ignite-cli ${g} \"#191015\" [--android-size=180 --ios-size=212]`,\n      ])\n    } else {\n      // standard generators\n      command(g.padEnd(longestGen), `generates a ${g}`, [`npx ignite-cli ${g} Demo`])\n    }\n  })\n}\n\nexport function updateGenerators(toolbox: GluegunToolbox) {\n  const { parameters } = toolbox\n\n  if (!isIgniteProject()) {\n    warning(\"⚠️  Not in an Ignite project root. Go to your Ignite project root to see generators.\")\n    return\n  }\n\n  let generatorsToUpdate\n  if (parameters.first) {\n    // only update the specified one\n    generatorsToUpdate = [parameters.first]\n  } else {\n    // update any available generators\n    generatorsToUpdate = availableGenerators()\n  }\n\n  const changes = installGenerators(generatorsToUpdate)\n  const distinct = (val, index, self) => self.indexOf(val) === index\n  const allGenerators = changes.concat(generatorsToUpdate).filter(distinct).sort()\n\n  heading(`Updated ${changes.length} generator${changes.length === 1 ? \"\" : \"s\"}`)\n  allGenerators.forEach((g) => {\n    if (changes.includes(g)) {\n      heading(`  ${g} - updated`)\n    } else {\n      p(`  ${g} - no changes`)\n    }\n  })\n}\n\nfunction isIgniteProject(): boolean {\n  return filesystem.exists(\"./ignite\") === \"dir\"\n}\n\nfunction cwd() {\n  return process.cwd()\n}\n\nfunction igniteDir() {\n  return filesystem.path(cwd(), \"ignite\")\n}\n\nfunction appDir() {\n  const routerPath = filesystem.path(cwd(), \"src/app\")\n  if (filesystem.exists(routerPath) === \"dir\") {\n    return filesystem.path(cwd(), \"src\")\n  }\n\n  return filesystem.path(cwd(), \"app\")\n}\n\nfunction templatesDir() {\n  return filesystem.path(igniteDir(), \"templates\")\n}\n\nfunction frontMatter(contents: string) {\n  const parts = contents.split(`---${NEW_LINE}`)\n  if (parts.length === 1 || parts.length === 3) {\n    return {\n      data: parts[1] ? YAML.parse(parts[1]) : {},\n      content: parts[2] ?? parts[0],\n    }\n  } else {\n    return {}\n  }\n}\n\n/**\n * Patch front matter configuration\n */\ntype Patch = GluegunPatchingPatchOptions & {\n  path: string\n  append?: string\n  prepend?: string\n  replace?: string\n  skip?: boolean\n}\n\n/**\n * Handles patching files via front matter config\n */\nasync function handlePatches(data: { patches?: Patch[]; patch?: Patch }) {\n  const patches = data.patches ?? []\n  if (data.patch) patches.push(data.patch)\n  for (const patch of patches) {\n    const { path: patchPath, skip, ...patchOpts } = patch\n    if (patchPath && !skip) {\n      if (patchOpts.append) {\n        await patching.append(patchPath, patchOpts.append)\n      }\n      if (patchOpts.prepend) {\n        await patching.prepend(patchPath, patchOpts.prepend)\n      }\n      if (patchOpts.replace) {\n        await patching.replace(patchPath, patchOpts.replace, patchOpts.insert)\n      }\n      await patching.patch(patchPath, patchOpts)\n    }\n  }\n}\n\n/**\n * Finds generator templates installed in the current project\n */\nfunction installedGenerators(): string[] {\n  const { subdirectories, separator } = filesystem\n\n  const generators = subdirectories(templatesDir()).map((g) => g.split(separator).slice(-1)[0])\n\n  return generators\n}\n\ntype GeneratorCaseOptions = \"auto\" | \"pascal\" | \"camel\" | \"kebab\" | \"snake\" | \"none\"\n\ntype GeneratorOptions = {\n  name: string\n  originalName: string\n  subdirectory: string\n  overwrite: boolean\n  dir?: string\n  case?: GeneratorCaseOptions\n}\n\n/**\n * Generates something using a template\n */\nexport async function generateFromTemplate(\n  generator: string,\n  options: GeneratorOptions,\n): Promise<{ written: string[]; overwritten: string[]; exists: string[] }> {\n  const { find, path, dir, separator } = filesystem\n  const { pascalCase, kebabCase, pluralize, camelCase, snakeCase } = strings\n\n  // permutations of the name\n  const pascalCaseName = pascalCase(options.name)\n  const kebabCaseName = kebabCase(options.name)\n  const camelCaseName = camelCase(options.name)\n  const snakeCaseName = snakeCase(options.name)\n\n  // array of written, exists and overwritten files\n  const written: string[] = []\n  const overwritten: string[] = []\n  const exists: string[] = []\n\n  // passed into the template generator\n  const props = { camelCaseName, kebabCaseName, pascalCaseName, snakeCaseName, ...options }\n\n  // where are we copying from?\n  const templateDir = path(templatesDir(), generator)\n\n  // find the files\n  const files = find(templateDir, { matching: \"*\" })\n\n  // check case options\n  let formattedName: string = pascalCaseName\n  switch (options.case) {\n    case \"camel\":\n      formattedName = camelCaseName\n      break\n    case \"kebab\":\n      formattedName = kebabCaseName\n      break\n    case \"snake\":\n      formattedName = snakeCaseName\n      break\n    case \"none\":\n      formattedName = options.originalName\n      break\n    case \"auto\":\n    default:\n      formattedName = pascalCaseName\n      break\n  }\n\n  // loop through the files\n  for (const templateFilename of files) {\n    // get the filename and replace `NAME` with the actual name\n    let filename = templateFilename.split(separator).slice(-1)[0].replace(\"NAME\", formattedName)\n\n    // strip the .ejs\n    if (filename.endsWith(\".ejs\")) {\n      filename = filename.slice(0, -4)\n    }\n\n    // read template file\n    let templateContents = filesystem.read(templateFilename)\n\n    // render ejs\n    if (templateFilename.endsWith(\".ejs\")) {\n      templateContents = ejs.render(templateContents, { props: { ...props, filename } })\n    }\n\n    // parse out front matter data and content\n    const { data: frontMatterData, content } = frontMatter(templateContents)\n    if (!content) {\n      warning(\"⚠️  Unable to parse front matter. Please check your delimiters.\")\n      return { written, exists, overwritten }\n    }\n\n    // where are we copying to?\n    const defaultDestinationDir = path(appDir(), pluralize(generator), options.subdirectory) // e.g. app/components, app/screens\n    const overrideDestinationDir = options.dir ?? frontMatterData.destinationDir // cli dir takes priority over front matter dir\n    const destinationDir = overrideDestinationDir\n      ? path(cwd(), overrideDestinationDir)\n      : defaultDestinationDir\n\n    // apply any provided patches\n    const destinationPath = path(destinationDir, frontMatterData.filename ?? filename)\n\n    // apply any provided patches\n    const isFileExist = filesystem.exists(destinationPath)\n    if (!isFileExist) await handlePatches(frontMatterData)\n\n    // ensure destination folder exists\n    dir(destinationDir)\n\n    // check if file exist or not and check of overwrite property\n    if (isFileExist) {\n      if (props.overwrite) {\n        filesystem.write(destinationPath, content)\n        overwritten.push(destinationPath)\n      } else {\n        exists.push(destinationPath)\n      }\n    } else {\n      filesystem.write(destinationPath, content)\n      written.push(destinationPath)\n    }\n  }\n  return { written, exists, overwritten }\n}\n\n/**\n * Checks a file for a directoryDir in template front matter.\n */\nexport function frontMatterDirectoryDir(generator: string): string | undefined {\n  if (!validateGenerator(generator)) {\n    return undefined\n  }\n\n  const { path } = filesystem\n\n  // where are we copying from?\n  const templateDir = path(templatesDir(), generator)\n\n  const fileContents = filesystem.read(`${templateDir}/NAME.tsx.ejs`)\n  const { data: frontMatterData } = frontMatter(fileContents)\n\n  return frontMatterData?.destinationDir\n}\n\n/**\n * Ignite cli root directory\n */\nfunction igniteCliRootDir(): string {\n  return filesystem.path(__filename, \"..\", \"..\", \"..\")\n}\n\n/**\n * Directory where we can find Ignite CLI generator templates\n */\nfunction sourceDirectory(): string {\n  return filesystem.path(igniteCliRootDir(), \"boilerplate\", \"ignite\", \"templates\")\n}\n\n/**\n * Finds generator templates in Ignite CLI\n */\nfunction availableGenerators(): string[] {\n  const { subdirectories, separator } = filesystem\n  return subdirectories(sourceDirectory()).map((g) => g.split(separator).slice(-1)[0])\n}\n\n/**\n * Copies over generators (specific generators, or all) from Ignite CLI to the project\n * ignite/templates folder.\n */\nfunction installGenerators(generators: string[]): string[] {\n  const { path, find, copy, dir, cwd, separator, exists, read } = filesystem\n  const sourceDir = sourceDirectory()\n  const targetDir = path(cwd(), \"ignite\", \"templates\")\n\n  // for each generator type, copy it over to the ignite/templates folder\n  const changedGenerators = generators.filter((gen) => {\n    const sourceGenDir = path(sourceDir, gen)\n    const targetGenDir = path(targetDir, gen)\n\n    // ensure the directory exists\n    dir(targetDir)\n\n    // find all source files\n    const files = find(sourceGenDir, { matching: \"*\" })\n\n    // copy them over\n    const changedFiles = files.filter((file) => {\n      const filename = file.split(separator).slice(-1)[0]\n      const targetFile = path(targetGenDir, filename)\n\n      if (!exists(targetFile) || read(targetFile) !== read(file)) {\n        copy(file, targetFile, { overwrite: true })\n        return true\n      } else {\n        return false\n      }\n    })\n\n    return changedFiles.length > 0\n  })\n\n  return changedGenerators\n}\n\nenum Platforms {\n  Ios = \"ios\",\n  Android = \"android\",\n  Expo = \"expo\",\n}\n\n// prettier-ignore\nconst APP_ICON_RULESET = {\n  icons: [\n    { platform: Platforms.Ios, type: \"universal\", name: \"Icon-{size}-{idiom}{scale}.png\", inputFile: \"ios-universal.png\" },\n    { platform: Platforms.Android, type: \"adaptive\", name: \"mipmap-{dpi}/ic_launcher_background.png\", inputFile: \"android-adaptive-background.png\" },\n    { platform: Platforms.Android, type: \"adaptive\", name: \"mipmap-{dpi}/ic_launcher_foreground.png\", inputFile: \"android-adaptive-foreground.png\" },\n    { platform: Platforms.Android, type: \"legacy\", name: \"mipmap-{dpi}/ic_launcher.png\", inputFile: \"android-legacy.png\", transform: { size: 812, radius: 64, padding: 106 } },\n    { platform: Platforms.Android, type: \"legacy\", name: \"mipmap-{dpi}/ic_launcher_round.png\", inputFile: \"android-legacy.png\", transform: { size: 944, radius: 472, padding: 40 } },\n    { platform: Platforms.Expo, type: \"mobile\", name: \"app-icon-ios.png\", inputFile: \"ios-universal.png\" },\n    { platform: Platforms.Expo, type: \"mobile\", name: \"app-icon-android-legacy.png\", inputFile: \"android-legacy.png\" },\n    { platform: Platforms.Expo, type: \"mobile\", name: \"app-icon-android-adaptive-background.png\", inputFile: \"android-adaptive-background.png\" },\n    { platform: Platforms.Expo, type: \"mobile\", name: \"app-icon-android-adaptive-foreground.png\", inputFile: \"android-adaptive-foreground.png\" },\n    { platform: Platforms.Expo, type: \"mobile\", name: \"app-icon-all.png\", inputFile: \"ios-universal.png\" },\n    { platform: Platforms.Expo, type: \"web\", name: \"app-icon-web-favicon.png\", inputFile: \"ios-universal.png\" },\n  ],\n  rules: [\n    { platform: Platforms.Ios, size: { universal: 1024 }, scale: 1, idiom: \"ios-marketing\" },\n    { platform: Platforms.Ios, size: { universal: 83.5 }, scale: 2, idiom: \"ipad\" },\n    { platform: Platforms.Ios, size: { universal: 20 }, scale: 1, idiom: \"ipad\" },\n    { platform: Platforms.Ios, size: { universal: 20 }, scale: 2, idiom: \"ipad\" },\n    { platform: Platforms.Ios, size: { universal: 29 }, scale: 1, idiom: \"ipad\" },\n    { platform: Platforms.Ios, size: { universal: 29 }, scale: 2, idiom: \"ipad\" },\n    { platform: Platforms.Ios, size: { universal: 40 }, scale: 1, idiom: \"ipad\" },\n    { platform: Platforms.Ios, size: { universal: 40 }, scale: 2, idiom: \"ipad\" },\n    { platform: Platforms.Ios, size: { universal: 76 }, scale: 1, idiom: \"ipad\" },\n    { platform: Platforms.Ios, size: { universal: 76 }, scale: 2, idiom: \"ipad\" },\n    { platform: Platforms.Ios, size: { universal: 20 }, scale: 2, idiom: \"iphone\" },\n    { platform: Platforms.Ios, size: { universal: 20 }, scale: 3, idiom: \"iphone\" },\n    { platform: Platforms.Ios, size: { universal: 29 }, scale: 2, idiom: \"iphone\" },\n    { platform: Platforms.Ios, size: { universal: 29 }, scale: 3, idiom: \"iphone\" },\n    { platform: Platforms.Ios, size: { universal: 40 }, scale: 2, idiom: \"iphone\" },\n    { platform: Platforms.Ios, size: { universal: 40 }, scale: 3, idiom: \"iphone\" },\n    { platform: Platforms.Ios, size: { universal: 60 }, scale: 2, idiom: \"iphone\" },\n    { platform: Platforms.Ios, size: { universal: 60 }, scale: 3, idiom: \"iphone\" },\n    { platform: Platforms.Android, size: { legacy: 192, adaptive: 432 }, dpi: \"xxxhdpi\" },\n    { platform: Platforms.Android, size: { legacy: 144, adaptive: 324 }, dpi: \"xxhdpi\" },\n    { platform: Platforms.Android, size: { legacy: 96, adaptive: 216 }, dpi: \"xhdpi\" },\n    { platform: Platforms.Android, size: { legacy: 72, adaptive: 162 }, dpi: \"hdpi\" },\n    { platform: Platforms.Android, size: { legacy: 48, adaptive: 108 }, dpi: \"mdpi\" },\n    { platform: Platforms.Expo, size: { mobile: 1024, web: 48 } },\n  ]\n}\n\n/**\n * Validates that all necessary app-icon input files exist in the template dir.\n * Additionally validates that they are of the correct size.\n */\nexport async function validateAppIconGenerator(option: `${Platforms}` | \"all\", flags: Options) {\n  const { skipSourceEqualityValidation } = flags || {}\n\n  const { path, exists, inspect } = filesystem\n\n  const allowedOptions = Object.values(Platforms) as `${Platforms}`[]\n\n  // check that the option is allowed\n  if (option !== \"all\" && !allowedOptions.includes(option)) {\n    return {\n      isValid: false,\n      messages: [`The option \"${option}\" is not valid for generator \"app-icon\"`],\n    }\n  }\n\n  const optionsToValidate = option === \"all\" ? allowedOptions : [option]\n\n  // get all the file-names that are required for the supplied option(s) and dedup\n  const inputFileNames = APP_ICON_RULESET.icons\n    .filter((i) => optionsToValidate.includes(i.platform))\n    .reduce((acc, i) => Array.from(new Set([...acc, i.inputFile])), [])\n\n  // validate both presence and size of all input files\n  const validationPromises = inputFileNames.map(async (fileName) => {\n    const boilerplateInputFilePath = path(sourceDirectory(), \"app-icon\", fileName)\n    const inputFilePath = path(templatesDir(), \"app-icon\", fileName)\n\n    const isMissing = !exists(inputFilePath)\n    const isInvalidSize = await (async function () {\n      if (isMissing) return false\n\n      const metadata = await sharp(inputFilePath).metadata()\n      return metadata.width !== 1024 || metadata.height !== 1024\n    })()\n    const isSameAsSource = await (async function () {\n      if (skipSourceEqualityValidation) return false\n      if (isMissing) return false\n\n      const inputFileMd5 = inspect(inputFilePath, { checksum: \"md5\" }).md5\n      const sourceFileMd5 = inspect(boilerplateInputFilePath, { checksum: \"md5\" }).md5\n\n      return inputFileMd5 === sourceFileMd5\n    })()\n\n    return { fileName, isMissing, isInvalidSize, isSameAsSource }\n  })\n\n  const validationResults = await Promise.all(validationPromises)\n\n  // accumulate error messages for any failed validations\n  const validationMessages = validationResults.reduce((acc, r) => {\n    const messages = [\n      r.isMissing && \"   • the file is missing\",\n      r.isInvalidSize && \"   • the file is the wrong size (expected 1024x1024px)\",\n      r.isSameAsSource &&\n        \"   • looks like you're using our default template; customize the file with your own icon first\",\n    ].filter(Boolean)\n\n    if (messages.length) {\n      acc.push(`⚠️  ignite/templates/app-icon/${r.fileName}:`, ...messages)\n    }\n\n    return acc\n  }, [])\n\n  return {\n    isValid: !validationMessages.length,\n    messages: validationMessages,\n  }\n}\n\n/**\n * Generates app-icons for specified option.\n */\nexport async function generateAppIcons(option: `${Platforms}` | \"all\") {\n  const { path, exists, find, copy, write } = filesystem\n  const cwd = process.cwd()\n\n  const options = option === \"all\" ? Object.values(Platforms) : ([option] as `${Platforms}`[])\n\n  const optionGenerationSuccesses = []\n\n  // start the generation process for each platform\n  // looping instead of mapping allows us to await for each platform sequentially\n  for (const o of options) {\n    const optionProjectName = { expo: \"Expo\" }[o]\n\n    // find the output path for platform and check if it exists\n    // iOS is a bit weird since it's named differently for each project\n    const relativeOutputDirPath = {\n      expo: \"assets/images\",\n      android: \"android/app/src/main/res\",\n      ios: (function () {\n        const searchPath = path(cwd, \"ios\")\n\n        if (!exists(searchPath)) return searchPath\n\n        return (\n          find(searchPath, {\n            directories: true,\n            files: false,\n            matching: \"AppIcon.appiconset\",\n          })?.[0] || \"ios/**/Images.xcassets/AppIcon.appiconset\"\n        )\n      })(),\n    }[o]\n    const outputDirPath = path(cwd, relativeOutputDirPath)\n\n    // if not, skip...\n    if (exists(outputDirPath) !== \"dir\") {\n      warning(\n        `⚠️  No output directory found for \"${optionProjectName}\" at \"${outputDirPath}\". Skipping...`,\n      )\n      continue\n    }\n\n    heading(`Generating ${optionProjectName} app icons...`)\n\n    const icons = APP_ICON_RULESET.icons.filter((i) => i.platform === o)\n\n    // prepare each icon for generation sequentially\n    for (const i of icons) {\n      const inputFilePath = path(templatesDir(), \"app-icon\", i.inputFile)\n\n      // get the input file for sharp and do some initial transforms when necessary (e.g. radius and padding)\n      const inputFile = await (async function () {\n        if (!i.transform) return inputFilePath\n\n        try {\n          const { size, radius, padding } = i.transform\n          const cutoutMask = Buffer.from(\n            `<svg><rect x=\"0\" y=\"0\" width=\"${size}\" height=\"${size}\" rx=\"${radius}\" ry=\"${radius}\"/></svg>`,\n          )\n          return await sharp(inputFilePath)\n            .resize(size, size, { fit: \"fill\" })\n            .composite([{ input: cutoutMask, blend: \"dest-in\" }])\n            .extend({\n              top: padding,\n              bottom: padding,\n              left: padding,\n              right: padding,\n              background: { r: 0, g: 0, b: 0, alpha: 0 },\n            })\n            .toBuffer()\n        } catch (error) {}\n      })()\n\n      const rules = APP_ICON_RULESET.rules.filter((i) => i.platform === o)\n\n      // actually resize the input files and save to output dir sequentially\n      for (const r of rules) {\n        // construct the output file name\n        const outputFileName = {\n          expo: i.name,\n          android: i.name.replace(\"{dpi}\", r.dpi),\n          ios: i.name\n            .replace(\"{size}\", r.size[i.type])\n            .replace(\"{idiom}\", r.idiom)\n            .replace(\"{scale}\", r.scale > 1 ? `@${r.scale}x` : \"\"),\n        }[o]\n\n        if (!inputFile) {\n          warning(`⚠️  ${outputFileName}: transform failed, please file an issue on GitHub`)\n          continue\n        }\n\n        const outputFilePath = path(outputDirPath, outputFileName)\n        const outputSize = r.size[i.type] * (r.scale || 1)\n\n        // finally, resize and save\n        try {\n          await sharp(inputFile)\n            .resize(outputSize, outputSize, { fit: \"fill\" })\n            .toFile(outputFilePath)\n\n          direction(`✅ ${outputFileName}`)\n        } catch (error) {\n          warning(`⚠️  ${outputFileName}: saving failed, check if the directory exists`)\n        }\n      }\n    }\n\n    const boilerplateDirPath = path(igniteCliRootDir(), \"boilerplate\")\n\n    const postGenerationFn = {\n      ios: () => {\n        const sourceContentsJsonFilePath = path(\n          boilerplateDirPath,\n          \"ios\",\n          require(path(boilerplateDirPath, \"app.json\")).name,\n          \"Images.xcassets/AppIcon.appiconset/Contents.json\",\n        )\n        copy(sourceContentsJsonFilePath, path(outputDirPath, \"Contents.json\"), { overwrite: true })\n        direction(`✅ Contents.json`)\n      },\n\n      android: () => {\n        const sourceIcLauncherXmlFilePath = path(\n          boilerplateDirPath,\n          relativeOutputDirPath,\n          \"mipmap-anydpi-v26/ic_launcher.xml\",\n        )\n\n        copy(\n          sourceIcLauncherXmlFilePath,\n          path(outputDirPath, \"mipmap-anydpi-v26/ic_launcher.xml\"),\n          { overwrite: true },\n        )\n        direction(`✅ mipmap-anydpi-v26/ic_launcher.xml`)\n      },\n\n      expo: () => {\n        const merge = require(\"deepmerge-json\")\n        const sourceExpoConfig = require(path(boilerplateDirPath, \"app.json\"))?.expo\n        const outputAppConfig = path(cwd, \"app.json\")\n\n        const iconConfig = {\n          expo: {\n            icon: sourceExpoConfig?.icon,\n            android: {\n              icon: sourceExpoConfig?.android?.icon,\n              adaptiveIcon: sourceExpoConfig?.android?.adaptiveIcon,\n            },\n            ios: { icon: sourceExpoConfig?.ios?.icon },\n            web: { favicon: sourceExpoConfig?.web?.favicon },\n          },\n        }\n\n        // Check if app.json exists - however, could also be `app.config.js` or `app.config.ts` in\n        // which case we should output a warning of what files to update\n        if (!exists(outputAppConfig)) {\n          const appConfigFiles = find(cwd, { matching: \"app.config.*\" })\n          if (appConfigFiles.length > 0) {\n            warning(\n              `⚠️  No \"app.json\" found at \"${outputAppConfig}\". It looks like you are using a dynamic configuration! Learn more at ${link(\n                \"https://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs\",\n              )}`,\n            )\n            warning(`⚠️  Please add the following to your app.config.js manually:`)\n            JSON.stringify(iconConfig, null, 2)\n              .split(\"\\n\")\n              .map((line) => p(`  ${line}`))\n          } else {\n            warning(`⚠️  No \"app.json\" found at \"${outputAppConfig}\". Skipping...`)\n          }\n\n          return\n        }\n\n        const updatedConfig = merge(require(outputAppConfig), iconConfig)\n\n        write(path(cwd, \"app.json\"), JSON.stringify(updatedConfig, null, 2) + \"\\n\")\n        direction(`✅ app.json`)\n      },\n    }[o]\n\n    postGenerationFn()\n\n    // if we reached this point, generation for this platform was successful\n    optionGenerationSuccesses.push(true)\n  }\n\n  return !!optionGenerationSuccesses.length\n}\n/**\n * Validates that splash screen icon input file exists in the template dir.\n * Additionally validates the size and background parameters.\n */\nexport async function validateSplashScreenGenerator(\n  options: { androidSize: number; iosSize: number; backgroundColor: string },\n  flags: Options,\n) {\n  const { androidSize, iosSize, backgroundColor } = options || {}\n  const { skipSourceEqualityValidation } = flags || {}\n\n  const { path, exists, inspect } = filesystem\n\n  const validationMessages = []\n\n  // check if the android size option is numerical and non-zero\n  const androidMessages = [\n    Number.isNaN(androidSize) && \"   • a numerical value\",\n    androidSize <= 0 && \"   • a value greater than 0\",\n    androidSize >= 288 && \"   • a value less than 288\",\n  ].filter(Boolean)\n\n  if (androidMessages.length) {\n    validationMessages.push(`⚠️  \"--android-size\" option must be:`, ...androidMessages)\n  }\n\n  // check if the ios size option is numerical and non-zero\n  const iosMessages = [\n    Number.isNaN(iosSize) && \"   • a numerical value\",\n    iosSize <= 0 && \"   • a value greater than 0\",\n  ].filter(Boolean)\n\n  if (iosMessages.length) {\n    validationMessages.push(`⚠️  \"--ios-size\" option must be:`, ...iosMessages)\n  }\n\n  // check if the background option is a valid hex color\n  if (!/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(backgroundColor)) {\n    validationMessages.push(`⚠️  background color parameter must be:`)\n    validationMessages.push(`   • a valid hex color`)\n  }\n\n  // validate template input file\n  const boilerplateInputFilePath = path(sourceDirectory(), \"splash-screen\", \"logo.png\")\n  const inputFilePath = path(templatesDir(), \"splash-screen\", \"logo.png\")\n\n  const isMissing = !exists(inputFilePath)\n  const isInvalidSize = await (async function () {\n    if (isMissing) return false\n\n    const metadata = await sharp(inputFilePath).metadata()\n    return metadata.width !== 1024 || metadata.height !== 1024\n  })()\n  const isSameAsSource = await (async function () {\n    if (skipSourceEqualityValidation) return false\n    if (isMissing) return false\n\n    const inputFileMd5 = inspect(inputFilePath, { checksum: \"md5\" }).md5\n    const sourceFileMd5 = inspect(boilerplateInputFilePath, { checksum: \"md5\" }).md5\n\n    return inputFileMd5 === sourceFileMd5\n  })()\n\n  const messages = [\n    isMissing && \"   • the file is missing\",\n    isInvalidSize && \"   • the file is the wrong size (expected 1024x1024px)\",\n    isSameAsSource &&\n      \"   • looks like you're using our default template; customize the file with your own icon first\",\n  ].filter(Boolean)\n\n  if (messages.length) {\n    validationMessages.push(`⚠️  ignite/templates/splash-screen/logo.png:`, ...messages)\n  }\n\n  return {\n    isValid: !validationMessages.length,\n    messages: validationMessages,\n  }\n}\n\n/**\n * Generates splash screen for all platforms\n */\nexport async function generateSplashScreen(options: {\n  androidSize: number\n  iosSize: number\n  backgroundColor: string\n}) {\n  const { androidSize, iosSize, backgroundColor } = options || {}\n  const { path, exists, write, find } = filesystem\n  const cwd = process.cwd()\n\n  const inputFilePath = path(templatesDir(), \"splash-screen\", \"logo.png\")\n  const expoOutputDirPath = path(cwd, \"assets/images\")\n  const isExpoOutputDirExists = exists(expoOutputDirPath) === \"dir\"\n\n  const optionGenerationSuccesses = []\n\n  async function generateForExpo(\n    type: \"ios\" | \"android\" | \"web\" | \"all\",\n    size: number,\n    expoRules: { name?: string; width: number; height: number; scale: number }[],\n  ) {\n    for (const expoRule of expoRules) {\n      const { name, width, height, scale } = expoRule\n\n      const outputFileName = [`splash-logo`, type, name].filter(Boolean).join(\"-\") + \".png\"\n      const outputFilePath = path(expoOutputDirPath, outputFileName)\n      const logoSize = size * scale\n      const verticalPadding = Math.ceil((height - logoSize) / 2)\n      const horizontalPadding = Math.ceil((width - logoSize) / 2)\n\n      try {\n        await sharp(inputFilePath)\n          .resize(logoSize, logoSize, { fit: \"fill\" })\n          .extend({\n            top: verticalPadding,\n            bottom: verticalPadding,\n            left: horizontalPadding,\n            right: horizontalPadding,\n            background: { r: 0, g: 0, b: 0, alpha: 0 },\n          })\n          .toFile(outputFilePath)\n\n        direction(`✅ assets/images/${outputFileName}`)\n        optionGenerationSuccesses.push(true)\n      } catch (error) {}\n    }\n  }\n\n  heading(`Generating Expo splash screens (Android, iOS, and Web)...`)\n  if (isExpoOutputDirExists) {\n    await generateForExpo(\"ios\", iosSize, [\n      { name: \"mobile\", width: 1284, height: 2778, scale: 3 },\n      { name: \"tablet\", width: 2048, height: 2732, scale: 2 },\n    ])\n    await generateForExpo(\"android\", androidSize, [\n      { name: \"universal\", width: 1440, height: 2560, scale: 4 },\n    ])\n    await generateForExpo(\"web\", 300, [{ width: 1920, height: 1080, scale: 1 }])\n    await generateForExpo(\"all\", 180, [{ width: 1242, height: 2436, scale: 3 }])\n\n    // update app.json\n    const boilerplateDirPath = path(igniteCliRootDir(), \"boilerplate\")\n    const merge = require(\"deepmerge-json\")\n    const sourceExpoConfig = require(path(boilerplateDirPath, \"app.json\"))?.expo\n    const outputAppConfig = path(cwd, \"app.json\")\n\n    const splashConfig = {\n      expo: {\n        splash: {\n          backgroundColor,\n          image: sourceExpoConfig?.splash?.image,\n          resizeMode: sourceExpoConfig?.splash?.resizeMode,\n        },\n        android: {\n          splash: {\n            backgroundColor,\n            image: sourceExpoConfig?.android?.splash?.image,\n            resizeMode: sourceExpoConfig?.android?.splash?.resizeMode,\n          },\n        },\n        ios: {\n          splash: {\n            backgroundColor,\n            image: sourceExpoConfig?.ios?.splash?.image,\n            resizeMode: sourceExpoConfig?.ios?.splash?.resizeMode,\n          },\n        },\n        web: {\n          splash: {\n            backgroundColor,\n            image: sourceExpoConfig?.web?.splash?.image,\n            resizeMode: sourceExpoConfig?.web?.splash?.resizeMode,\n          },\n        },\n      },\n    }\n\n    // Check if app.json exists - however, could also be `app.config.js` or `app.config.ts` in\n    // which case we should output a warning of what files to update\n    if (!exists(outputAppConfig)) {\n      const appConfigFiles = find(cwd, { matching: \"app.config.*\" })\n      if (appConfigFiles.length > 0) {\n        warning(\n          `⚠️  No \"app.json\" found at \"${outputAppConfig}\". It looks like you are using a dynamic configuration! Learn more at ${link(\n            \"https://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs\",\n          )}`,\n        )\n        warning(`⚠️  Please add the following to your app.config.js manually:`)\n        JSON.stringify(splashConfig, null, 2)\n          .split(\"\\n\")\n          .map((line) => p(`  ${line}`))\n      } else {\n        warning(`⚠️  No \"app.json\" found at \"${outputAppConfig}\". Skipping...`)\n      }\n\n      return\n    }\n\n    const updatedConfig = merge(require(outputAppConfig), splashConfig)\n\n    write(path(cwd, \"app.json\"), JSON.stringify(updatedConfig, null, 2) + \"\\n\")\n    direction(`✅ app.json`)\n  } else {\n    warning(`⚠️  No output directory found for \"Expo\" at \"${expoOutputDirPath}\". Skipping...`)\n  }\n\n  return !!optionGenerationSuccesses.length\n}\n"
  },
  {
    "path": "src/tools/markup.test.ts",
    "content": "import {\n  MarkupComments,\n  markupComment,\n  removeBlocks,\n  removeCurrentLine,\n  removeNextLine,\n  updateFile,\n} from \"./markup\"\n\nconst TEST_MARKUP_PREFIX = \"@test\"\n\ndescribe(\"markup\", () => {\n  describe(\"removeCurrentLine\", () => {\n    it(`should remove line with \"// ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveCurrentLine}\" comment`, () => {\n      const comment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveCurrentLine)\n      const contents = `\n        import { StyleProp, View, ViewStyle } from \"react-native\"\n\n        interface DemoDividerProps {\n            type?: \"vertical\" | \"horizontal\"\n            size?: number\n            style?: StyleProp<ViewStyle>\n        }\n\n        export function DemoDivider(props: DemoDividerProps) {\n            const { type = \"horizontal\", size = 10, style: $styleOverride } = props\n\n            return (\n              <View\n                  style={[\n                    $divider,\n                    type === \"horizontal\" && { height: size },\n                    type === \"vertical\" && { width: size },\n                    $styleOverride,\n                  ]}\n              />\n            )\n        }\n\n        const $divider: ViewStyle = {\n            flexGrow: 0,\n            flexShrink: 0, // ${comment}\n        }\n      `\n      const result = removeCurrentLine(contents, comment)\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(comment)\n    })\n\n    it(`should remove line with \"/* ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveCurrentLine} */\" comment`, () => {\n      const comment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveCurrentLine)\n      const contents = `\n        import { StyleProp, View, ViewStyle } from \"react-native\"\n\n        interface DemoDividerProps {\n            type?: \"vertical\" | \"horizontal\"\n            size?: number\n            style?: StyleProp<ViewStyle>\n        }\n\n        export function DemoDivider(props: DemoDividerProps) {\n            const { type = \"horizontal\", size = 10, style: $styleOverride } = props\n\n            return (\n              <View\n                  style={[\n                    $divider,\n                    type === \"horizontal\" && { height: size },\n                    type === \"vertical\" && { width: size },\n                    $styleOverride, {/* ${comment} */}\n                  ]}\n              />\n            )\n        }\n\n        const $divider: ViewStyle = {\n            flexGrow: 0,\n            flexShrink: 0, // ${comment}\n        }\n      `\n      const result = removeCurrentLine(contents, comment)\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(comment)\n    })\n\n    it(`should remove line with \"# ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveCurrentLine}\" comment`, () => {\n      const comment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveCurrentLine)\n      const contents = `\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      appId: com.helloworld # ${comment}\n      env:\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      `\n      const result = removeCurrentLine(contents, comment)\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(comment)\n    })\n  })\n  describe(\"removeNextLine\", () => {\n    it(`should remove comment and next line after \"// ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveNextLine}\"`, () => {\n      const comment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveNextLine)\n      const contents = `\n          export * from \"./WelcomeScreen\"\n          export * from \"./LoginScreen\"\n          // ${comment}\n          export * from \"./DemoCommunityScreen\"\n        `\n      const result = removeNextLine(contents, comment)\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(comment)\n      expect(result).not.toContain(\"DemoCommunityScreen\")\n    })\n\n    it(`should remove comment and next line after \"/* ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveNextLine} */\"`, () => {\n      const comment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveNextLine)\n      const contents = `\n          export * from \"./WelcomeScreen\"\n          export * from \"./LoginScreen\"\n          /* ${comment} */\n          export * from \"./DemoCommunityScreen\"\n        `\n      const result = removeNextLine(contents, comment)\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(comment)\n      expect(result).not.toContain(\"DemoCommunityScreen\")\n    })\n\n    it(`should remove comment and next line after \"# ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveNextLine}\"`, () => {\n      const comment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveNextLine)\n      const contents = `\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        # ${comment}\n        appId: com.helloworld\n        env:\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        `\n      const result = removeNextLine(contents, comment)\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(comment)\n      expect(result).not.toContain(\"appId: com.helloworld\")\n    })\n\n    it(`should not modify other lines other than \"// ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveNextLine} and line after\"`, () => {\n      const comment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveNextLine)\n      // simulate whitespace and new lines of file after prettier format\n      const contents = [\n        `export * from \"./DemoIcon\"`,\n        `export * from \"./DemoTextField\"`,\n        `export * from \"./DemoButton\"`,\n        `export * from \"./DemoListItem\"`,\n        `export * from \"./DemoHeader\"`,\n        `// ${comment}`,\n        `export * from \"./DemoAutoImage\"`,\n        `export * from \"./DemoText\"`,\n      ].join(\"\\n\")\n\n      const result = removeNextLine(contents, comment)\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(comment)\n      expect(result).toEqual(\n        [\n          `export * from \"./DemoIcon\"`,\n          `export * from \"./DemoTextField\"`,\n          `export * from \"./DemoButton\"`,\n          `export * from \"./DemoListItem\"`,\n          `export * from \"./DemoHeader\"`,\n          `export * from \"./DemoText\"`,\n        ].join(\"\\n\"),\n      )\n    })\n  })\n  describe(\"removeBlock\", () => {\n    it(`should remove comments and lines between \"// ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveBlockStart}\" and \"// ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveBlockEnd}\"`, () => {\n      const startComment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveBlockStart)\n      const endComment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveBlockEnd)\n      const contents = `\n          export * from \"./WelcomeScreen\"\n          export * from \"./LoginScreen\"\n          // ${startComment}\n          export * from \"./DemoCommunityScreen\"\n          export * from \"./DemoDebugScreen\"\n          export * from \"./DemoShowroomScreen/DemoShowroomScreen\"\n          // ${endComment}\n          export * from \"./ErrorScreen/ErrorBoundary\"\n          // export other screens here\n        `\n      const result = removeBlocks(contents, { start: startComment, end: endComment })\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(startComment)\n      expect(result).not.toContain(endComment)\n      expect(result).not.toContain(\"DemoCommunityScreen\")\n      expect(result).not.toContain(\"DemoDebugScreen\")\n      expect(result).not.toContain(\"DemoShowroomScreen\")\n    })\n\n    it(`should remove comments and lines between \"/* ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveBlockStart} */\" and \"/* ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveBlockEnd} */\"`, () => {\n      const startComment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveBlockStart)\n      const endComment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveBlockEnd)\n      const contents = `\n          export * from \"./WelcomeScreen\"\n          export * from \"./LoginScreen\"\n          /* ${startComment} */\n          export * from \"./DemoCommunityScreen\"\n          export * from \"./DemoDebugScreen\"\n          export * from \"./DemoShowroomScreen/DemoShowroomScreen\"\n          /* ${endComment} */\n          export * from \"./ErrorScreen/ErrorBoundary\"\n          // export other screens here\n        `\n      const result = removeBlocks(contents, { start: startComment, end: endComment })\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(startComment)\n      expect(result).not.toContain(endComment)\n      expect(result).not.toContain(\"DemoCommunityScreen\")\n      expect(result).not.toContain(\"DemoDebugScreen\")\n      expect(result).not.toContain(\"DemoShowroomScreen\")\n    })\n\n    it(`should remove comments and lines between \"# ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveBlockStart}\" and \"# ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveBlockEnd}\"`, () => {\n      const startComment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveBlockStart)\n      const endComment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveBlockEnd)\n      const contents = `\n        # ${startComment}\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        appId: com.helloworld\n        env:\n        TITLE: \"RNR 257 - META RESPONDS! How can we improve React Native, part 2\"\n        FAVORITES_TEXT: \"Switch on to only show favorites\"\n        # ${endComment}\n\n        ---\n        - runFlow: Login.yaml\n        - tapOn: \"Podcast, tab, 3 of 4\"\n        - assertVisible: \"React Native Radio episodes\"\n        `\n      const result = removeBlocks(contents, { start: startComment, end: endComment })\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(startComment)\n      expect(result).not.toContain(endComment)\n      expect(result).not.toContain(\"# flow\")\n      expect(result).not.toContain(\"appId: com.helloworld\")\n      expect(result).not.toContain(\"env:\")\n      expect(result).not.toContain(\"TITLE:\")\n      expect(result).not.toContain(\"FAVORITES_TEXT:\")\n    })\n\n    it(`should remove multiple \"// ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveBlockStart}\" and \"// ${TEST_MARKUP_PREFIX} ${MarkupComments.RemoveBlockEnd}\" sections in the same file string`, () => {\n      const startComment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveBlockStart)\n      const endComment = markupComment(TEST_MARKUP_PREFIX, MarkupComments.RemoveBlockEnd)\n      const contents = [\n        `export * from \"./WelcomeScreen\"`,\n        `// ${startComment}`,\n        `export * from \"./LoginScreen\"`,\n        `// ${endComment}`,\n        `// ${startComment}`,\n        `export * from \"./DemoCommunityScreen\"`,\n        `export * from \"./DemoDebugScreen\"`,\n        `export * from \"./DemoShowroomScreen/DemoShowroomScreen\"`,\n        `// ${endComment}`,\n        `export * from \"./ErrorScreen/ErrorBoundary\"`,\n        `// export other screens here'`,\n      ].join(\"\\n\")\n\n      const result = removeBlocks(contents, { start: startComment, end: endComment })\n\n      expect(result).toEqual(\n        [\n          `export * from \"./WelcomeScreen\"`,\n          `export * from \"./ErrorScreen/ErrorBoundary\"`,\n          `// export other screens here'`,\n        ].join(\"\\n\"),\n      )\n      expect(result).not.toContain(startComment)\n      expect(result).not.toContain(endComment)\n    })\n  })\n\n  describe(\"remove\", () => {\n    const removeMarkupPrefix = \"@demo\"\n    it(\"should remove all comments in WelcomeScreen\", () => {\n      const blockStartComment = markupComment(removeMarkupPrefix, MarkupComments.RemoveBlockStart)\n      const blockEndComment = markupComment(removeMarkupPrefix, MarkupComments.RemoveBlockEnd)\n      const currentLineComment = markupComment(removeMarkupPrefix, MarkupComments.RemoveCurrentLine)\n\n      const result = updateFile(WelcomeScreen, removeMarkupPrefix)\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(blockStartComment)\n      expect(result).not.toContain(blockEndComment)\n      expect(result).not.toContain(currentLineComment)\n      expect(result).not.toContain(\n        /* jsx */ `<Button preset=\"reversed\" tx=\"welcomeScreen:letsGo\" onPress={goNext} />`,\n      )\n      expect(result).not.toContain(`props: WelcomeScreenProps`)\n      expect(result).not.toContain(`goNext()`)\n    })\n\n    it(\"should remove all comments in AppNavigator\", () => {\n      const blockStartComment = markupComment(removeMarkupPrefix, MarkupComments.RemoveBlockStart)\n      const blockEndComment = markupComment(removeMarkupPrefix, MarkupComments.RemoveBlockEnd)\n      const currentLineComment = markupComment(removeMarkupPrefix, MarkupComments.RemoveCurrentLine)\n\n      const result = updateFile(AppNavigator, removeMarkupPrefix)\n      expect(result).toMatchSnapshot()\n      expect(result).not.toContain(blockStartComment)\n      expect(result).not.toContain(blockEndComment)\n      expect(result).not.toContain(currentLineComment)\n      expect(result).not.toContain(`NavigatorScreenParams`)\n      expect(result).not.toContain(\n        `import { DemoNavigator, DemoTabParamList } from \"./DemoNavigator\" \"`,\n      )\n      expect(result).not.toContain(\") : (\")\n    })\n  })\n})\n\nconst WelcomeScreen = /* jsx */ `\nimport { FC } from \"react\"\nimport { Image, ImageStyle, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Button } from \"@/components/Button\" // @demo remove-current-line\nimport { Screen } from \"@/components/Screen\"\nimport { Text } from \"@/components/Text\"\nimport { useAuth } from \"@/context/AuthContext\" // @demo remove-current-line\nimport { isRTL } from \"@/i18n\"\nimport type { AppStackScreenProps } from \"@/navigators/AppNavigator\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { useAppTheme } from \"@/theme/context\"\nimport { $styles } from \"@/theme/styles\"\nimport { useHeader } from \"@/utils/useHeader\" // @demo remove-current-line\nimport { useSafeAreaInsetsStyle } from \"@/utils/useSafeAreaInsetsStyle\"\n\nconst welcomeLogo = require(\"@assets/images/logo.png\")\nconst welcomeFace = require(\"@assets/images/welcome-face.png\")\n\ninterface WelcomeScreenProps extends AppStackScreenProps<\"Welcome\"> {}\n\nexport const WelcomeScreen: FC<WelcomeScreenProps> = (\n  _props, // @demo remove-current-line\n) => {\n  const { themed, theme } = useAppTheme()\n  // @demo remove-block-start\n  const { navigation } = _props\n  const { logout } = useAuth()\n\n  function goNext() {\n    navigation.navigate(\"Demo\", { screen: \"DemoShowroom\", params: {} })\n  }\n\n  useHeader(\n    {\n      rightTx: \"common:logOut\",\n      onRightPress: logout,\n    },\n    [logout],\n  )\n  // @demo remove-block-end\n\n  const $bottomContainerInsets = useSafeAreaInsetsStyle([\"bottom\"])\n\n  return (\n    <Screen preset=\"fixed\" contentContainerStyle={$styles.flex1}>\n      <View style={themed($topContainer)}>\n        <Image style={themed($welcomeLogo)} source={welcomeLogo} resizeMode=\"contain\" />\n        <Text\n          testID=\"welcome-heading\"\n          style={themed($welcomeHeading)}\n          tx=\"welcomeScreen:readyForLaunch\"\n          preset=\"heading\"\n        />\n        <Text tx=\"welcomeScreen:exciting\" preset=\"subheading\" />\n        <Image\n          style={$welcomeFace}\n          source={welcomeFace}\n          resizeMode=\"contain\"\n          tintColor={theme.colors.palette.neutral900}\n        />\n      </View>\n\n      <View style={themed([$bottomContainer, $bottomContainerInsets])}>\n        <Text tx=\"welcomeScreen:postscript\" size=\"md\" />\n        {/* @demo remove-block-start */}\n        <Button\n          testID=\"next-screen-button\"\n          preset=\"reversed\"\n          tx=\"welcomeScreen:letsGo\"\n          onPress={goNext}\n        />\n        {/* @demo remove-block-end */}\n      </View>\n    </Screen>\n  )\n}\n\nconst $topContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({\n  flexShrink: 1,\n  flexGrow: 1,\n  flexBasis: \"57%\",\n  justifyContent: \"center\",\n  paddingHorizontal: spacing.lg,\n})\n\nconst $bottomContainer: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({\n  flexShrink: 1,\n  flexGrow: 0,\n  flexBasis: \"43%\",\n  backgroundColor: colors.palette.neutral100,\n  borderTopLeftRadius: 16,\n  borderTopRightRadius: 16,\n  paddingHorizontal: spacing.lg,\n  justifyContent: \"space-around\",\n})\n\nconst $welcomeLogo: ThemedStyle<ImageStyle> = ({ spacing }) => ({\n  height: 88,\n  width: \"100%\",\n  marginBottom: spacing.xxl,\n})\n\nconst $welcomeFace: ImageStyle = {\n  height: 169,\n  width: 269,\n  position: \"absolute\",\n  bottom: -47,\n  right: -80,\n  transform: [{ scaleX: isRTL ? -1 : 1 }],\n}\n\nconst $welcomeHeading: ThemedStyle<TextStyle> = ({ spacing }) => ({\n  marginBottom: spacing.md,\n})\n`\nconst AppNavigator = /* jsx */ `\n/**\n * The app navigator (formerly \"AppNavigator\" and \"MainNavigator\") is used for the primary\n * navigation flows of your app.\n * Generally speaking, it will contain an auth flow (registration, login, forgot password)\n * and a \"main\" flow which the user will use once logged in.\n */\nimport { ComponentProps } from \"react\"\nimport {\n  NavigationContainer,\n  NavigatorScreenParams, // @demo remove-current-line\n} from \"@react-navigation/native\"\nimport { createNativeStackNavigator, NativeStackScreenProps } from \"@react-navigation/native-stack\"\n\nimport Config from \"@/config\"\nimport { useAuth } from \"@/context/AuthContext\" // @demo remove-current-line\nimport { ErrorBoundary } from \"@/screens/ErrorScreen/ErrorBoundary\"\nimport { LoginScreen } from \"@/screens/LoginScreen\" // @demo remove-current-line\nimport { WelcomeScreen } from \"@/screens/WelcomeScreen\"\nimport { useAppTheme } from \"@/theme/context\"\n\nimport { DemoNavigator, DemoTabParamList } from \"./DemoNavigator\" // @demo remove-current-line\nimport { navigationRef, useBackButtonHandler } from \"./navigationUtilities\"\n\n/**\n * This type allows TypeScript to know what routes are defined in this navigator\n * as well as what properties (if any) they might take when navigating to them.\n *\n * For more information, see this documentation:\n *   https://reactnavigation.org/docs/params/\n *   https://reactnavigation.org/docs/typescript#type-checking-the-navigator\n *   https://reactnavigation.org/docs/typescript/#organizing-types\n */\nexport type AppStackParamList = {\n  Welcome: undefined\n  Login: undefined // @demo remove-current-line\n  Demo: NavigatorScreenParams<DemoTabParamList> // @demo remove-current-line\n  // 🔥 Your screens go here\n  // IGNITE_GENERATOR_ANCHOR_APP_STACK_PARAM_LIST\n}\n\n/**\n * This is a list of all the route names that will exit the app if the back button\n * is pressed while in that screen. Only affects Android.\n */\nconst exitRoutes = Config.exitRoutes\n\nexport type AppStackScreenProps<T extends keyof AppStackParamList> = NativeStackScreenProps<\n  AppStackParamList,\n  T\n>\n\n// Documentation: https://reactnavigation.org/docs/stack-navigator/\nconst Stack = createNativeStackNavigator<AppStackParamList>()\n\nconst AppStack = () => {\n  // @demo remove-block-start\n  const { isAuthenticated } = useAuth()\n  // @demo remove-block-end\n  const {\n    theme: { colors },\n  } = useAppTheme()\n\n  return (\n    <Stack.Navigator\n      screenOptions={{\n        headerShown: false,\n        navigationBarColor: colors.background,\n        contentStyle: {\n          backgroundColor: colors.background,\n        },\n      }}\n      initialRouteName={isAuthenticated ? \"Welcome\" : \"Login\"} // @demo remove-current-line\n    >\n      {/* @demo remove-block-start */}\n      {isAuthenticated ? (\n        <>\n          {/* @demo remove-block-end */}\n          <Stack.Screen name=\"Welcome\" component={WelcomeScreen} />\n          {/* @demo remove-block-start */}\n          <Stack.Screen name=\"Demo\" component={DemoNavigator} />\n        </>\n      ) : (\n        <>\n          <Stack.Screen name=\"Login\" component={LoginScreen} />\n        </>\n      )}\n      {/* @demo remove-block-end */}\n      {/** 🔥 Your screens go here */}\n      {/* IGNITE_GENERATOR_ANCHOR_APP_STACK_SCREENS */}\n    </Stack.Navigator>\n  )\n}\n\nexport interface NavigationProps\n  extends Partial<ComponentProps<typeof NavigationContainer<AppStackParamList>>> {}\n\nexport const AppNavigator = (props: NavigationProps) => {\n  const { navigationTheme } = useAppTheme()\n\n  useBackButtonHandler((routeName) => exitRoutes.includes(routeName))\n\n  return (\n    <NavigationContainer ref={navigationRef} theme={navigationTheme} {...props}>\n      <ErrorBoundary catchErrors={Config.catchErrors}>\n        <AppStack />\n      </ErrorBoundary>\n    </NavigationContainer>\n  )\n}\n`\n"
  },
  {
    "path": "src/tools/markup.ts",
    "content": "import { filesystem, patching } from \"gluegun\"\nimport * as pathlib from \"path\"\n\n// all possible comment types\nexport enum MarkupComments {\n  RemoveCurrentLine = \"remove-current-line\",\n  RemoveNextLine = \"remove-next-line\",\n  RemoveBlockStart = \"remove-block-start\",\n  RemoveBlockEnd = \"remove-block-end\",\n  RemoveFile = \"remove-file\",\n  ReplaceNextLine = \"replace-next-line\",\n}\n\n// markup comments follow format:\n// // @prefix ActionName\nexport const markupComment = (prefix: string, commentType: MarkupComments) =>\n  `${prefix} ${commentType}`\n\nexport const markupRegex = (prefix: string) => {\n  const pattern = `(\\\\/\\\\/|#)\\\\s*${prefix}.*|{?\\\\/.*${prefix}.*\\\\/}?`\n  return new RegExp(pattern, \"gm\")\n}\n\n/**\n * Take the file content as a string and remove any\n * line of code with an `// @x remove-current-line` comment\n */\nexport function removeCurrentLine(contents: string, comment: string): string {\n  const lines = contents.split(\"\\n\")\n  const result = lines.filter((line) => !line.includes(comment))\n  return result.join(\"\\n\")\n}\n\n/**\n * Take the file content as a string and remove the next line\n * of code with the REMOVE_NEXT_LINE comment before it\n */\nexport function removeNextLine(contents: string, comment: string): string {\n  const lines = contents.split(\"\\n\")\n  const result = lines.filter((line, index) => {\n    const prevLine = lines[index - 1]\n\n    const preserveCurrent = line.includes(comment) === false\n    const preservePrevious = prevLine !== undefined && prevLine.includes(comment) === false\n\n    if (index === 0) {\n      // if we are on the first line, there is no previous line to check\n      return preserveCurrent\n    }\n\n    // keep current line if there is no comment in current or previous line\n    const keepLine = preserveCurrent && preservePrevious\n    return keepLine\n  })\n  return result.join(\"\\n\")\n}\n\n/**\n * Take the file content as a string and replace the current line\n * of code with the contents of the REPLACE_NEXT_LINE comment before it\n * example: // @prefix replace-next-line const newLine = \"new line\"\n */\nexport function replaceNextLine(contents: string, comment: string): string {\n  const lines = contents.split(\"\\n\")\n  const result = lines.map((line, index) => {\n    const prevLine = lines[index - 1]\n    if (prevLine?.includes(comment)) {\n      const newLineContent = prevLine.replace(\"//\", \"\").replace(comment, \"\").trim()\n      return newLineContent\n    } else {\n      return line\n    }\n  })\n  return result.join(\"\\n\")\n}\n\n/**\n * Take the file content as a string and remove the lines of code between\n * start and end block comments\n */\nexport function removeBlocks(contents: string, comment: { start: string; end: string }): string {\n  const { start, end } = comment\n  const lines = contents.split(\"\\n\")\n\n  const findIndex = (l: typeof lines, c: typeof start | typeof end) =>\n    l.findIndex((line) => line.includes(c))\n  const NOT_FOUND = -1\n\n  const blockStartIndex = findIndex(lines, start)\n  const blockEndIndex = findIndex(lines, end)\n  const blockExists = findIndex(lines, start) !== NOT_FOUND && blockEndIndex !== NOT_FOUND\n\n  if (blockExists) {\n    const blockLength = blockEndIndex - blockStartIndex + 1\n    lines.splice(blockStartIndex, blockLength) // mutates `lines`\n  }\n\n  const updateContents = lines.join(\"\\n\")\n\n  const anotherBlockExists =\n    findIndex(lines, start) !== NOT_FOUND && findIndex(lines, end) !== NOT_FOUND\n  if (anotherBlockExists) {\n    return removeBlocks(updateContents, comment)\n  }\n\n  return updateContents\n}\n\n/**\n * Perform all operations possible in a file\n * @param contents The file contents as a string\n * @return The file contents with all remove operations performed\n */\nexport function updateFile(contents: string, markupPrefix: string): string {\n  let result = contents\n  result = removeBlocks(result, {\n    start: markupComment(markupPrefix, MarkupComments.RemoveBlockStart),\n    end: markupComment(markupPrefix, MarkupComments.RemoveBlockEnd),\n  })\n  result = removeCurrentLine(result, markupComment(markupPrefix, MarkupComments.RemoveCurrentLine))\n  result = removeNextLine(result, markupComment(markupPrefix, MarkupComments.RemoveNextLine))\n  result = replaceNextLine(result, markupComment(markupPrefix, MarkupComments.ReplaceNextLine))\n\n  return result\n}\n\nconst DEFAULT_MATCHING_GLOBS = [\n  \"!**/.yarn{,/**}\",\n  \"!**/.DS_Store\",\n  \"!**/.expo{,/**}\",\n  \"!**/.git{,/**}\",\n  \"!**/.vscode{,/**}\",\n  \"!**/node_modules{,/**}\",\n  \"!**/ios/build{,/**}\",\n  \"!**/ios/Pods{,/**}\",\n  \"!**/ios/*.xcworkspace{,/**}\",\n  \"!**/android/build{,/**}\",\n  \"!**/android/app/build{,/**}\",\n]\n\n/**\n * Perform replace on all types of markup\n * @param contents The file contents as a string\n * @return The file contents with all CommentType removed\n */\n\nexport function findFiles(targetDir: string, matching?: string[]) {\n  const filePaths = filesystem\n    .cwd(targetDir)\n    .find({\n      matching: matching ?? DEFAULT_MATCHING_GLOBS,\n      recursive: true,\n      files: true,\n      directories: false,\n    })\n    .map((path) => pathlib.join(targetDir, path))\n  return filePaths\n}\n\nexport function removeEmptyDirs({\n  targetDir,\n  dryRun,\n  matching = DEFAULT_MATCHING_GLOBS,\n}: {\n  targetDir: string\n  dryRun: boolean\n  matching?: string[]\n}) {\n  const removedDirs: string[] = []\n  const getEmptyDirPaths = () =>\n    filesystem\n      .cwd(targetDir)\n      .find({\n        matching,\n        recursive: true,\n        files: false,\n        directories: true,\n      })\n      .map((path) => pathlib.join(targetDir, path))\n      .filter((path) => !filesystem.list(path)?.length)\n\n  let emptyDirPaths = getEmptyDirPaths()\n  while (emptyDirPaths.length > 0) {\n    emptyDirPaths.forEach((path) => {\n      if (!dryRun) filesystem.remove(path)\n      removedDirs.push(path)\n    })\n    emptyDirPaths = getEmptyDirPaths()\n  }\n\n  return removedDirs\n}\n\nexport async function deleteFiles({\n  filePaths,\n  comment,\n  dryRun = true,\n}: {\n  filePaths: string[]\n  comment: string\n  dryRun?: boolean\n}) {\n  const commentResults = await Promise.allSettled(\n    filePaths.map(async (path) => {\n      const { remove } = filesystem\n      const { exists } = patching\n      if (await exists(path, comment)) {\n        if (!dryRun) {\n          remove(path)\n        }\n        return { path }\n      }\n    }),\n  )\n\n  return commentResults\n}\n\nexport async function updateFiles({\n  filePaths,\n  markupPrefix,\n  dryRun = true,\n  removeMarkupOnly = false,\n}: {\n  filePaths: string[]\n  markupPrefix: string\n  dryRun?: boolean\n  removeMarkupOnly?: boolean\n}) {\n  const sanitize = (contents: string) => {\n    return contents.replace(markupRegex(markupPrefix), \"\")\n  }\n\n  // Go through every file path and handle the operation for each comment\n  const commentResults = await Promise.allSettled(\n    filePaths.map(async (path) => {\n      const { exists, update } = patching\n      const { read } = filesystem\n\n      const comments: string[] = []\n\n      // remove files first\n      if (await exists(path, markupComment(markupPrefix, MarkupComments.RemoveFile))) {\n        console.log(\"removing file\", path)\n        if (!dryRun) {\n          if (removeMarkupOnly) {\n            const contents = read(path)\n            const sanitized = sanitize(contents)\n            filesystem.write(path, sanitized)\n          } else {\n            filesystem.remove(path)\n          }\n        }\n        comments.push(MarkupComments.RemoveFile)\n        return { path, comments }\n      }\n\n      // filter out RemoveFile (weve already handled it above)\n      // and create a regex for the remaining comment types\n      const operationComments = Object.keys(MarkupComments)\n        .filter((key) => MarkupComments[key] !== MarkupComments.RemoveFile)\n        .map((key) => markupComment(markupPrefix, MarkupComments[key]))\n\n      const shouldUpdate = removeMarkupOnly\n        ? markupRegex(markupPrefix)\n        : RegExp(operationComments.join(\"|\"), \"g\")\n\n      if (await exists(path, shouldUpdate)) {\n        const before = read(path)\n\n        operationComments.forEach((operation) => {\n          if (before.includes(operation)) {\n            comments.push(operation)\n          }\n        })\n\n        if (!dryRun) {\n          await update(path, (contents: string) => {\n            let result = contents\n            if (removeMarkupOnly) {\n              result = sanitize(result)\n            } else {\n              result = updateFile(result, markupPrefix)\n              result = sanitize(result)\n            }\n            return result\n          })\n        }\n      }\n\n      return { path, comments }\n    }),\n  )\n\n  return commentResults\n}\n"
  },
  {
    "path": "src/tools/packager.test.ts",
    "content": "import { list } from \"./packager\"\n\ndescribe(\"packager\", () => {\n  describe(\"list\", () => {\n    describe(\"npm\", () => {\n      it(\"should handle non-json input to parseFn\", () => {\n        const [cmd, parseFn] = list({ packagerName: \"npm\" })\n\n        expect(cmd.includes(\"npm\")).toBe(true)\n\n        const input = `\n            npm WARN config global \\`--global\\`, \\`--local\\` are deprecated. Use \\`--location=global\\` instead.\n            npm WARN config global \\`--global\\`, \\`--local\\` are deprecated. Use \\`--location=global\\` instead.\n            {\n                \"resolved\": \"file:../../Roaming/fnm/node-versions/v16.16.0/installation\",\n                \"dependencies\": {\n                    \"corepack\": {\n                        \"version\": \"0.10.0\"\n                    },\n                    \"expo-cli\": {\n                        \"version\": \"6.0.1\"\n                    },\n                    \"npm\": {\n                        \"version\": \"8.11.0\"\n                    }\n                }\n            }\n        `\n\n        expect(() => parseFn(input)).not.toThrow()\n      })\n\n      it(\"should handle transforming input json string to expected shape\", () => {\n        const [cmd, parseFn] = list({ packagerName: \"npm\" })\n\n        expect(cmd.includes(\"npm\")).toBe(true)\n\n        const input = `\n            {\n                \"resolved\": \"file:../../Roaming/fnm/node-versions/v16.16.0/installation\",\n                \"dependencies\": {\n                    \"corepack\": {\n                        \"version\": \"0.10.0\"\n                    },\n                    \"expo-cli\": {\n                        \"version\": \"6.0.1\"\n                    },\n                    \"npm\": {\n                        \"version\": \"8.11.0\"\n                    }\n                }\n            }\n        `\n\n        expect(parseFn(input)).toStrictEqual([\n          [\"corepack\", \"0.10.0\"],\n          [\"expo-cli\", \"6.0.1\"],\n          [\"npm\", \"8.11.0\"],\n        ])\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/tools/packager.ts",
    "content": "import { system } from \"gluegun\"\n\nimport { spawnProgress } from \"./spawn\"\n\n// we really need a packager core extension on Gluegun\n// in the meantime, we'll use this hacked together version\n\nexport type PackagerName = \"npm\" | \"yarn\" | \"pnpm\" | \"bun\"\ntype PackageOptions = {\n  packagerName?: PackagerName\n  dev?: boolean\n  global?: boolean\n  silent?: boolean\n}\n\ntype PackageRunOptions = PackageOptions & {\n  onProgress?: (out: string) => void\n}\nconst packageInstallOptions: PackageRunOptions = {\n  dev: false,\n  onProgress: (out: string) => console.log(out),\n}\n\nconst packageListOptions: PackageOptions = {\n  global: false,\n}\n\nlet isYarn\nfunction yarnAvailable() {\n  if (isYarn !== undefined) return isYarn\n  isYarn = Boolean(system.which(\"yarn\"))\n  return isYarn\n}\n\nlet isPnpm\nfunction pnpmAvailable() {\n  if (isPnpm !== undefined) return isPnpm\n  isPnpm = Boolean(system.which(\"pnpm\"))\n  return isPnpm\n}\n\nlet isBun\nfunction bunAvailable() {\n  if (isBun !== undefined) return isBun\n  isBun = Boolean(system.which(\"bun\"))\n  return isBun\n}\n\nfunction detectPackager(): PackagerName {\n  if (yarnAvailable()) {\n    return \"yarn\"\n  } else if (pnpmAvailable()) {\n    return \"pnpm\"\n  } else if (bunAvailable()) {\n    return \"bun\"\n  } else {\n    return \"npm\"\n  }\n}\n\nfunction availablePackagers(): PackagerName[] {\n  const packagers: PackagerName[] = [\"npm\"]\n\n  if (yarnAvailable()) {\n    packagers.push(\"yarn\")\n  }\n  if (pnpmAvailable()) {\n    packagers.unshift(\"pnpm\") // pnpm should be first in the list if it's available\n  }\n\n  if (bunAvailable()) {\n    packagers.push(\"bun\")\n  }\n\n  return packagers\n}\n\n/**\n *\n * Returns a string command to run a generic install with a packager of your choice (or auto-detects).\n *\n * For example, `yarn add ramda` or `npm install ramda`.\n *\n */\nfunction addCmd(pkg: string, options: PackageRunOptions = packageInstallOptions) {\n  const silent = options.silent ? \" --silent\" : \"\"\n\n  let cmd\n\n  if (options.packagerName === \"pnpm\") {\n    cmd = `pnpm install`\n  } else if (options.packagerName === \"yarn\") {\n    cmd = `yarn add`\n  } else if (options.packagerName === \"npm\") {\n    cmd = `npm install`\n  } else if (options.packagerName === \"bun\") {\n    cmd = `bun add`\n  } else {\n    // neither expo nor a packagerName was provided, so let's detect one\n    return addCmd(pkg, { ...options, packagerName: detectPackager() })\n  }\n\n  return `${cmd} ${pkg}${options.dev ? \" --save-dev\" : \"\"}${silent}`\n}\n\n/**\n *\n * Returns a string command to remove a package with a packager of your choice (or auto-detects).\n *\n * For example, `yarn remove ramda` or `npm uninstall ramda`.\n *\n */\nfunction removeCmd(pkg: string, options: PackageOptions = packageInstallOptions) {\n  const silent = options.silent ? \" --silent\" : \"\"\n\n  let cmd\n\n  if (options.packagerName === \"pnpm\") {\n    cmd = \"pnpm uninstall\"\n  } else if (options.packagerName === \"yarn\") {\n    cmd = `yarn remove`\n  } else if (options.packagerName === \"npm\") {\n    cmd = `npm uninstall`\n  } else if (options.packagerName === \"bun\") {\n    cmd = `bun remove`\n  } else {\n    // neither expo nor a packagerName was provided, so let's detect one\n    return removeCmd(pkg, { ...options, packagerName: detectPackager() })\n  }\n\n  return `${cmd} ${pkg}${options.dev ? \" --save-dev\" : \"\"}${silent}`\n}\n\n/**\n *\n * Returns a string command to run a generic install with a packager of your choice (or auto-detects).\n *\n * For example, `yarn install` or `npm install`.\n *\n */\nfunction installCmd(options: PackageRunOptions) {\n  const silent = options.silent ? \" --silent\" : \"\"\n\n  if (options.packagerName === \"pnpm\") {\n    return `pnpm install${silent}`\n  } else if (options.packagerName === \"yarn\") {\n    return `yarn install${silent}`\n  } else if (options.packagerName === \"npm\") {\n    return `npm install${silent} --legacy-peer-deps`\n  } else if (options.packagerName === \"bun\") {\n    return `bun install${silent}`\n  } else {\n    return installCmd({ ...options, packagerName: detectPackager() })\n  }\n}\n\ntype PackageListOutput = [string, (string) => [string, string][]]\nexport function list(options: PackageOptions = packageListOptions): PackageListOutput {\n  if (options.packagerName === \"pnpm\") {\n    // TODO: pnpm list?\n    throw new Error(\"pnpm list is not supported yet\")\n  } else if (options.packagerName === \"bun\") {\n    return [\n      // TODO do we need to add --global here?\n      `bun pm ls`,\n      (output: string): [string, string][] => {\n        // Parse yarn's human-readable output\n        return output\n          .split(\"\\n\")\n          .reduce((acc: [string, string][], line: string): [string, string][] => {\n            const match = line.match(/info \"([^@]+)@([^\"]+)\" has binaries/)\n            return match ? [...acc, [match[1], match[2]]] : acc\n          }, [])\n      },\n    ]\n  } else if (\n    options.packagerName === \"yarn\" ||\n    (options.packagerName === undefined && yarnAvailable())\n  ) {\n    return [\n      `yarn${options.global ? \" global\" : \"\"} list`,\n      (output: string): [string, string][] => {\n        // Parse yarn's human-readable output\n        return output\n          .split(\"\\n\")\n          .reduce((acc: [string, string][], line: string): [string, string][] => {\n            const match = line.match(/info \"([^@]+)@([^\"]+)\" has binaries/)\n            return match ? [...acc, [match[1], match[2]]] : acc\n          }, [])\n      },\n    ]\n  } else {\n    return [\n      `npm list${options.global ? \" --global\" : \"\"} --depth=0 --json`,\n      (output: string): [string, string][] => {\n        // npm returns a single JSON blob with a \"dependencies\" key\n        // however, sometimes npm can emit warning messages prepended to json output\n        const json = JSON.parse(output.replace(/npm WARN.+/g, \"\"))\n        return Object.keys(json.dependencies || []).map((key: string): [string, string] => [\n          key,\n          json.dependencies[key].version,\n        ])\n      },\n    ]\n  }\n}\n\n/**\n * Returns a string command to run a script via a packager of your choice.\n */\nfunction runCmd(command: string, options: PackageOptions) {\n  const silent = options.silent ? \" --silent\" : \"\"\n  if (options.packagerName === \"pnpm\") {\n    return `pnpm run ${command}${silent}`\n  } else if (options.packagerName === \"yarn\") {\n    return `yarn ${command}${silent}`\n  } else if (options.packagerName === \"bun\") {\n    return `bun run ${command}`\n  } else {\n    // defaults to npm run\n    return `npm run ${command}${silent}`\n  }\n}\n\nexport const packager = {\n  run: async (command: string, options: PackageRunOptions = packageInstallOptions) => {\n    return spawnProgress(`${runCmd(command, options)}`, {\n      onProgress: options.onProgress,\n    })\n  },\n  add: async (pkg: string, options: PackageRunOptions = packageInstallOptions) => {\n    const cmd = addCmd(pkg, options)\n    return spawnProgress(cmd, { onProgress: options.onProgress })\n  },\n  remove: async (pkg: string, options: PackageRunOptions = packageInstallOptions) => {\n    const cmd = removeCmd(pkg, options)\n    return spawnProgress(cmd, { onProgress: options.onProgress })\n  },\n  install: async (options: PackageRunOptions = packageInstallOptions) => {\n    const cmd = installCmd(options)\n    return spawnProgress(cmd, { onProgress: options.onProgress })\n  },\n  list: async (options: PackageOptions = packageListOptions) => {\n    const [cmd, parseFn] = list(options)\n    return parseFn(await spawnProgress(cmd, {}))\n  },\n  has: (packageManager: \"yarn\" | \"npm\" | \"pnpm\" | \"bun\"): boolean => {\n    if (packageManager === \"yarn\") return yarnAvailable()\n    if (packageManager === \"pnpm\") return pnpmAvailable()\n    if (packageManager === \"bun\") return bunAvailable()\n    return true\n  },\n  detectPackager,\n  runCmd,\n  addCmd,\n  installCmd,\n  availablePackagers,\n}\n"
  },
  {
    "path": "src/tools/pretty.ts",
    "content": "import { print } from \"gluegun\"\n\nimport { asset, AssetName } from \"../assets\"\nimport type { PackagerName } from \"./packager\"\n\nconst { bgRed, bgWhite, underline, gray, white, bold, red, yellow } = print.colors\n\nexport const INDENT = \"   \"\n\nexport const p = (m = \"\") => print.info(gray(INDENT + m))\n\nexport const heading = (m = \"\") => p(white(bold(m)))\n\nexport const command = (\n  m: string | { m: string; width: number } = \"\",\n  second = \"\",\n  examples: string[] = [],\n) => {\n  m = typeof m === \"string\" ? m : m.m + \" \".repeat(m.width - m.m.length)\n  p(white(m) + \"  \" + gray(second))\n  const indent = m.length + 2\n  if (examples) {\n    examples.forEach((ex) => p(gray(\" \".repeat(indent) + white(ex))))\n  }\n}\n\nexport const direction = (m = \"\") => p(red(m))\n\nexport const warning = (m = \"\") => p(yellow(m))\n\nexport const igniteHeading = () =>\n  p(\n    red(\n      bold(\n        \"· · · · · · · · · · · · · · · · · · 🔥 Ignite 🔥 · · · · · · · · · · · · · · · · · ·\\n\",\n      ),\n    ),\n  )\n\nexport const ascii = (assetname: AssetName) => {\n  console.log(\n    asset\n      .get(assetname)\n      .split(\"\\n\")\n      .map((line) => INDENT + line)\n      .join(\"\\n\"),\n  )\n}\n\nexport const hr = () => p(` ────────────────────────────────────────────────────`)\n\nexport const prettyprint = {\n  ascii,\n  command,\n  direction,\n  heading,\n  hr,\n  igniteHeading,\n  p,\n  warning,\n}\n\n/**\n * enquirer style customization\n * @see https://github.dev/enquirer/enquirer/blob/36785f3399a41cd61e9d28d1eb9c2fcd73d69b4c/examples/select/option-elements.js#L19\n */\nexport const prefix = (state: { status: \"pending\" | \"submitted\" | \"canceled\" }): string => {\n  return {\n    pending: \"📝\",\n    submitted: \"✅\",\n    cancelled: \"❌\",\n  }[state.status]\n}\n\n/** Format displayed messages for prompts */\nexport const format = {\n  /** Format boolean values for human on prompts  */\n  boolean: (value: string): string | Promise<string> => {\n    return value ? \"Yes\" : \"No\"\n  },\n}\n\nexport const prettyPrompt = {\n  prefix,\n  format,\n}\n\ntype Spinner = ReturnType<typeof print.spin>\nconst spinners: { [key: string]: Spinner } = {}\n\nexport const startSpinner = (m = \"\") => {\n  let spinner = spinners[m]\n  if (!spinner) {\n    spinner = print.spin({ prefixText: INDENT, text: gray(m) })\n    spinners[m] = spinner\n  }\n  return spinner\n}\n\nexport const stopSpinner = (m: string, symbol: string) => {\n  const spinner = spinners[m]\n  if (spinner) {\n    spinner.stopAndPersist({ symbol })\n    delete spinners[m]\n  }\n}\n\nexport const stopLastSpinner = (symbol: string) => {\n  const lastKey = Object.keys(spinners).pop()\n  if (lastKey) {\n    const lastSpinner = spinners[lastKey]\n    lastSpinner.stopAndPersist({ symbol })\n    delete spinners[lastKey]\n  }\n}\n\nexport const clearSpinners = () => {\n  Object.keys(spinners).forEach((m) => {\n    spinners[m].stop()\n    delete spinners[m]\n  })\n}\n\nexport const spinner = {\n  start: startSpinner,\n  stop: stopSpinner,\n  stopLast: stopLastSpinner,\n  clear: clearSpinners,\n} as const\n\nexport const link = (m = \"\") => underline(white(m))\n\nexport const em = (m = \"\") => bold(white(m))\n\nexport const ir = (m = \"\") => bgRed(bold(white(m)))\n\nexport const highlight = (m = \"\") => bold(bgWhite(red(m)))\n\nexport const pkgColor = (packagerName: PackagerName) => {\n  const packagerColors: Record<PackagerName, keyof typeof print.colors> = {\n    npm: \"red\",\n    yarn: \"blue\",\n    pnpm: \"yellow\",\n    bun: \"cyan\",\n  }\n  return print.colors[packagerColors[packagerName]] as (text: string) => string\n}\n\nexport const theme = {\n  em,\n  highlight,\n  link,\n  ir,\n}\n"
  },
  {
    "path": "src/tools/react-native.test.ts",
    "content": "import { filesystem } from \"gluegun\"\nimport * as tempy from \"tempy\"\n\nimport { updatePackagerCommandsInReadme } from \"./react-native\"\n\nconst EXAMPLE_README = `\npnpm run\npnpm run start\npnpm run build:ios:sim\npnpm run build:ios:device\npnpm run build:ios:prod\n`\n\ndescribe(\"react native\", () => {\n  describe(\"updatePackagerCommandsInReadme\", () => {\n    let tempDir: string\n\n    beforeEach(() => {\n      tempDir = tempy.directory({ prefix: \"ignite-\" })\n      filesystem.write(`${tempDir}/README.md`, EXAMPLE_README)\n    })\n\n    afterEach(() => {\n      filesystem.remove(tempDir) // clean up our mess\n    })\n\n    it(\"should update to npm\", () => {\n      const readmePath = filesystem.path(tempDir, \"README.md\")\n\n      updatePackagerCommandsInReadme(readmePath, \"npm\")\n\n      const results = filesystem.read(readmePath)\n      const expectedResults = `\nnpm install --legacy-peer-deps\nnpm run start\nnpm run build:ios:sim\nnpm run build:ios:device\nnpm run build:ios:prod\n`\n\n      expect(results).toBe(expectedResults)\n    })\n\n    it(\"should update to yarn\", () => {\n      const readmePath = filesystem.path(tempDir, \"README.md\")\n\n      updatePackagerCommandsInReadme(readmePath, \"yarn\")\n\n      const results = filesystem.read(readmePath)\n      const expectedResults = `\nyarn install\nyarn start\nyarn build:ios:sim\nyarn build:ios:device\nyarn build:ios:prod\n`\n\n      expect(results).toBe(expectedResults)\n    })\n    it(\"should update to pnpm\", () => {\n      const readmePath = filesystem.path(tempDir, \"README.md\")\n\n      updatePackagerCommandsInReadme(readmePath, \"pnpm\")\n\n      const results = filesystem.read(readmePath)\n      const expectedResults = `\npnpm install\npnpm run start\npnpm run build:ios:sim\npnpm run build:ios:device\npnpm run build:ios:prod\n`\n\n      expect(results).toBe(expectedResults)\n    })\n    it(\"should update to bun\", () => {\n      const readmePath = filesystem.path(tempDir, \"README.md\")\n\n      updatePackagerCommandsInReadme(readmePath, \"bun\")\n\n      const results = filesystem.read(readmePath)\n      const expectedResults = `\nbun install\nbun run start\nbun run build:ios:sim\nbun run build:ios:device\nbun run build:ios:prod\n`\n\n      expect(results).toBe(expectedResults)\n    })\n  })\n})\n"
  },
  {
    "path": "src/tools/react-native.ts",
    "content": "import { filesystem, GluegunToolbox } from \"gluegun\"\n\nimport { children } from \"./filesystem-ext\"\nimport { boolFlag } from \"./flag\"\nimport { packager, PackagerName } from \"./packager\"\n\nexport const isAndroidInstalled = (toolbox: GluegunToolbox): boolean => {\n  const androidHome = process.env.ANDROID_HOME\n  const hasAndroidEnv = !toolbox.strings.isBlank(androidHome)\n  const hasAndroid = hasAndroidEnv && toolbox.filesystem.exists(`${androidHome}/tools`) === \"dir\"\n\n  return Boolean(hasAndroid)\n}\n\ntype CopyBoilerplateOptions = {\n  boilerplatePath: string\n  targetPath: string\n  excluded: Array<string>\n  overwrite?: boolean\n}\n\n/**\n * Copies the boilerplate over to the destination folder.\n *\n */\nexport async function copyBoilerplate(toolbox: GluegunToolbox, options: CopyBoilerplateOptions) {\n  const { filesystem } = toolbox\n  const { copyAsync, path } = filesystem\n\n  // ensure the destination folder exists\n  await filesystem.dirAsync(options.targetPath)\n\n  // rather than copying everything wholesale, let's check what's in the boilerplate folder\n  // and copy over everything except stuff like lockfiles and node_modules\n  // just to make it faster, y'know? Don't want to copy unnecessary stuff\n  const filesAndFolders = children(options.boilerplatePath, true)\n  const copyTargets = filesAndFolders.filter(\n    (file) => !options.excluded.find((exclusion) => file.includes(exclusion)),\n  )\n\n  const { overwrite } = options\n  // kick off a bunch of copies\n  const copyPromises = copyTargets.map((fileOrFolder) =>\n    copyAsync(path(options.boilerplatePath, fileOrFolder), path(options.targetPath, fileOrFolder), {\n      ...(overwrite && { overwrite }),\n    }),\n  )\n\n  // copy them all at once\n  return Promise.all(copyPromises)\n}\n\nexport async function renameReactNativeApp(\n  toolbox: GluegunToolbox,\n  oldName: string,\n  newName: string,\n  oldBundleIdentifier: string,\n  newBundleIdentifier: string,\n) {\n  const { parameters, filesystem, print, strings } = toolbox\n  const { kebabCase } = strings\n  const { path } = filesystem\n\n  // debug?\n  const debug = boolFlag(parameters.options.debug)\n  const log = <T = unknown>(m: T): T => {\n    debug && print.info(` ${m}`)\n    return m\n  }\n\n  // lower case stuff\n  const oldnamelower = oldName.toLowerCase()\n  const newnamelower = newName.toLowerCase()\n\n  // kebab case\n  const oldnamekebab = kebabCase(oldName)\n  const newnamekebab = kebabCase(newName)\n\n  // SCREAMING_SNAKE_CASE\n  const oldnamesnake = oldnamelower.replace(/[^a-z0-9]/g, \"_\").toUpperCase()\n  const newnamesnake = newnamelower.replace(/[^a-z0-9]/g, \"_\").toUpperCase()\n\n  async function rename(oldFile: string, newFile: string) {\n    log(`Renaming ${oldFile} to ${newFile}`)\n    return filesystem.renameAsync(oldFile, newFile)\n  }\n\n  // rename files and folders\n\n  // prettier-ignore\n  await Promise.allSettled([\n    rename(`ios/${oldName}.xcodeproj/xcshareddata/xcschemes/${oldName}.xcscheme`, `${newName}.xcscheme`),\n    rename(`ios/${oldName}Tests/${oldName}Tests.m`, `${newName}Tests.m`),\n    rename(`ios/${oldName}/${oldName}-Bridging-Header.h`, `${newName}-Bridging-Header.h`),\n    rename(`ios/${oldName}/${oldName}.entitlements`, `${newName}.entitlements`),\n    rename(`ios/${oldName}.xcworkspace`, `${newName}.xcworkspace`),\n    rename(`ios/${oldName}`, `${newName}`),\n  ])\n\n  // these we delay to avoid race conditions\n  await Promise.allSettled([\n    rename(`ios/${oldName}Tests`, `${newName}Tests`),\n    rename(`ios/${oldName}.xcodeproj`, `${newName}.xcodeproj`),\n  ])\n\n  // if the bundle identifier / android package name changed,\n  // we need to move everything to the new folder structure\n  const oldPath = oldBundleIdentifier.replace(/\\./g, \"/\")\n  const newPath = newBundleIdentifier.replace(/\\./g, \"/\")\n\n  if (oldBundleIdentifier !== newBundleIdentifier) {\n    log(`Renaming bundle identifier to ${newBundleIdentifier}`)\n\n    // move everything at the old bundle identifier path to the new one\n    await Promise.allSettled([\n      filesystem.moveAsync(\n        `android/app/src/main/java/${oldPath}`,\n        `android/app/src/main/java/${newPath}`,\n      ),\n      filesystem.moveAsync(\n        `android/app/src/debug/java/${oldPath}`,\n        `android/app/src/debug/java/${newPath}`,\n      ),\n      filesystem.moveAsync(\n        `android/app/src/release/java/${oldPath}`,\n        `android/app/src/release/java/${newPath}`,\n      ),\n    ])\n  }\n\n  // here's a list of all the files to patch the name in\n  const filesToPatch = [\n    `app.json`,\n    `package.json`,\n    `android/settings.gradle`,\n    `android/app/_BUCK`,\n    `android/app/BUCK`,\n    `android/app/build.gradle`,\n    `android/app/src/debug/java/${newPath}/ReactNativeFlipper.java`,\n    `android/app/src/release/java/${newPath}/ReactNativeFlipper.java`,\n    `android/app/src/main/AndroidManifest.xml`,\n    `android/app/src/main/java/${newPath}/MainActivity.java`,\n    `android/app/src/main/java/${newPath}/MainApplication.java`,\n    `android/app/src/main/java/${newPath}/MainApplication.java`,\n    `android/app/src/main/java/${newPath}/newarchitecture/MainApplicationReactNativeHost.java`,\n    `android/app/src/main/java/${newPath}/newarchitecture/components/MainComponentsRegistry.java`,\n    `android/app/src/main/java/${newPath}/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java`,\n    `android/app/src/main/jni/Android.mk`,\n    `android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.h`,\n    `android/app/src/main/jni/MainComponentsRegistry.h`,\n    `android/app/src/main/res/values/strings.xml`,\n    `ios/Podfile`,\n    `ios/main.jsbundle`, // this file could just be regenerated, but this isn't bad to do\n    `ios/${newName}/Info.plist`,\n    `ios/${newName}.xcodeproj/project.pbxproj`,\n    `ios/${newName}.xcodeproj/xcshareddata/xcschemes/${newName}.xcscheme`,\n    `ios/${newName}.xcworkspace/contents.xcworkspacedata`,\n    `ios/${newName}Tests/${newName}Tests.m`,\n    `ios/${newName}/AppDelegate.mm`,\n    `ios/${newName}/LaunchScreen.storyboard`,\n  ]\n\n  // patch the files\n  await Promise.allSettled(\n    filesToPatch.map(async (file) => {\n      // no need to patch files that don't exist\n      const exists = await filesystem.existsAsync(path(file))\n      if (!exists) return\n\n      const content = await filesystem.readAsync(path(process.cwd(), file), \"utf8\")\n\n      log(`Patching ${file} - ${oldName} to ${newName} and variants`)\n\n      // replace all instances of the old name and all its variants\n      const newContent = content\n        .replace(new RegExp(oldBundleIdentifier, \"g\"), newBundleIdentifier)\n        .replace(new RegExp(oldnamekebab, \"g\"), newnamekebab)\n        .replace(new RegExp(oldnamesnake, \"g\"), newnamesnake)\n        .replace(new RegExp(oldName, \"g\"), newName)\n        .replace(new RegExp(oldnamelower, \"g\"), newnamelower)\n\n      // write the new content back to the file\n      await filesystem.writeAsync(file, newContent, { atomic: true })\n    }),\n  )\n}\n\nexport async function replaceMaestroBundleIds(\n  toolbox: GluegunToolbox,\n  oldBundleIdentifier: string,\n  newBundleIdentifier: string,\n) {\n  const { parameters, filesystem, print } = toolbox\n  const { path } = filesystem\n\n  // debug?\n  const debug = boolFlag(parameters.options.debug)\n  const log = <T = unknown>(m: T): T => {\n    debug && print.info(` ${m}`)\n    return m\n  }\n\n  // here's a list of all the maestro test files to fix the bundle id\n  const TARGET_DIR = path(process.cwd())\n  const filesToPatch = filesystem.cwd(TARGET_DIR).find({\n    matching: `.maestro/**.yaml`,\n    files: true,\n    directories: false,\n  })\n\n  // patch the files\n  await Promise.allSettled(\n    filesToPatch.map(async (file) => {\n      // no need to patch files that don't exist\n      const exists = await filesystem.existsAsync(path(file))\n      if (!exists) return\n\n      const content = await filesystem.readAsync(path(process.cwd(), file), \"utf8\")\n\n      log(`Patching ${file} - ${oldBundleIdentifier} to ${newBundleIdentifier} and variants`)\n\n      // replace all instances of the placeholder bundle id with the new one\n      const newContent = content.replace(new RegExp(oldBundleIdentifier, \"g\"), newBundleIdentifier)\n\n      // write the new content back to the file\n      await filesystem.writeAsync(file, newContent, { atomic: true })\n    }),\n  )\n}\n\n/**\n * Defines an ejs template for a screen when using Expo Router.\n */\nexport const EXPO_ROUTER_SCREEN_TEMPLATE = `---\ndestinationDir: src/screens\n---\nimport { ViewStyle } from \"react-native\"\n\nimport { Screen } from \"@/components/Screen\"\nimport { Text } from \"@/components/Text\"\n\nexport default function <%= props.pascalCaseName %>Screen() {\n  return (\n    <Screen style={$root} preset=\"scroll\">\n      <Text text=\"<%= props.camelCaseName %>\" />\n    </Screen>\n  )\n}\n\nconst $root: ViewStyle = {\n  flex: 1,\n}\n`\n\n/**\n * Defines an ejs template for a route when using Expo Router. The route\n * will be inside the proper `app` directory which will just render the\n * appropriate screen from src/screens.\n */\nexport const EXPO_ROUTER_ROUTE_TEMPLATE = `---\nfilename: <%= props.kebabCaseName %>.tsx\n---\nimport { <%= props.pascalCaseName %>Screen } from \"@/screens/<%= props.pascalCaseName %>Screen\"\n\nexport default function <%= props.pascalCaseName %>() {\n  return <<%= props.pascalCaseName %>Screen />\n}\n\n`\n\nexport const EXPO_ROUTER_DYNAMIC_ROUTE_TEMPLATE = `import { <%= props.pascalCaseName %>Screen } from \"@/screens/<%= props.pascalCaseName %>Screen\"\n\nexport default function <%= props.pascalCaseName %>() {\n  return <<%= props.pascalCaseName %>Screen />\n}\n\n`\n\nexport function createGeneratorTemplate(\n  toolbox: GluegunToolbox,\n  path: string,\n  templateEjs: string,\n) {\n  const { filesystem, parameters, print } = toolbox\n\n  // debug?\n  const debug = boolFlag(parameters.options.debug)\n  const log = <T = unknown>(m: T): T => {\n    debug && print.info(` ${m}`)\n    return m\n  }\n\n  try {\n    filesystem.write(path, templateEjs)\n  } catch (e) {\n    log(`Unable to write generator template at ${path}.`)\n  }\n}\n\nexport function refactorExpoRouterReactotronCmds(toolbox: GluegunToolbox) {\n  const { filesystem, parameters, print } = toolbox\n\n  // debug?\n  const debug = boolFlag(parameters.options.debug)\n  const log = <T = unknown>(m: T): T => {\n    debug && print.info(` ${m}`)\n    return m\n  }\n\n  try {\n    const TARGET_DIR = filesystem.path(process.cwd())\n    const reactotronConfigPath = filesystem.path(TARGET_DIR, \"src/devtools/ReactotronConfig.ts\")\n\n    let reactotronConfig = filesystem.read(reactotronConfigPath)\n    reactotronConfig = reactotronConfig\n      .replace(/import { goBack, resetRoot, navigate }.*/g, 'import { router } from \"expo-router\"')\n      .replace(/navigate\\(route as any\\).*/g, \"router.push(route)\")\n      .replace(/goBack\\(\\).*/g, \" router.back()\")\n\n    // this one gets removed entirely\n    const customCommandToRemoveRegex =\n      /reactotron\\.onCustomCommand\\({\\s*title: \"Reset Navigation State\",\\s*description: \"Resets the navigation state\",\\s*command: \"resetNavigation\",\\s*handler: \\(\\) => {\\s*Reactotron\\.log\\(\"resetting navigation state\"\\)\\s*resetRoot\\({ index: 0, routes: \\[\\] }\\)\\s*},\\s*}\\),?\\n?/g\n    reactotronConfig = reactotronConfig.replace(customCommandToRemoveRegex, \"\")\n\n    filesystem.write(reactotronConfigPath, reactotronConfig)\n  } catch (e) {\n    log(`Unable to update ReactotronConfig.`)\n  }\n}\n\nexport function updateExpoRouterSrcDir(toolbox: GluegunToolbox) {\n  const { filesystem, parameters, print } = toolbox\n\n  // debug?\n  const debug = boolFlag(parameters.options.debug)\n  const log = <T = unknown>(m: T): T => {\n    debug && print.info(` ${m}`)\n    return m\n  }\n\n  const TARGET_DIR = filesystem.path(process.cwd())\n  const expoRouterFilesToFix = [\n    \"tsconfig.json\",\n    // has its own tsconfig, needs updating separately\n    \"test/i18n.test.ts\",\n    \"test/setup.ts\",\n    \"ignite/templates/component/NAME.tsx.ejs\",\n  ]\n  expoRouterFilesToFix.forEach((file) => {\n    const filePath = filesystem.path(TARGET_DIR, file)\n    let fileContents = filesystem.read(filePath)\n    try {\n      fileContents = fileContents.replace(/app\\//g, \"src/\")\n      filesystem.write(filePath, fileContents)\n    } catch (e) {\n      log(`Unable to locate ${file}.`)\n    }\n  })\n}\n\nexport function updateExpoRouterPackageJson(toolbox: GluegunToolbox) {\n  const { filesystem, parameters, print } = toolbox\n\n  // debug?\n  const debug = boolFlag(parameters.options.debug)\n  const log = <T = unknown>(m: T): T => {\n    debug && print.info(` ${m}`)\n    return m\n  }\n\n  const TARGET_DIR = filesystem.path(process.cwd())\n  const packageJsonPath = filesystem.path(TARGET_DIR, \"package.json\")\n\n  try {\n    let packageJsonRaw = filesystem.read(packageJsonPath)\n\n    // update depcruise script to use src instead of app\n    packageJsonRaw = packageJsonRaw.replace(\n      /\"depcruise\": \"depcruise app --config .dependency-cruiser.js\"/g,\n      `\"depcruise\": \"depcruise src --config .dependency-cruiser.js\"`,\n    )\n\n    // update dependency graph script to use src instead of app\n    packageJsonRaw = packageJsonRaw.replace(\n      /\"depcruise:graph\": \"depcruise app --include-only \\\"^app\\\" --config .dependency-cruiser.js --output-type dot > app-dependency-graph.dot && dot -T svg app-dependency-graph.dot -o app-dependency-graph.svg && dot -T png app-dependency-graph.dot -o app-dependency-graph.png && rm app-dependency-graph.dot\"/g,\n      `\"depcruise:graph\": \"depcruise src --include-only \\\"^src\\\" --config .dependency-cruiser.js --output-type dot > app-dependency-graph.dot && dot -T svg app-dependency-graph.dot -o app-dependency-graph.svg && dot -T png src-dependency-graph.dot -o app-dependency-graph.png && rm app-dependency-graph.dot\"`,\n    )\n\n    filesystem.write(packageJsonPath, packageJsonRaw)\n  } catch (e) {\n    log(`Unable to update package.json for Expo Router.`)\n  }\n}\n\nexport function cleanupExpoRouterConversion(toolbox: GluegunToolbox, targetPath: string) {\n  const { filesystem } = toolbox\n\n  const workingDir = filesystem.cwd(targetPath)\n  workingDir.cwd(\"src\").remove(\"app.tsx\")\n  workingDir.move(\n    workingDir.path(\"src\", \"screens\", \"ErrorScreen\"),\n    workingDir.path(\"src\", \"components\", \"ErrorBoundary\"),\n  )\n  workingDir.remove(\"index.tsx\")\n  workingDir.remove(workingDir.path(\"ignite\", \"templates\", \"navigator\"))\n  workingDir.remove(workingDir.path(\"src\", \"navigators\"))\n  workingDir.remove(\"app\")\n}\n\nexport function updatePackagerCommandsInReadme(readmePath: string, packagerName: PackagerName) {\n  try {\n    let readmeContents = filesystem.read(readmePath)\n\n    // replace `yarn` exactly with the install command\n    readmeContents = readmeContents.replace(\"pnpm run\", packager.installCmd({ packagerName }))\n\n    // replace `yarn` plus some command after the space with the proper packager run command\n    // pass the matched command to runCmd as string excluding the `yarn` part\n    readmeContents = readmeContents.replace(/^pnpm run\\s(.*)$/gm, (_, cmd) =>\n      packager.runCmd(cmd, { packagerName }),\n    )\n\n    filesystem.write(readmePath, readmeContents)\n  } catch (e) {\n    console.error(\"Unable to update README.md.\")\n  }\n}\n"
  },
  {
    "path": "src/tools/spawn.ts",
    "content": "import * as crossSpawn from \"cross-spawn\"\n\nexport type SpawnOptions = {\n  onProgress?: (data: string) => void\n  env?: Record<string, unknown>\n}\nexport function spawnProgress(commandLine: string, options: SpawnOptions): Promise<string> {\n  return new Promise((resolve, reject) => {\n    const args = commandLine.split(\" \")\n    const spawned = crossSpawn(args.shift(), args, options)\n    const output = []\n\n    spawned.stdout.on(\"data\", (data) => {\n      data = data.toString()\n      return options.onProgress ? options.onProgress(data) : output.push(data)\n    })\n    spawned.stderr.on(\"data\", (data) => output.push(data))\n    spawned.on(\"close\", (code) => (code === 0 ? resolve(output.join(\"\")) : reject(output.join(\"\"))))\n    spawned.on(\"error\", (err) => reject(err))\n  })\n}\n"
  },
  {
    "path": "src/tools/strip-ansi.ts",
    "content": "// https://github.com/chalk/ansi-regex/blob/02fa893d619d3da85411acc8fd4e2eea0e95a9d9/index.js\nfunction ansiRegex({ onlyFirst = false } = {}) {\n  const pattern = [\n    \"[\\\\u001B\\\\u009B][[\\\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\\\d\\\\/#&.:=?%@~_]+)*|[a-zA-Z\\\\d]+(?:;[-a-zA-Z\\\\d\\\\/#&.:=?%@~_]*)*)?\\\\u0007)\",\n    \"(?:(?:\\\\d{1,4}(?:;\\\\d{0,4})*)?[\\\\dA-PR-TZcf-nq-uy=><~]))\",\n  ].join(\"|\")\n\n  return new RegExp(pattern, onlyFirst ? undefined : \"g\")\n}\n\nexport function stripANSI(string) {\n  if (typeof string !== \"string\") {\n    throw new TypeError(`Expected a \\`string\\`, got \\`${typeof string}\\``)\n  }\n\n  // Even though the regex is global, we don't need to reset the `.lastIndex`\n  // because unlike `.exec()` and `.test()`, `.replace()` does it automatically\n  // and doing it manually has a performance penalty.\n  return string.replace(ansiRegex(), \"\")\n}\n"
  },
  {
    "path": "src/tools/validations.ts",
    "content": "import { GluegunToolbox } from \"gluegun\"\n\nimport { prefix } from \"./pretty\"\n\n// #region Error Guards\ntype IsError = (str: string) => boolean\ntype ErrorMessage = (str?: string) => string\ntype ErrorGuard = [IsError, ErrorMessage]\n\nconst isIgnite: ErrorGuard = [\n  (str) => str.toLowerCase() === \"ignite\",\n  (str) => `Hey...that's my name! Please name your project something other than '${str}'.`,\n]\nconst isOnlyNumbers: ErrorGuard = [\n  (str) => /^\\d+$/.test(str),\n  () => `Please use at least one non-numeric character for your project name`,\n]\nconst isNotAlphaNumeric: ErrorGuard = [\n  (str) => !/^[a-z_][a-z0-9_-]+$/i.test(str),\n  () =>\n    `The project name can only contain alphanumeric characters and underscore, but must not begin with a number.`,\n]\n\nconst guards: ErrorGuard[] = [isIgnite, isOnlyNumbers, isNotAlphaNumeric]\n\n/**\n * check if the value matches any of the error guards\n * @returns error message from the first guard that matches, or `true` if no guards match\n */\nconst validate = (value: string): true | string => {\n  for (const [isError, errorMessage] of guards) {\n    if (isError(value)) {\n      return errorMessage(value)\n    }\n  }\n  return true\n}\n// #endregion\n\nexport async function validateProjectName(toolbox: GluegunToolbox): Promise<string> {\n  const { parameters, strings, print } = toolbox\n  const { isBlank } = strings\n\n  // grab the project name\n  let projectName: string = (parameters.first || \"\").toString()\n\n  // verify the project name is a thing\n  if (isBlank(projectName)) {\n    const projectNameResponse = await toolbox.prompt.ask(() => ({\n      name: \"projectName\",\n      type: \"input\",\n      message: \"What do you want to call it?\",\n      prefix,\n      validate,\n    }))\n    projectName = projectNameResponse.projectName\n  }\n\n  // warn if more than one argument is provided for <projectName>\n  if (parameters.second) {\n    print.info(`Info: You provided more than one argument for <projectName>. The first one (${projectName}) will be used and the rest are ignored.`) // prettier-ignore\n  }\n\n  const error = validate(projectName)\n  if (typeof error === \"string\") {\n    print.error(error)\n    process.exit(1)\n  }\n\n  return projectName\n}\n\nexport function validateBundleIdentifier(\n  toolbox: GluegunToolbox,\n  bundleID: string | undefined,\n): string | undefined {\n  const { print } = toolbox\n\n  // no bundle ID provided\n  if (bundleID === undefined) return undefined\n\n  const id = bundleID.split(\".\")\n  const validBundleID = /^([a-zA-Z]([a-zA-Z0-9_])*\\.)+[a-zA-Z]([a-zA-Z0-9_])*$/u\n  if (id.length < 2) {\n    print.error(\n      'Invalid Bundle Identifier. Add something like \"com.travelapp\" or \"com.junedomingo.travelapp\"',\n    )\n    process.exit(1)\n  }\n  if (!validBundleID.test(bundleID)) {\n    print.error(\n      \"Invalid Bundle Identifier. It must have at least two segments (one or more dots). Each segment must start with a letter. All characters must be alphanumeric or an underscore [a-zA-Z0-9_]\",\n    )\n    process.exit(1)\n  }\n\n  return bundleID\n}\n\n/**\n * Check for project path issues (Windows length, macOS spaces)\n *\n */\n\nexport function validateProjectPath(absPath: string, toolbox: GluegunToolbox) {\n  const { print } = toolbox\n  const normalized = absPath.replace(/[\\\\/]+$/, \"\")\n  const len = normalized.length\n\n  // Check for spaces in path on macOS (Xcode build issues)\n  if (process.platform === \"darwin\" && normalized.includes(\" \")) {\n    print.warning(`Project path contains spaces, which can cause Xcode build failures.`)\n    print.warning(`Tip: use a path without spaces, e.g. \"/Users/username/MyApp\"`)\n    print.warning(`Why: Xcode build scripts and CI systems can fail when paths contain spaces.`)\n    print.warning(\" \")\n  }\n\n  if (process.platform === \"win32\") {\n    // Threshold for warning about long paths on Windows\n    const PATH_WARN_THRESHOLD = 120\n\n    // Warn if the path is too long: https://github.com/expo/expo/issues/36274\n    if (len > PATH_WARN_THRESHOLD) {\n      print.warning(\n        `Windows project path is quite long (${len} chars). Android native builds can fail on very long paths.`,\n      )\n      print.warning(`Path: ${normalized}`)\n      print.warning(`Tip: move your project closer to the drive root, e.g. ${\"C:\\\\src\\\\MyApp\"}`)\n      print.warning(\n        `Why: CMake/Ninja can generate very long object file paths that hit Windows limits.`,\n      )\n      print.warning(\" \")\n    }\n  }\n}\n\nexport type ValidationsExports = {\n  validateProjectName: typeof validateProjectName\n  validateBundleIdentifier: typeof validateBundleIdentifier\n  validateProjectPath: typeof validateProjectPath\n}\n"
  },
  {
    "path": "src/types.ts",
    "content": "export { GluegunCommand, GluegunToolbox } from \"gluegun\"\n\nexport type CLIType = \"ignite-classic\" | \"react-native-cli\" | \"expo-cli\" | \"create-react-native-app\"\n\nexport type CLIOptions = {\n  cli: CLIType\n  template: string\n}\n"
  },
  {
    "path": "template.config.js",
    "content": "/**\n * React Native CLI configuration file\n */\nmodule.exports = {\n  placeholderName: \"HelloWorld\",\n  templateDir: \"./boilerplate\",\n}\n"
  },
  {
    "path": "test/_test-helpers.ts",
    "content": "import { spawn } from \"child_process\"\nimport { WriteStream } from \"fs\"\nimport { system, filesystem } from \"gluegun\"\n\nimport {\n  EXPO_ROUTER_SCREEN_TEMPLATE,\n  EXPO_ROUTER_ROUTE_TEMPLATE,\n  EXPO_ROUTER_DYNAMIC_ROUTE_TEMPLATE,\n} from \"../src/tools/react-native\"\nimport { stripANSI } from \"../src/tools/strip-ansi\"\n\nconst IGNITE = \"node \" + filesystem.path(__dirname, \"..\", \"bin\", \"ignite\")\nconst shellOpts = { stdio: \"inherit\" }\n\njest.setTimeout(10 * 60 * 1000)\n\ntype RunOptions = {\n  pre?: string // command to run before the command\n  post?: string // command to run after the command\n}\n\ntype SpawnOptions = RunOptions & {\n  outputFileName: string\n}\n\ntype CommandOutput = {\n  output: string\n  exitCode: number\n}\n\nfunction buildCommand(cmd: string, options: RunOptions) {\n  return `${options.pre ? options.pre + \" && \" : \"\"}${cmd}${options.post ? \" && \" + options.post : \"\"}`\n}\n\nexport async function run(cmd: string, options: RunOptions = {}): Promise<string> {\n  const resultANSI = await system.run(buildCommand(cmd, options), shellOpts)\n  return stripANSI(resultANSI)\n}\n\nexport async function runError(cmd: string): Promise<string | any> {\n  let resultANSI: string\n  try {\n    resultANSI = await system.run(`${IGNITE} ${cmd}`, shellOpts)\n  } catch (e) {\n    return e\n  }\n  return `No error thrown? Output: ${resultANSI}`\n}\n\nexport async function runIgnite(cmd: string, options: RunOptions = {}): Promise<string> {\n  return run(`${IGNITE} ${cmd}`, options)\n}\n\nasync function deleteFileIfExists(file: string) {\n  if (filesystem.exists(file)) {\n    filesystem.remove(file)\n  }\n}\n\nconst artifactsDirectory = filesystem.path(__dirname, \"artifacts\")\n\nasync function setUpLogFile(filePath: string): Promise<WriteStream> {\n  filesystem.dir(artifactsDirectory)\n  const outputLog = filesystem.createWriteStream(filePath)\n\n  await new Promise((resolve, reject) => {\n    outputLog.on(\"open\", resolve)\n    outputLog.on(\"error\", (err) => reject(err))\n  })\n\n  return outputLog\n}\n\n/**\n * Spawns a shell command and logs its output to the provided WriteStream.\n * Meant to log to a temporary file for later reading. This is an internal\n * function for use by `spawnAndLog`.\n *\n * @param cmd - The shell command to execute.\n * @param outputLog - The WriteStream where the command's output will be logged.\n * @returns A promise that resolves to the exit code of the command, or 99 if the exit code is null or undefined.\n *\n * @throws Will reject the promise if the subprocess fails to start.\n */\nasync function startSpawnAndLog(cmd: string, outputLog: WriteStream): Promise<number> {\n  return new Promise((resolve, reject) => {\n    const subprocess = spawn(\"sh\", [\"-c\", cmd], { stdio: [\"ignore\", outputLog, outputLog] })\n    subprocess.on(\"close\", (code) => {\n      console.log(`${cmd} exited with code ${code}`)\n      resolve(code ?? 99)\n    })\n    subprocess.on(\"error\", (err) => {\n      console.log(`Failed to start subprocess: ${err}`)\n      reject(err)\n    })\n  })\n}\n\n/**\n * Executes a command, logs its output to a file, and returns the command's exit code and output.\n * Uses `spawn`to run commands so that we can capture output in case of failure.\n * We can log the output to the test console by throwing it, or if that fails,\n * we can read the file for troubleshooting. Keep in mind that the output will\n * need ANSI characters stripped to be readable in that case.\n *\n * The error code should typically be `1` if the command fails, but is set to `99` by default in `runSpawnAndLog`.\n *\n * @param cmd - The command to execute.\n * @param options - Options for spawning the command, including the output file name.\n * @returns A promise that resolves to an object containing the exit code and the output of the command.\n * @throws Will throw an error if the output file cannot be read or if the command execution fails.\n */\nexport async function spawnAndLog(cmd: string, options: SpawnOptions): Promise<CommandOutput> {\n  const fullCmd = buildCommand(cmd, options)\n  const filePath = filesystem.path(artifactsDirectory, options.outputFileName)\n  await deleteFileIfExists(filePath)\n\n  const outputLog = await setUpLogFile(filePath)\n\n  try {\n    const exitCode = await startSpawnAndLog(fullCmd, outputLog)\n    outputLog.end()\n\n    const fileData = await filesystem.readAsync(filePath)\n    if (fileData === undefined) {\n      throw new Error(\"Failed to read output file\")\n    }\n    return { exitCode, output: fileData }\n  } catch (e) {\n    outputLog.end()\n    throw e\n  }\n}\n\nexport async function spawnAndLogIgnite(\n  cmd: string,\n  options: SpawnOptions,\n): Promise<CommandOutput> {\n  return spawnAndLog(`${IGNITE} ${cmd}`, options)\n}\n\n// Designed for printing command output to the Jest test console if it fails, by\n// throwing the output.\nexport async function spawnIgniteAndPrintIfFail(\n  cmd: string,\n  options: SpawnOptions,\n): Promise<string> {\n  const { output, exitCode } = await spawnAndLogIgnite(cmd, options)\n  if (exitCode !== 0) {\n    // print entire command output to test console\n    throw new Error(`Ignite new exited with code ${exitCode}: \\n${stripANSI(output)}`)\n  } else {\n    return output\n  }\n}\n\nfunction generateScreenTemplatePath(pathname: string): string {\n  return filesystem.path(pathname, \"ignite\", \"templates\", \"screen\", \"NAMEScreen.tsx.ejs\")\n}\n\nfunction generateRouteTemplatePath(pathname: string): string {\n  return filesystem.path(pathname, \"ignite\", \"templates\", \"route\", \"NAME.tsx.ejs\")\n}\n\nfunction generateDynamicRouteTemplatePath(pathname: string): string {\n  return filesystem.path(pathname, \"ignite\", \"templates\", \"dynamic-route\", \"NAME.tsx.ejs\")\n}\n\nexport function copyDefaultScreenGenerator(tempBoilerplatePath: string): void {\n  const REACT_NAVIGATION_SCREEN_TEMPLATE = filesystem.read(\n    filesystem.path(tempBoilerplatePath, \"ignite\", \"templates\", \"screen\", \"NAMEScreen.tsx.ejs\"),\n  )\n\n  const destination = generateScreenTemplatePath(tempBoilerplatePath)\n  if (REACT_NAVIGATION_SCREEN_TEMPLATE) {\n    filesystem.write(destination, REACT_NAVIGATION_SCREEN_TEMPLATE)\n  }\n}\n\nexport function copyExpoRouterGeneratorTemplates(tempBoilerplatePath: string): void {\n  const screenDestination = generateScreenTemplatePath(tempBoilerplatePath)\n  filesystem.write(screenDestination, EXPO_ROUTER_SCREEN_TEMPLATE)\n\n  const routeDestination = generateRouteTemplatePath(tempBoilerplatePath)\n  filesystem.write(routeDestination, EXPO_ROUTER_ROUTE_TEMPLATE)\n\n  const dynamicRouteDestination = generateDynamicRouteTemplatePath(tempBoilerplatePath)\n  filesystem.write(dynamicRouteDestination, EXPO_ROUTER_DYNAMIC_ROUTE_TEMPLATE)\n}\n\nexport function removeScreenGenerator(tempBoilerplatePath: string): void {\n  const destination = generateScreenTemplatePath(tempBoilerplatePath)\n  filesystem.remove(destination)\n}\n\nexport function removeExpoRouterGeneratorTemplates(tempBoilerplatePath: string): void {\n  const screenDestination = generateScreenTemplatePath(tempBoilerplatePath)\n  filesystem.remove(screenDestination)\n\n  const routeDestination = generateRouteTemplatePath(tempBoilerplatePath)\n  filesystem.remove(routeDestination)\n}\n"
  },
  {
    "path": "test/vanilla/__snapshots__/ignite-remove-demo.test.ts.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`ignite-cli remove-demo should print the expected response 1`] = `\n\"   \n   Removing demo code from '/user/home/ignite'\nremoving file /user/home/ignite/.maestro/flows/FavoritePodcast.yaml\nremoving file /user/home/ignite/.maestro/flows/Login.yaml\nremoving file /user/home/ignite/.maestro/shared/_Login.yaml\nremoving file /user/home/ignite/app/context/AuthContext.tsx\nremoving file /user/home/ignite/app/context/EpisodeContext.tsx\nremoving file /user/home/ignite/app/i18n/demo-ar.ts\nremoving file /user/home/ignite/app/i18n/demo-en.ts\nremoving file /user/home/ignite/app/i18n/demo-es.ts\nremoving file /user/home/ignite/app/i18n/demo-fr.ts\nremoving file /user/home/ignite/app/i18n/demo-hi.ts\nremoving file /user/home/ignite/app/i18n/demo-ja.ts\nremoving file /user/home/ignite/app/i18n/demo-ko.ts\nremoving file /user/home/ignite/app/navigators/DemoNavigator.tsx\nremoving file /user/home/ignite/app/screens/DemoCommunityScreen.tsx\nremoving file /user/home/ignite/app/screens/DemoDebugScreen.tsx\nremoving file /user/home/ignite/app/screens/DemoPodcastListScreen.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/DemoDivider.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/DemoUseCase.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/DrawerIconButton.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/SectionListWithKeyboardAwareScrollView.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoAutoImage.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoButton.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoCard.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoEmptyState.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoHeader.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoIcon.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoText.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoTextField.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoToggle.tsx\nremoving file /user/home/ignite/app/screens/DemoShowroomScreen/demos/index.ts\nremoving file /user/home/ignite/app/screens/LoginScreen.tsx\n   Found 'remove-file' in /user/home/ignite/.maestro/flows/FavoritePodcast.yaml\n   Found 'remove-file' in /user/home/ignite/.maestro/flows/Login.yaml\n   Found 'remove-file' in /user/home/ignite/.maestro/shared/_Login.yaml\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end' in /user/home/ignite/app/app.tsx\n   Found '@demo remove-current-line' in /user/home/ignite/app/components/Icon.tsx\n   Found 'remove-file' in /user/home/ignite/app/context/AuthContext.tsx\n   Found 'remove-file' in /user/home/ignite/app/context/EpisodeContext.tsx\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end' in /user/home/ignite/app/i18n/ar.ts\n   Found 'remove-file' in /user/home/ignite/app/i18n/demo-ar.ts\n   Found 'remove-file' in /user/home/ignite/app/i18n/demo-en.ts\n   Found 'remove-file' in /user/home/ignite/app/i18n/demo-es.ts\n   Found 'remove-file' in /user/home/ignite/app/i18n/demo-fr.ts\n   Found 'remove-file' in /user/home/ignite/app/i18n/demo-hi.ts\n   Found 'remove-file' in /user/home/ignite/app/i18n/demo-ja.ts\n   Found 'remove-file' in /user/home/ignite/app/i18n/demo-ko.ts\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end' in /user/home/ignite/app/i18n/en.ts\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end' in /user/home/ignite/app/i18n/es.ts\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end' in /user/home/ignite/app/i18n/fr.ts\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end' in /user/home/ignite/app/i18n/hi.ts\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end' in /user/home/ignite/app/i18n/ja.ts\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end' in /user/home/ignite/app/i18n/ko.ts\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end' in /user/home/ignite/app/navigators/AppNavigator.tsx\n   Found 'remove-file' in /user/home/ignite/app/navigators/DemoNavigator.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoCommunityScreen.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoDebugScreen.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoPodcastListScreen.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/DemoDivider.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoAutoImage.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoButton.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoCard.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoEmptyState.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoHeader.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoIcon.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoText.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoTextField.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/demos/DemoToggle.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/demos/index.ts\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/DemoUseCase.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/DrawerIconButton.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/DemoShowroomScreen/SectionListWithKeyboardAwareScrollView.tsx\n   Found 'remove-file' in /user/home/ignite/app/screens/LoginScreen.tsx\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end', '@demo replace-next-line' in /user/home/ignite/app/screens/WelcomeScreen.tsx\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end' in /user/home/ignite/app/services/api/index.ts\n   Found '@demo remove-current-line', '@demo remove-block-start', '@demo remove-block-end' in /user/home/ignite/app/theme/styles.ts\n   Removed empty directory '/user/home/ignite/.maestro/flows'\n   Removed empty directory '/user/home/ignite/app/context'\n   Removed demo directory '/user/home/ignite/assets/icons/demo'\n   Removed demo directory '/user/home/ignite/assets/images/demo'\n   Done removing demo code from '/user/home/ignite'\n\"\n`;\n"
  },
  {
    "path": "test/vanilla/ignite-generate.test.ts",
    "content": "import { filesystem } from \"gluegun\"\nimport * as tempy from \"tempy\"\n\nimport {\n  copyDefaultScreenGenerator,\n  copyExpoRouterGeneratorTemplates,\n  removeExpoRouterGeneratorTemplates,\n  removeScreenGenerator,\n  runIgnite,\n} from \"../_test-helpers\"\n\nconst BOILERPLATE_PATH = filesystem.path(__dirname, \"../../boilerplate\")\n\nconst setup = (): { TEMP_DIR: string } => {\n  const TEMP_DIR = tempy.directory({ prefix: \"ignite-\" })\n\n  beforeEach(() => {\n    // create the destination directory\n    filesystem.dir(TEMP_DIR)\n    // copy the relevant folders\n    filesystem.copy(BOILERPLATE_PATH + \"/app\", TEMP_DIR + \"/app\", { overwrite: true })\n    filesystem.copy(BOILERPLATE_PATH + \"/ignite\", TEMP_DIR + \"/ignite\", { overwrite: true })\n  })\n\n  afterEach(() => {\n    filesystem.remove(TEMP_DIR) // clean up our mess\n  })\n\n  return { TEMP_DIR }\n}\n\nconst { read } = filesystem\n\nconst { TEMP_DIR } = setup()\nconst options = {\n  pre: `cd ${TEMP_DIR}`,\n  post: `cd ${process.cwd()}`,\n}\n\n/**\n * \"/user/home/ignite\" replaces the temp directory, so we don't get failures when it changes every test run\n * @returns command output with temp directory replaced\n */\nconst replaceHomeDir = (result: string, { mock = \"/user/home/ignite\", temp = TEMP_DIR } = {}) =>\n  result.replace(new RegExp(temp, \"g\"), mock)\n\ndescribe(\"ignite-cli generate\", () => {\n  describe(\"components\", () => {\n    it(\"should generate Topping component and patch index components export\", async () => {\n      const result = await runIgnite(`generate component Topping`, options)\n\n      expect(replaceHomeDir(result)).toMatchInlineSnapshot(`\n        \"   \n           \n           Generated new files:\n           /user/home/ignite/app/components/Topping.tsx\n        \"\n      `)\n      expect(read(`${TEMP_DIR}/app/components/Topping.tsx`)).toMatchInlineSnapshot(`\n\"import { StyleProp, TextStyle, View, ViewStyle } from \"react-native\"\nimport { useAppTheme } from \"@/theme/context\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { Text } from \"@/components/Text\"\n\nexport interface ToppingProps {\n  /**\n   * An optional style override useful for padding & margin.\n   */\n  style?: StyleProp<ViewStyle>\n}\n\n/**\n * Describe your component here\n */\nexport const Topping = (props: ToppingProps) => {\n  const { style } = props\n  const $styles = [$container, style]\n  const { themed } = useAppTheme();\n\n  return (\n    <View style={$styles}>\n      <Text style={themed($text)}>Hello</Text>\n    </View>\n  )\n}\n\nconst $container: ViewStyle = {\n  justifyContent: \"center\",\n}\n\nconst $text: ThemedStyle<TextStyle> = ({ colors, typography }) => ({\n  fontFamily: typography.primary.normal,\n  fontSize: 14,\n  color: colors.palette.primary500,\n})\n\"\n`)\n      expect(read(`${TEMP_DIR}/app/components/index.ts`)).toMatchInlineSnapshot(`undefined`)\n    })\n\n    it(\"should generate Topping component in subdirectory and patch index components export\", async () => {\n      const result = await runIgnite(`generate component sub/to/my/Topping`, options)\n\n      expect(replaceHomeDir(result)).toMatchInlineSnapshot(`\n        \"   \n           \n           Generated new files:\n           /user/home/ignite/app/components/sub/to/my/Topping.tsx\n        \"\n      `)\n      expect(read(`${TEMP_DIR}/app/components/sub/to/my/Topping.tsx`)).toMatchInlineSnapshot(`\n\"import { StyleProp, TextStyle, View, ViewStyle } from \"react-native\"\nimport { useAppTheme } from \"@/theme/context\"\nimport type { ThemedStyle } from \"@/theme/types\"\nimport { Text } from \"@/components/Text\"\n\nexport interface ToppingProps {\n  /**\n   * An optional style override useful for padding & margin.\n   */\n  style?: StyleProp<ViewStyle>\n}\n\n/**\n * Describe your component here\n */\nexport const Topping = (props: ToppingProps) => {\n  const { style } = props\n  const $styles = [$container, style]\n  const { themed } = useAppTheme();\n\n  return (\n    <View style={$styles}>\n      <Text style={themed($text)}>Hello</Text>\n    </View>\n  )\n}\n\nconst $container: ViewStyle = {\n  justifyContent: \"center\",\n}\n\nconst $text: ThemedStyle<TextStyle> = ({ colors, typography }) => ({\n  fontFamily: typography.primary.normal,\n  fontSize: 14,\n  color: colors.palette.primary500,\n})\n\"\n`)\n      expect(read(`${TEMP_DIR}/app/components/index.ts`)).toMatchInlineSnapshot(`undefined`)\n    })\n  })\n})\n\ndescribe(\"ignite-cli generate with path params\", () => {\n  it(\"should generate Topping component in the src/components directory\", async () => {\n    const result = await runIgnite(`generate component Topping --dir=src/components`, options)\n\n    expect(replaceHomeDir(result)).toMatchInlineSnapshot(`\n      \"   \n         \n         Generated new files:\n         /user/home/ignite/src/components/Topping.tsx\n      \"\n    `)\n  })\n\n  it(\"should generate Sicilian screen in the src/screens directory\", async () => {\n    const result = await runIgnite(`generate screen Sicilian --dir=src/screens`, options)\n\n    expect(replaceHomeDir(result)).toMatchInlineSnapshot(`\n      \"   \n         \n         Generated new files:\n         /user/home/ignite/src/screens/SicilianScreen.tsx\n      \"\n    `)\n  })\n})\n\ndescribe(\"ignite-cli generate screens expo-router style\", () => {\n  beforeEach(() => {\n    // modify the generator template for screens to be a standard pattern for expo-router\n    removeScreenGenerator(TEMP_DIR)\n\n    // copy expo router specific screen and route generator templates\n    copyExpoRouterGeneratorTemplates(TEMP_DIR)\n  })\n\n  afterEach(() => {\n    // restore the generator template for screens to be a standard pattern for react-navigation\n    removeExpoRouterGeneratorTemplates(TEMP_DIR)\n    copyDefaultScreenGenerator(TEMP_DIR)\n  })\n\n  it(\"should generate `log-in` screen exactly in the requested path\", async () => {\n    const result = await runIgnite(`generate route log-in --dir=\"src/app/(app)/(tabs)\"`, options)\n\n    expect(replaceHomeDir(result)).toMatchInlineSnapshot(`\n      \"   \n         \n         Generated new files:\n         /user/home/ignite/src/app/(app)/(tabs)/log-in.tsx\n      \"\n    `)\n\n    expect(read(`${TEMP_DIR}/src/app/(app)/(tabs)/log-in.tsx`)).toMatchInlineSnapshot(`\n      \"import { LogInScreen } from \"@/screens/LogInScreen\"\n      \n      export default function LogIn() {\n        return <LogInScreen />\n      }\n\n      \"\n      `)\n  })\n\n  it(\"should generate dynamic id files at requested path\", async () => {\n    const result = await runIgnite(\n      `generate dynamic-route [id] --case=none --dir=\"src/app/(app)/(tabs)/podcasts\"`,\n      options,\n    )\n\n    expect(replaceHomeDir(result)).toMatchInlineSnapshot(`\n        \"   \n           \n           Generated new files:\n           /user/home/ignite/src/app/(app)/(tabs)/podcasts/[id].tsx\n        \"\n      `)\n    expect(read(`${TEMP_DIR}/src/app/(app)/(tabs)/podcasts/[id].tsx`)).toMatchInlineSnapshot(`\n      \"import { IdScreen } from \"@/screens/IdScreen\"\n\n      export default function Id() {\n        return <IdScreen />\n      }\n\n      \"\n      `)\n  })\n})\n"
  },
  {
    "path": "test/vanilla/ignite-help.test.ts",
    "content": "import { runIgnite } from \"../_test-helpers\"\n\ntest(`ignite help`, async () => {\n  const result = await runIgnite(`help`)\n\n  expect(result).toContain(\"Ignite\")\n  expect(result).toContain(\"new\")\n  expect(result).toContain(\"generate\")\n  expect(result).toContain(\"doctor\")\n  expect(result).toContain(`community.infinite.red`)\n})\n"
  },
  {
    "path": "test/vanilla/ignite-new-expo-router.test.ts",
    "content": "import { filesystem } from \"gluegun\"\nimport * as tempy from \"tempy\"\n\nimport { run, spawnIgniteAndPrintIfFail } from \"../_test-helpers\"\n\nconst APP_NAME = \"Foo\"\nconst originalDir = process.cwd()\n\ndescribe(`ignite new with expo-router`, () => {\n  describe(`ignite new ${APP_NAME} --debug --packager=bun --install-deps=true --experimental=expo-router --yes`, () => {\n    let tempDir: string\n    let result: string\n    let appPath: string\n\n    beforeAll(async () => {\n      tempDir = tempy.directory({ prefix: \"ignite-\" })\n      result = await spawnIgniteAndPrintIfFail(\n        `new ${APP_NAME} --debug --packager=bun --install-deps=true --experimental=expo-router --yes`,\n        {\n          pre: `cd ${tempDir}`,\n          post: `cd ${originalDir}`,\n          outputFileName: \"ignite-new-router-bun.txt\",\n        },\n      )\n      appPath = filesystem.path(tempDir, APP_NAME)\n    })\n\n    afterAll(() => {\n      // console.log(tempDir) // uncomment for debugging, then run `code <tempDir>` to see the generated app\n      filesystem.remove(tempDir) // clean up our mess\n    })\n\n    it(\"should convert to Expo Router\", async () => {\n      expect(result).toContain(\"--experimental=expo-router\")\n\n      // make sure src/navigators, app/, app.tsx is gone\n      const dirs = filesystem.list(appPath)\n      expect(dirs).toContain(\"src\")\n      expect(dirs).not.toContain(\"app\")\n      expect(dirs).not.toContain(\"app.tsx\")\n      expect(dirs).not.toContain(\"src/navigators\")\n\n      // check the contents of ignite/templates\n      const templates = filesystem.list(`${appPath}/ignite/templates`)\n      expect(templates).toContain(\"component\")\n      expect(templates).toContain(\"screen\")\n      expect(templates).toContain(\"route\")\n      expect(templates).not.toContain(\"navigator\")\n\n      // inspect that destinationDir has been adjusted\n      const componentTpl = filesystem.read(`${appPath}/ignite/templates/component/NAME.tsx.ejs`)\n      expect(componentTpl).not.toContain(\"app/components\")\n      expect(componentTpl).toContain(\"src/components\")\n\n      // check entry point\n      const packageJson = filesystem.read(`${appPath}/package.json`)\n      expect(packageJson).toContain(\"expo-router/entry\")\n      expect(packageJson).not.toContain(\"AppEntry.js\")\n\n      // check plugin in app.json\n      // check typedRoutes is turned on\n      const appJson = filesystem.read(`${appPath}/app.json`)\n      expect(appJson).toContain(\"expo-router\")\n      expect(appJson).toContain(\"typedRoutes\")\n\n      // check ReactotronConfig for router.back etc\n      const reactotronConfig = filesystem.read(`${appPath}/src/devtools/ReactotronConfig.ts`)\n      expect(reactotronConfig).toContain(\"router.back()\")\n      expect(reactotronConfig).not.toContain(\"navigate(\")\n      expect(reactotronConfig).not.toContain(\"react-navigation\")\n      expect(reactotronConfig).not.toContain(\"reset navigation state\")\n    })\n\n    it(\"should pass test, lint, and compile checks\", async () => {\n      const runOpts = {\n        pre: `cd ${appPath}`,\n        post: `cd ${originalDir}`,\n      }\n      // #region Assert package.json Scripts Can Be Run\n      // run the tests; if they fail, run will raise and this test will fail\n      await run(`bun run test`, runOpts)\n      await run(`bun run lint`, runOpts)\n      await run(`bun run compile`, runOpts)\n      await run(`bun run depcruise`, runOpts)\n      expect(await run(\"git diff HEAD --no-ext-diff\", runOpts)).toBe(\"\")\n    })\n  })\n})\n"
  },
  {
    "path": "test/vanilla/ignite-new.test.ts",
    "content": "import { filesystem } from \"gluegun\"\nimport * as tempy from \"tempy\"\n\nimport { runError, run, runIgnite, spawnIgniteAndPrintIfFail } from \"../_test-helpers\"\n\nconst APP_NAME = \"Foo\"\nconst originalDir = process.cwd()\n\ndescribe(\"ignite new\", () => {\n  describe(\"errors\", () => {\n    let tempDir: string\n\n    beforeEach(() => {\n      tempDir = tempy.directory({ prefix: \"ignite-\" })\n    })\n\n    afterEach(() => {\n      filesystem.remove(tempDir) // clean up our mess\n    })\n\n    test(`invalid bundle id \"thisisbad\" throws expected error`, async () => {\n      const result = await runError(`new BadBundleID --bundle thisisbad --yes`)\n      expect((result as any).stdout).toContain(`Invalid Bundle Identifier.`)\n    })\n  })\n\n  describe(`ignite new ${APP_NAME} --debug --packager=bun --yes`, () => {\n    let tempDir: string\n    let result: string\n    let appPath: string\n\n    beforeAll(async () => {\n      tempDir = tempy.directory({ prefix: \"ignite-\" })\n\n      result = await spawnIgniteAndPrintIfFail(`new ${APP_NAME} --debug --packager=bun --yes`, {\n        pre: `cd ${tempDir}`,\n        post: `cd ${originalDir}`,\n        outputFileName: \"ignite-new-output-bun.txt\",\n      })\n\n      appPath = filesystem.path(tempDir, APP_NAME)\n    })\n\n    afterAll(() => {\n      // console.log(tempDir) // uncomment for debugging, then run `code <tempDir>` to see the generated app\n      filesystem.remove(tempDir) // clean up our mess\n    })\n\n    it(\"should print success message\", () => {\n      // at some point this should probably be a snapshot?\n      expect(result).toContain(\"Now get cooking! 🍽\")\n    })\n\n    it(\"should have created expected directories\", () => {\n      // now let's examine the spun-up app\n      const dirs = filesystem.list(appPath)\n      expect(dirs).toContain(\"ios\")\n      expect(dirs).toContain(\"android\")\n      expect(dirs).toContain(\"app\")\n      expect(dirs).toContain(\"bun.lock\")\n\n      // check the contents of ignite/templates\n      const templates = filesystem.list(`${appPath}/ignite/templates`)\n      expect(templates).toContain(\"component\")\n      expect(templates).toContain(\"screen\")\n      expect(templates).toContain(\"app-icon\")\n    })\n\n    it(\"should have created an apple privacy manifest file\", () => {\n      // now let's examine the spun-up app\n      const dirs = filesystem.list(appPath + `/ios/${APP_NAME}/`)\n      expect(dirs).toContain(\"PrivacyInfo.xcprivacy\")\n    })\n\n    it(`should have renamed all permutations of hello-world to ${APP_NAME}`, async () => {\n      // react-native-rename doesn't always catch everything, so we need to check for\n      // any instances and fail if it doesn't work\n      await checkForLeftoverHelloWorld(appPath)\n    })\n\n    it(\"should have changed the android bundle id\", () => {\n      const androidPackageName = APP_NAME.toLowerCase()\n      const mainAppJava = filesystem.read(\n        `${appPath}/android/app/src/main/java/com/${androidPackageName}/MainApplication.kt`,\n      )\n      expect(mainAppJava).toContain(`package com.${androidPackageName}`)\n      const mainActivityJava = filesystem.read(\n        `${appPath}/android/app/src/main/java/com/${androidPackageName}/MainActivity.kt`,\n      )\n      expect(mainActivityJava).toContain(`package com.${androidPackageName}`)\n    })\n\n    it(\"should have modified package.json for proper run scripts\", () => {\n      const igniteJSON = filesystem.read(`${appPath}/package.json`, \"json\")\n      expect(igniteJSON).toHaveProperty(\"scripts\")\n      expect(igniteJSON).toHaveProperty(\"dependencies\")\n      expect(igniteJSON.scripts.android).toBe(\"expo run:android\")\n      expect(igniteJSON.scripts.ios).toBe(\"expo run:ios\")\n    })\n\n    it(\"should have created app.tsx with export\", () => {\n      const appJS = filesystem.read(`${appPath}/app/app.tsx`)\n      expect(appJS).toContain(\"export function App\")\n    })\n\n    it(\"should be able to use `generate` command and have pass output pass bun run test, bun run lint, and bun run compile scripts\", async () => {\n      // other common test operations\n      const runOpts = {\n        pre: `cd ${appPath}`,\n        post: `cd ${originalDir}`,\n      }\n\n      // #region Assert Typescript Compiles With No Errors\n      let resultTS: string\n      try {\n        resultTS = await run(`bun run compile`, runOpts)\n      } catch (e) {\n        resultTS = e.stdout\n        console.error(resultTS) // This will only show if you run in --verbose mode.\n      }\n      expect(resultTS).not.toContain(\"error\")\n      // #endregion\n\n      // #region Assert Generators Work\n      // now lets test generators too, since we have a properly spun-up app!\n      // components\n      const componentGen = await runIgnite(`generate component womp-bomp`, runOpts)\n      expect(componentGen).toContain(`app/components/WompBomp.tsx`)\n      expect(filesystem.list(`${appPath}/app/components`)).toContain(\"WompBomp.tsx\")\n      expect(filesystem.read(`${appPath}/app/components/WompBomp.tsx`)).toContain(\n        \"export const WompBomp\",\n      )\n\n      // screens\n      const screenGen = await runIgnite(`generate screen bowser-screen`, runOpts)\n      expect(screenGen).toContain(`Stripping Screen from end of name`)\n      expect(screenGen).toContain(`app/screens/BowserScreen.tsx`)\n      expect(filesystem.list(`${appPath}/app/screens`)).toContain(\"BowserScreen.tsx\")\n      expect(filesystem.read(`${appPath}/app/screens/BowserScreen.tsx`)).toContain(\n        \"export const BowserScreen\",\n      )\n\n      // app-icons\n      const iconSearchPath = \"assets/images\"\n      const iconMatchString = \"app-icon*.png\"\n\n      const allAppIcons = filesystem.find(filesystem.path(appPath, iconSearchPath), {\n        directories: false,\n        files: true,\n        matching: iconMatchString,\n      })\n\n      allAppIcons.forEach((i) => {\n        expect(filesystem.exists(i) === \"file\").toBe(true)\n        filesystem.remove(i)\n        expect(filesystem.exists(i) === \"file\").toBe(false)\n      })\n\n      const appIconGen = await runIgnite(\n        `generate app-icon --skip-source-equality-validation`,\n        runOpts,\n      )\n\n      expect(appIconGen).toContain(`Generating Expo app icons...`)\n\n      allAppIcons.forEach((i) => {\n        expect(filesystem.exists(i) === \"file\").toBe(true)\n      })\n\n      const inputFiles = filesystem.find(`${appPath}/ignite/templates/app-icon`, {\n        directories: false,\n        files: true,\n        matching: \"*.png\",\n      })\n\n      inputFiles.forEach((i) => {\n        expect(filesystem.exists(i) === \"file\").toBe(true)\n        filesystem.remove(i)\n        expect(filesystem.exists(i) === \"file\").toBe(false)\n      })\n\n      await runIgnite(`generate --update`, runOpts)\n\n      inputFiles.forEach((i) => {\n        expect(filesystem.exists(i) === \"file\").toBe(true)\n      })\n\n      // splash-screen\n      const splashSearchPath = \"assets/images\"\n      const splashMatchString = \"splash-logo*.png\"\n\n      const splashScreenAssets = filesystem.find(filesystem.path(appPath, splashSearchPath), {\n        directories: false,\n        files: true,\n        matching: splashMatchString,\n      })\n\n      splashScreenAssets.forEach((i) => {\n        expect(filesystem.exists(i) === \"file\").toBe(true)\n        filesystem.remove(i)\n        expect(filesystem.exists(i) === \"file\").toBe(false)\n      })\n\n      function verifySplashScreenColor(type: \"android\" | \"ios\" | \"expo\", matchString: string) {\n        const splashScreenColorStrings = {\n          android: filesystem.read(\n            filesystem.path(appPath, \"android/app/src/main/res/values/colors.xml\"),\n          ),\n          ios: filesystem.read(filesystem.path(appPath, \"ios/Foo/BootSplash.storyboard\")),\n          expo: filesystem.read(filesystem.path(appPath, \"app.json\")),\n        }\n\n        const colorContent = splashScreenColorStrings[type]\n\n        if (!colorContent) return\n\n        expect(colorContent).toContain(matchString)\n      }\n\n      verifySplashScreenColor(\"android\", `#191015`)\n      verifySplashScreenColor(\"expo\", `#191015`)\n      verifySplashScreenColor(\n        \"ios\",\n        `red=\"0.0980392156862745\" green=\"0.0627450980392157\" blue=\"0.0823529411764706\"`,\n      )\n\n      const splashScreenGen = await runIgnite(\n        `generate splash-screen 000000  --skip-source-equality-validation`,\n        runOpts,\n      )\n\n      expect(splashScreenGen).toContain(`Generating Expo splash screens`)\n\n      splashScreenAssets.forEach((i) => {\n        expect(filesystem.exists(i) === \"file\").toBe(true)\n      })\n\n      verifySplashScreenColor(\"expo\", `#000000`)\n\n      const inputFile = filesystem.path(appPath, \"ignite/templates/splash-screen/logo.png\")\n      expect(filesystem.exists(inputFile) === \"file\").toBe(true)\n      filesystem.remove(inputFile)\n      expect(filesystem.exists(inputFile) === \"file\").toBe(false)\n      await runIgnite(`generate --update`, runOpts)\n      expect(filesystem.exists(inputFile) === \"file\").toBe(true)\n      // #endregion\n\n      // #region Assert Changes Can Be Commit To Git\n      // commit the change\n      await run(`git add ./app/context ./app/components ./app.json ./assets/images`, runOpts)\n      await run(`git commit -m \"generated test components & assets\"`, runOpts)\n      // #endregion\n\n      // #region Assert package.json Scripts Can Be Run\n      // run the tests; if they fail, run will raise and this test will fail\n      await run(`bun run test`, runOpts)\n      await run(`bun run lint`, runOpts)\n      await run(`bun run compile`, runOpts)\n      await run(`bun run depcruise`, runOpts)\n      expect(await run(\"git diff HEAD --no-ext-diff\", runOpts)).toContain(\"+  Bowser: undefined\")\n      // #endregion\n\n      // we're done!\n    })\n  })\n\n  // Yarn (only testing what might be affected by a different package manager: dependency installation, running commands)\n  describe(`ignite new ${APP_NAME} --debug --packager=yarn --yes`, () => {\n    let tempDir: string\n    let result: string\n    let appPath: string\n    beforeAll(async () => {\n      tempDir = tempy.directory({ prefix: \"ignite-\" })\n\n      result = await spawnIgniteAndPrintIfFail(\n        `new ${APP_NAME} --debug --packager=yarn --workflow=cng --yes`,\n        {\n          pre: `cd ${tempDir}`,\n          post: `cd ${originalDir}`,\n          outputFileName: \"ignite-new-output-yarn.txt\",\n        },\n      )\n\n      appPath = filesystem.path(tempDir, APP_NAME)\n    })\n\n    afterAll(() => {\n      // console.log(tempDir) // uncomment for debugging, then run `code <tempDir>` to see the generated app\n      filesystem.remove(tempDir) // clean up our mess\n    })\n\n    it(\"should print success message\", () => {\n      // at some point this should probably be a snapshot?\n      expect(result).toContain(\"Now get cooking! 🍽\")\n    })\n\n    it(\"should be able to use `generate` command and have pass output pass pnpm run test, pnpm run lint, and pnpm run compile scripts\", async () => {\n      // other common test operations\n      const runOpts = {\n        pre: `cd ${appPath}`,\n        post: `cd ${originalDir}`,\n      }\n\n      // #region Assert package.json Scripts Can Be Run\n      // run the tests; if they fail, run will raise and this test will fail\n      await run(`pnpm run test`, runOpts)\n      await run(`pnpm run lint`, runOpts)\n      await run(`pnpm run compile`, runOpts)\n      await run(`pnpm run depcruise`, runOpts)\n      expect(await run(\"git diff HEAD --no-ext-diff\", runOpts)).toBe(\"\")\n    })\n    // #endregion\n\n    // we're done!\n  })\n})\n\nasync function checkForLeftoverHelloWorld(filePath: string) {\n  const ignoreFolders = [\n    \"/xcuserdata\",\n    \"bun.lock\",\n    \".git\",\n    \"node_modules\",\n    \"Pods\",\n    \"/build\",\n    \".expo\",\n    \".yarn\",\n  ]\n  // ignore some folders\n  if (!ignoreFolders.every((f) => !filePath.includes(f))) return\n\n  if (!filesystem.isDirectory(filePath)) {\n    // we append the filePath to the end of the message to make it easier to\n    // find the file in the console output\n    const contents = filesystem.read(filePath) + ` (Filename: ${filePath})`\n    expect(contents).not.toContain(\"helloworld\")\n    expect(contents).not.toContain(\"HelloWorld\")\n    expect(contents).not.toContain(\"hello-world\")\n\n    // it's a file, so eject\n    return\n  }\n\n  // check to make sure there are no instances of helloworld or HelloWorld or hello-world\n  // anywhere in the app -- including folder and filenames.\n  const appFiles = filesystem.list(filePath) ?? []\n\n  for (const file of appFiles) {\n    expect(file).not.toContain(\"helloworld\")\n    expect(file).not.toContain(\"HelloWorld\")\n    expect(file).not.toContain(\"hello-world\")\n    await checkForLeftoverHelloWorld(`${filePath}/${file}`)\n  }\n}\n"
  },
  {
    "path": "test/vanilla/ignite-remove-demo.test.ts",
    "content": "import { filesystem } from \"gluegun\"\nimport * as tempy from \"tempy\"\n\nimport { runIgnite } from \"../_test-helpers\"\n\nconst BOILERPLATE_PATH = filesystem.path(__dirname, \"../../boilerplate\")\n\nconst setup = (): { TEMP_DIR: string } => {\n  const TEMP_DIR = tempy.directory({ prefix: \"ignite-\" })\n\n  beforeEach(() => {\n    // create the destination directory\n    filesystem.dir(TEMP_DIR)\n    // copy the relevant folders\n    filesystem.copy(BOILERPLATE_PATH + \"/app\", TEMP_DIR + \"/app\", { overwrite: true })\n    filesystem.copy(BOILERPLATE_PATH + \"/.maestro\", TEMP_DIR + \"/.maestro\", { overwrite: true })\n    filesystem.copy(BOILERPLATE_PATH + \"/assets\", TEMP_DIR + \"/assets\", { overwrite: true })\n  })\n\n  afterEach(() => {\n    filesystem.remove(TEMP_DIR) // clean up our mess\n  })\n\n  return { TEMP_DIR }\n}\n\ndescribe(\"ignite-cli remove-demo\", () => {\n  const { TEMP_DIR } = setup()\n\n  it(\"should print the expected response\", async () => {\n    const result = await runIgnite(`remove-demo ${TEMP_DIR}`)\n\n    // \"/user/home/ignite\" replaces the temp directory, so we don't get failures when it changes every test run\n    const MOCK_DIR = `/user/home/ignite`\n    const output = result.replace(new RegExp(TEMP_DIR, \"g\"), MOCK_DIR)\n\n    expect(output).toMatchSnapshot()\n  })\n})\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowSyntheticDefaultImports\": true,\n    \"experimentalDecorators\": true,\n    \"target\": \"es6\",\n    \"lib\": [\"es2015\", \"es2016\", \"es2016.array.include\", \"scripthost\", \"dom\"],\n    \"module\": \"commonjs\",\n    \"moduleResolution\": \"node\",\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": true,\n    \"noUnusedLocals\": true,\n    \"sourceMap\": true,\n    \"outDir\": \"./build\",\n    \"skipLibCheck\": true,\n    \"types\": [\"node\", \"jest\"]\n  },\n  \"include\": [\"./src\"],\n  \"exclude\": [\"./boilerplate\", \"./templates\", \"**/**.test.ts\"]\n}\n"
  }
]