Repository: infinitered/ignite Branch: master Commit: a0464bf13370 Files: 276 Total size: 983.4 KB Directory structure: gitextract_mtxjpkh_/ ├── .circleci/ │ └── config.yml ├── .dependency-cruiser.js ├── .eslintignore ├── .eslintrc.js ├── .github/ │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ └── enhancement.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .yarnrc.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── bin/ │ └── ignite ├── boilerplate/ │ ├── .dependency-cruiser.js │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .maestro/ │ │ ├── flows/ │ │ │ ├── FavoritePodcast.yaml │ │ │ └── Login.yaml │ │ └── shared/ │ │ ├── _Login.yaml │ │ └── _OnFlowStart.yaml │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── README.md │ ├── app/ │ │ ├── app.tsx │ │ ├── components/ │ │ │ ├── AutoImage.tsx │ │ │ ├── Button.tsx │ │ │ ├── Card.tsx │ │ │ ├── EmptyState.tsx │ │ │ ├── Header.tsx │ │ │ ├── Icon.tsx │ │ │ ├── ListItem.tsx │ │ │ ├── Screen.tsx │ │ │ ├── Text.test.tsx │ │ │ ├── Text.tsx │ │ │ ├── TextField.tsx │ │ │ └── Toggle/ │ │ │ ├── Checkbox.tsx │ │ │ ├── Radio.tsx │ │ │ ├── Switch.tsx │ │ │ └── Toggle.tsx │ │ ├── config/ │ │ │ ├── config.base.ts │ │ │ ├── config.dev.ts │ │ │ ├── config.prod.ts │ │ │ └── index.ts │ │ ├── context/ │ │ │ ├── AuthContext.tsx │ │ │ └── EpisodeContext.tsx │ │ ├── devtools/ │ │ │ ├── ReactotronClient.ts │ │ │ ├── ReactotronClient.web.ts │ │ │ └── ReactotronConfig.ts │ │ ├── i18n/ │ │ │ ├── ar.ts │ │ │ ├── demo-ar.ts │ │ │ ├── demo-en.ts │ │ │ ├── demo-es.ts │ │ │ ├── demo-fr.ts │ │ │ ├── demo-hi.ts │ │ │ ├── demo-ja.ts │ │ │ ├── demo-ko.ts │ │ │ ├── en.ts │ │ │ ├── es.ts │ │ │ ├── fr.ts │ │ │ ├── hi.ts │ │ │ ├── index.ts │ │ │ ├── ja.ts │ │ │ ├── ko.ts │ │ │ └── translate.ts │ │ ├── navigators/ │ │ │ ├── AppNavigator.tsx │ │ │ ├── DemoNavigator.tsx │ │ │ ├── navigationTypes.ts │ │ │ └── navigationUtilities.ts │ │ ├── screens/ │ │ │ ├── DemoCommunityScreen.tsx │ │ │ ├── DemoDebugScreen.tsx │ │ │ ├── DemoPodcastListScreen.tsx │ │ │ ├── DemoShowroomScreen/ │ │ │ │ ├── DemoDivider.tsx │ │ │ │ ├── DemoShowroomScreen.tsx │ │ │ │ ├── DemoUseCase.tsx │ │ │ │ ├── DrawerIconButton.tsx │ │ │ │ ├── SectionListWithKeyboardAwareScrollView.tsx │ │ │ │ └── demos/ │ │ │ │ ├── DemoAutoImage.tsx │ │ │ │ ├── DemoButton.tsx │ │ │ │ ├── DemoCard.tsx │ │ │ │ ├── DemoEmptyState.tsx │ │ │ │ ├── DemoHeader.tsx │ │ │ │ ├── DemoIcon.tsx │ │ │ │ ├── DemoListItem.tsx │ │ │ │ ├── DemoText.tsx │ │ │ │ ├── DemoTextField.tsx │ │ │ │ ├── DemoToggle.tsx │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── ErrorScreen/ │ │ │ │ ├── ErrorBoundary.tsx │ │ │ │ └── ErrorDetails.tsx │ │ │ ├── LoginScreen.tsx │ │ │ └── WelcomeScreen.tsx │ │ ├── services/ │ │ │ └── api/ │ │ │ ├── apiProblem.test.ts │ │ │ ├── apiProblem.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── theme/ │ │ │ ├── colors.ts │ │ │ ├── colorsDark.ts │ │ │ ├── context.tsx │ │ │ ├── context.utils.ts │ │ │ ├── spacing.ts │ │ │ ├── spacingDark.ts │ │ │ ├── styles.ts │ │ │ ├── theme.ts │ │ │ ├── timing.ts │ │ │ ├── types.ts │ │ │ └── typography.ts │ │ └── utils/ │ │ ├── crashReporting.ts │ │ ├── delay.ts │ │ ├── formatDate.ts │ │ ├── gestureHandler.native.ts │ │ ├── gestureHandler.ts │ │ ├── openLinkInBrowser.ts │ │ ├── storage/ │ │ │ ├── index.ts │ │ │ └── storage.test.ts │ │ ├── useHeader.tsx │ │ ├── useIsMounted.ts │ │ └── useSafeAreaInsetsStyle.ts │ ├── app.config.ts │ ├── app.json │ ├── babel.config.js │ ├── eas.json │ ├── ignite/ │ │ └── templates/ │ │ ├── component/ │ │ │ └── NAME.tsx.ejs │ │ ├── navigator/ │ │ │ └── NAMENavigator.tsx.ejs │ │ └── screen/ │ │ └── NAMEScreen.tsx.ejs │ ├── index.tsx │ ├── jest.config.js │ ├── metro.config.js │ ├── package.json │ ├── src/ │ │ └── app/ │ │ ├── _layout.tsx │ │ └── index.tsx │ ├── test/ │ │ ├── i18n.test.ts │ │ ├── mockFile.ts │ │ ├── setup.ts │ │ └── test-tsconfig.json │ ├── tsconfig.json │ └── types/ │ └── lib.es5.d.ts ├── docs/ │ ├── Guide.md │ ├── QuickStart.md │ ├── README.md │ ├── _category_.json │ ├── boilerplate/ │ │ ├── Boilerplate.md │ │ ├── _category_.json │ │ ├── android.md │ │ ├── app/ │ │ │ ├── _category_.json │ │ │ ├── app.md │ │ │ ├── app.tsx.md │ │ │ ├── components/ │ │ │ │ ├── AutoImage.md │ │ │ │ ├── Button.md │ │ │ │ ├── Card.md │ │ │ │ ├── Checkbox.md │ │ │ │ ├── Components.md │ │ │ │ ├── EmptyState.md │ │ │ │ ├── Header.md │ │ │ │ ├── Icon.md │ │ │ │ ├── ListItem.md │ │ │ │ ├── Radio.md │ │ │ │ ├── Screen.md │ │ │ │ ├── Switch.md │ │ │ │ ├── Text.md │ │ │ │ ├── TextField.md │ │ │ │ ├── _category_.json │ │ │ │ └── _toggle_props.mdx │ │ │ ├── config/ │ │ │ │ ├── Config.md │ │ │ │ └── _category_.json │ │ │ ├── context/ │ │ │ │ ├── Context.md │ │ │ │ └── _category_.json │ │ │ ├── devtools/ │ │ │ │ ├── Devtools.md │ │ │ │ └── _category_.json │ │ │ ├── i18n/ │ │ │ │ ├── Internationalization.md │ │ │ │ └── _category_.json │ │ │ ├── navigators/ │ │ │ │ ├── AppNavigator.tsx.md │ │ │ │ ├── Navigation.md │ │ │ │ ├── _category_.json │ │ │ │ └── navigationUtilities.ts.md │ │ │ ├── screens/ │ │ │ │ ├── Screens.md │ │ │ │ └── _category_.json │ │ │ ├── services/ │ │ │ │ ├── Services.md │ │ │ │ ├── _category_.json │ │ │ │ └── api.ts.md │ │ │ ├── theme/ │ │ │ │ ├── Theming.md │ │ │ │ ├── _category_.json │ │ │ │ ├── colors.ts.md │ │ │ │ ├── context.ts.md │ │ │ │ ├── spacing.ts.md │ │ │ │ └── typography.ts.md │ │ │ └── utils/ │ │ │ ├── Utils.md │ │ │ ├── _category_.json │ │ │ ├── useHeader.tsx.md │ │ │ └── useSafeAreaInsetsStyle.ts.md │ │ ├── app.json.md │ │ ├── assets.md │ │ ├── eas.json.md │ │ ├── ignite.md │ │ ├── index.tsx.md │ │ ├── ios.md │ │ ├── maestro.md │ │ ├── plugins/ │ │ │ ├── Plugins.md │ │ │ └── _category_.json │ │ └── test/ │ │ ├── Test.md │ │ └── _category_.json │ ├── cli/ │ │ ├── Ignite-CLI.md │ │ ├── Remove-Demo-Code.md │ │ ├── Troubleshooting.md │ │ └── _category_.json │ ├── concept/ │ │ ├── Concepts.md │ │ ├── Error-Boundary.md │ │ ├── Generator-Templates.md │ │ ├── Generators.md │ │ ├── Styling.md │ │ ├── Testing.md │ │ ├── TypeScript.md │ │ ├── Upgrades.md │ │ └── _category_.json │ ├── contributing/ │ │ ├── Contributing-To-Ignite.md │ │ ├── Releasing-Ignite.md │ │ ├── Tour-of-Ignite.md │ │ └── _category_.json │ └── expo/ │ ├── CNG.md │ ├── DIY.md │ ├── EAS.md │ ├── Expo-and-Ignite.md │ └── _category.json ├── jest.config.js ├── package.json ├── src/ │ ├── assets/ │ │ ├── index.ts │ │ ├── logo-sm.ascii.txt │ │ └── logo.ascii.txt │ ├── cli.ts │ ├── commands/ │ │ ├── cache.ts │ │ ├── deprecated.ts │ │ ├── doctor.ts │ │ ├── generate/ │ │ │ ├── app-icon.ts │ │ │ └── splash-screen.ts │ │ ├── generate.ts │ │ ├── help.ts │ │ ├── ignite.ts │ │ ├── issue.ts │ │ ├── new.ts │ │ ├── remove-demo-markup.ts │ │ ├── remove-demo.ts │ │ ├── rename.ts │ │ └── update.ts │ ├── tools/ │ │ ├── __snapshots__/ │ │ │ └── markup.test.ts.snap │ │ ├── cache.ts │ │ ├── demo.ts │ │ ├── filesystem-ext.ts │ │ ├── flag.ts │ │ ├── generators.ts │ │ ├── markup.test.ts │ │ ├── markup.ts │ │ ├── packager.test.ts │ │ ├── packager.ts │ │ ├── pretty.ts │ │ ├── react-native.test.ts │ │ ├── react-native.ts │ │ ├── spawn.ts │ │ ├── strip-ansi.ts │ │ └── validations.ts │ └── types.ts ├── template.config.js ├── test/ │ ├── _test-helpers.ts │ └── vanilla/ │ ├── __snapshots__/ │ │ └── ignite-remove-demo.test.ts.snap │ ├── ignite-generate.test.ts │ ├── ignite-help.test.ts │ ├── ignite-new-expo-router.test.ts │ ├── ignite-new.test.ts │ └── ignite-remove-demo.test.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .circleci/config.yml ================================================ # JavaScript Node CircleCI 2.0 configuration file # # Check https://circleci.com/docs/2.0/language-javascript/ for more details # defaults: &defaults docker: # Choose the version of Node you want here - image: cimg/node:20.19.4 working_directory: /mnt/ramdisk/repo version: 2.1 parameters: "force-release-docs": type: boolean default: false orbs: publish-docs: infinitered/publish-docs@0.5.0 jobs: tests: <<: *defaults resource_class: large steps: - checkout - run: name: Install the latest version of bun command: curl -fsSL https://bun.sh/install | bash - run: name: Link bun command: sudo ln -s ~/.bun/bin/bun /usr/local/bin/ - restore_cache: name: Restore node modules keys: - v2-dependencies-{{ checksum "package.json" }}-{{ arch }} # fallback to using the latest cache if no exact match is found - v2-dependencies- - run: name: Install dependencies command: pnpm install - save_cache: name: Save node modules paths: - node_modules key: v1-dependencies-{{ checksum "package.json" }}-{{ arch }} - run: # We don't want to install CocoaPods on ubuntu where we run the tests. name: Set up dummy `pod` command command: | sudo ln /bin/true /usr/local/bin/pod - run: name: Ensure git user is configured command: | git config --global user.email "ci@infinite.red" git config --global user.name "Infinite Red" - restore_cache: name: Restore ignite dependency cache key: ignite-deps-cache-{{ .Environment.IGNITE_DEPS_PACKAGER }}-{{ checksum "boilerplate/package.json" }}-{{ arch }}-{{ .Environment.IGNITE_DEPS_KEY_SUFFIX }} - run: name: Run static tests command: pnpm run format:check && pnpm run lint && pnpm run typecheck && pnpm run depcruise - run: name: Run jest tests command: pnpm run test no_output_timeout: 10m - save_cache: name: Save ignite dependency cache paths: - ~/.cache/ignite key: ignite-deps-cache-{{ .Environment.IGNITE_DEPS_PACKAGER }}-{{ checksum "boilerplate/package.json" }}-{{ arch }}-{{ .Environment.IGNITE_DEPS_KEY_SUFFIX }} publish: <<: *defaults steps: - checkout - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc - restore_cache: name: Restore node modules keys: - v1-dependencies-{{ checksum "package.json" }}-{{ arch }} # fallback to using the latest cache if no exact match is found - v1-dependencies- - run: name: Build package command: pnpm run build # Run semantic-release after all the above is set. - run: name: Publish to npm command: | export NPM_ID_TOKEN=$(circleci run oidc get --claims '{"aud": "npm:registry.npmjs.org"}') pnpm run ci:publish # Publishing docs details publish-details: &publish-details description: "Infinite Red's hottest boilerplate for React Native." git_email: "ci@infinite.red" git_username: "Infinite Red CI" label: "Ignite" project_name: "ignite-cli" source_docs_dir: docs source_repo_directory: "source" target_docs_dir: "docs" target_repo: "git@github.com:infinitered/ir-docs.git" target_repo_directory: "target" ssh_key_fingerprint: "SHA256:aovjemSAAaR4+VcbAyUjlRrHpVoKistvCQcc0adPkHU" workflows: version: 2 test_and_release: jobs: - tests - publish: context: ignite-npm-context requires: - tests filters: branches: only: master release-docs: when: and: - not: << pipeline.parameters.force-release-docs >> - true # Placeholder for correct YAML structure jobs: - publish-docs/publish_docs: <<: *publish-details filters: branches: only: - master tags: only: - '*v[0-9]+\.[0-9]+\.[0-9]+' force-release-docs: when: << pipeline.parameters.force-release-docs >> jobs: - publish-docs/publish_docs: <<: *publish-details ================================================ FILE: .dependency-cruiser.js ================================================ /** @type {import('dependency-cruiser').IConfiguration} */ module.exports = { forbidden: [ { name: "no-circular", severity: "warn", comment: "This dependency is part of a circular relationship. You might want to revise " + "your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ", from: {}, to: { circular: true, }, }, { name: "no-orphans", comment: "This is an orphan module - it's likely not used (anymore?). Either use it or " + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + "add an exception for it in your dependency-cruiser configuration. By default " + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + "files (.d.ts), tsconfig.json and some of the babel and webpack configs.", severity: "warn", from: { orphan: true, pathNot: [ "(^|/)\\.[^/]+\\.(js|cjs|mjs|ts|json)$", "\\.d\\.ts$", "(^|/)tsconfig\\.json$", "(^|/)(babel|webpack)\\.config\\.(js|cjs|mjs|ts|json)$", "template\\.config\\.js$", // template config for Ignite CLI "bin/ignite$", // CLI entry point ], }, to: {}, }, { name: "no-deprecated-core", comment: "A module depends on a node core module that has been deprecated. Find an alternative - these are " + "bound to exist - node doesn't deprecate lightly.", severity: "warn", from: {}, to: { dependencyTypes: ["core"], path: ["^(punycode)$", "^(domain)$", "^(constants)$", "^(sys)$"], }, }, { name: "not-to-deprecated", comment: "This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " + "version of that module, or find an alternative. Deprecated modules are a security risk.", severity: "warn", from: {}, to: { dependencyTypes: ["deprecated"], }, }, { name: "no-non-package-json", severity: "error", comment: "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + "That's problematic as the package either (1) won't be available on live (2) will be " + "available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " + "in your package.json.", from: {}, to: { dependencyTypes: ["npm-no-pkg", "npm-unknown"], }, }, { name: "not-to-unresolvable", comment: "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " + "module: add it to your package.json. In all other cases you likely already know what to do.", severity: "error", from: {}, to: { couldNotResolve: true, }, }, { name: "no-duplicate-dep-types", comment: "Likely this module depends on an external ('npm') package that occurs more than once " + "in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " + "maintenance problems later on.", severity: "warn", from: {}, to: { moreThanOneDependencyType: true, dependencyTypesNot: ["type-only"], }, }, { name: "not-to-spec", comment: "This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. " + "If there's something in a spec that's of use to other modules, it doesn't have that single " + "responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.", severity: "error", from: { pathNot: "\\.(spec|test)\\.(js|mjs|cjs|ts|tsx)$", }, to: { path: "\\.(spec|test)\\.(js|mjs|cjs|ts|tsx)$", }, }, { name: "not-to-dev-dep", severity: "error", comment: "This module depends on an npm package from the 'devDependencies' section of your " + "package.json. It looks like something that ships to production, though. To prevent problems " + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + "section of your package.json. If this module is development only - add it to the " + "from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration", from: { path: "^(src)", pathNot: "\\.(spec|test)\\.(js|mjs|cjs|ts|tsx)$", }, to: { dependencyTypes: ["npm-dev"], pathNot: ["node_modules/@types/"], }, }, { name: "optional-deps-used", severity: "info", comment: "This module depends on an npm package that is declared as an optional dependency " + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + "If you're using an optional dependency here by design - add an exception to your" + "dependency-cruiser configuration.", from: {}, to: { dependencyTypes: ["npm-optional"], }, }, { name: "peer-deps-used", comment: "This module depends on an npm package that is declared as a peer dependency " + "in your package.json. This makes sense if your package is e.g. a plugin, but in " + "other cases - maybe not so much. If the use of a peer dependency is intentional " + "add an exception to your dependency-cruiser configuration.", severity: "warn", from: {}, to: { dependencyTypes: ["npm-peer"], }, }, ], options: { doNotFollow: { path: "node_modules", }, tsPreCompilationDeps: true, tsConfig: { fileName: "tsconfig.json", }, enhancedResolveOptions: { exportsFields: ["exports"], conditionNames: ["import", "require", "node", "default"], extensions: [".ts", ".js", ".json"], }, reporterOptions: { dot: { collapsePattern: "node_modules/(@[^/]+/[^/]+|[^/]+)", }, archi: { collapsePattern: "^(src|test)/[^/]+", }, text: { highlightFocused: true, }, }, }, } ================================================ FILE: .eslintignore ================================================ node_modules .vscode **/*.snap **/*.txt bin/ignite ================================================ FILE: .eslintrc.js ================================================ const boilerplateLintConfig = require("./boilerplate/.eslintrc.js") module.exports = { ...boilerplateLintConfig, } ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributing to Ignite CLI We welcome all contributors to Ignite CLI! This contributing guide will help you get up and running to submit your first pull request. Before submitting a pull request, you will want to make sure that your branch meets the following requirements: \_Working on Ignite CLI requires pnpm 10.9.0 - Everything works on iOS/Android - Jest tests pass in the root folder (`pnpm run test`) - New tests are included for any new functionality - Code is compliant with our linter and prettier (`pnpm run format:write && pnpm run lint`) - Branch has already been [synced with the upstream repo](https://help.github.com/articles/syncing-a-fork/) and any merge-conflicts have been resolved. ## Requirements - Node (reasonably recent version) - pnpm (while you can use Ignite CLI without pnpm, we require it for contributors) ## Getting Started 1. Fork and then clone the repo (`git clone git@github.com:/ignite.git`) 2. CD into the directory (`cd ignite`) 3. Uninstall npm version (`pnpm remove ignite-cli -g`) 4. Pull all package dependencies (`pnpm install`) 5. Link the local binary (`pnpm link`) Test it out: ```sh $ ignite --version $ which ignite /usr/local/bin/ignite $ ignite new UberForHeadLice ... ``` Now you're ready to check out a new branch and get hacking on Ignite CLI! ## Source Code To 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). ## How to Build and Run App ```sh $ cd ~/your/projects $ ignite new HackingOnIgnite ``` ## Testing the App We use Jest for testing. To run tests from the ignite folder: ```sh $ pnpm run test ``` **To Run Lint** from ignite: ```sh $ pnpm run lint ``` ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yml ================================================ name: Bug report description: For issues with running ignite on your computer labels: ["bug"] body: - type: markdown attributes: value: > Before creating a **bug report**, please check the following: * Ensure you're running the latest version release. * Ensure there isn't an existing issue for your bug. If there is, leave a comment on the existing issue. If you're unsure whether you've hit a bug, check out the #react-native channel in our [Slack Community](https://community.infinite.red). - type: textarea attributes: label: Describe the bug description: Also include a description of how to reproduce the bug validations: required: true - type: input id: version attributes: label: Ignite version description: If you're not on release, provide the commit hash placeholder: 7.15.0 validations: required: true - type: textarea attributes: label: Additional info description: Run `npx ignite-cli doctor` and paste the results validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Discussions url: https://github.com/infinitered/ignite/discussions about: For questions and discussion about ignite - name: Slack Community url: https://community.infinite.red/ about: Check out our community for more real-time discussion - name: Twitter Community url: https://twitter.com/i/communities/1509407040095068166 about: We post news, tips, requests for help, and other discussions. ================================================ FILE: .github/ISSUE_TEMPLATE/enhancement.md ================================================ --- name: Enhancement about: For ignite enhancement suggestions or feature request title: "" labels: "enhancement" assignees: "" --- ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Description - Issues: ## Screenshots | Before | After | | -------------------------------- | ------------------------------- | | [Insert before screenshot/video] | [Insert after screenshot/video] | ## Checklist - [ ] `README.md` and other relevant documentation has been updated with my changes - [ ] 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)). ================================================ FILE: .gitignore ================================================ # OSX # .idea .DS_Store npm-debug.log npm-debug.log* yarn-error.log lerna-debug.log node_modules .vscode/* # TS build /build coverage # This is at root because we don't want to ignore it in # newly spun-up apps, but we do want to ignore it in # Ignite's source repo. boilerplate/yarn.lock boilerplate/bun.lockb boilerplate/bun.lock boilerplate/pnpm-lock.yaml boilerplate/.gitignore.template # Test artifacts test/artifacts/* # flame CLI .config # Ignore everything inside .yarn except the necessary subdirectories .yarn/* !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/sdks !.yarn/versions # Ignore Yarn's global cache (not needed for Zero-Installs) .yarn/cache # Ignore build artifacts .yarn/build-state.yml .yarn/install-state.gz # Dependency graph visualizations ignite-cli-dependency-graph.svg ignite-cli-dependency-graph.png ================================================ FILE: .nvmrc ================================================ v20 ================================================ FILE: .prettierignore ================================================ /build /boilerplate/* !/boilerplate/app ================================================ FILE: .prettierrc ================================================ { "printWidth": 100, "semi": false, "singleQuote": false, "trailingComma": "all", "quoteProps": "consistent" } ================================================ FILE: .yarnrc.yml ================================================ enableGlobalCache: false nodeLinker: node-modules yarnPath: .yarn/releases/yarn-4.9.1.cjs enableScripts: false ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In 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. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project 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. Project 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. ## Scope This 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. ## Enforcement Instances 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. Project 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. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2016-present Infinite Red, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- Note: this license only applies to the source code of the Ignite CLI, including the source code of the boilerplate project. It does not apply to any source code generated by the CLI, which are property of the person or entity that used the CLI to generate them. However, 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. ================================================ FILE: README.md ================================================

Ignite README Splash Image

# Ignite - the battle-tested React Native boilerplate npm version ![GitHub Repo stars](https://img.shields.io/github/stars/infinitered/ignite) ![Twitter Follow](https://img.shields.io/twitter/follow/ir_ignite) [![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) ## Proven React Native boilerplate Developed and maintained consistently since 2016, Ignite is the oldest active and most popular third-party React Native / Expo app boilerplate. This 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! ## Intro Videos Here are a few videos / talks that introduce Ignite and show off some of its features. Check them out!
Getting Started with Ignite
Getting Started with Ignite
Sweetening React Native Development With Ignite
Ignite talk at Chain React 2024 - Robin Heinze
## [Full Documentation](https://github.com/infinitered/ignite/blob/master/docs/README.md) We'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! ## Tech Stack Nothing 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: | Library | Category | Version | Description | | -------------------------------- | -------------------- | ------- | ---------------------------------------------- | | React Native | Mobile Framework | v0.81 | The best cross-platform mobile framework | | React | UI Framework | v19 | The most popular UI framework in the world | | TypeScript | Language | v5 | Static typechecking | | React Navigation | Navigation | v7 | Performant and consistent navigation framework | | Expo | SDK | v55 | Allows (optional) Expo modules | | Expo Font | Custom Fonts | v14 | Import custom fonts | | Expo Localization | Internationalization | v17 | i18n support (including RTL!) | | RN Reanimated | Animations | v4 | Beautiful and performant animations | | MMKV | Persistence | v3 | State persistence | | apisauce | REST client | v3 | Communicate with back-end | | Jest | Test Runner | v29 | Standard test runner for JS apps | | date-fns | Date library | v4 | Excellent date library | | react-native-keyboard-controller | Keyboard library | v1 | Great keyboard manager library | | react-native-edge-to-edge | UI library | v1 | Enables edge-to-edge in Android | | Reactotron RN | Inspector/Debugger | v5 | JS debugging | | Maestro | Testing Framework | | Automate end-to-end UI testing | | Hermes | JS engine | | Fine-tuned JS engine for RN | Ignite 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. ## Quick Start Prerequisites: - You'll need at least a recent version of [Node](https://nodejs.org/en) to run the CLI - 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). The Ignite CLI will walk you through the steps to ignite a new React Native app: ```bash # Get walked through the prompts for the different options to start your new app npx ignite-cli@latest new PizzaApp # Accept all the recommended defaults and get straight to coding! npx ignite-cli@latest new PizzaApp --yes ``` Once you're up and running, check out our [Getting Started Guide](https://docs.infinite.red/ignite-cli/Guide/). If 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). ### Troubleshooting The above commands may fail with various errors, depending on your operating system and dependency versions. Some troubleshooting steps to follow: - Uninstall global versions of the Ignite CLI via `npm uninstall -g ignite-cli` and use the CLI via `npx ignite-cli` - 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. - 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. - 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` - Opening the project in Xcode can give you other insights into what's happening: `open ./ios/.xcworkspace` - Add the `--debug` switch to the Ignite CLI new command to provide additional output during project initialization ## Reporting Bugs / Getting Help If 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)). If 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. ## Contributing to Ignite Want 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. ## Need Inspiration? If 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! ## No time to learn React Native? Hire Infinite Red for your next project We 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. Whether 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. Ready to see how we can work together? [Send us a message](https://infinite.red/contact) ## Further Reading - 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) - 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). - [Who are We?](https://infinite.red/react-native-app-development-company) - Learn More About Infinite Red, the top React Native app development company ## License and Trademark Notice This 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. ### Note on Generated Code The 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. However, 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. ================================================ FILE: bin/ignite ================================================ #!/usr/bin/env node /* tslint:disable */ // speed up `ignite-cli --version` et al if (["v", "version", "-v", "--v", "-version", "--version"].includes(process.argv[2])) { var contents = require("fs").readFileSync(__dirname + "/../package.json") var package = JSON.parse(contents) // now output the version and exit console.log(package.version) process.exit(0) } // normal source directory var sourceDir = __dirname + "/../build" // check if we're running in dev mode var devMode = require("fs").existsSync(`${__dirname}/../src`) var wantsCompiled = process.argv.indexOf("--compiled-build") >= 0 if (devMode && !wantsCompiled) { // hook into ts-node so we can run typescript on the fly require("ts-node").register({ project: `${__dirname}/../tsconfig.json` }) sourceDir = __dirname + "/../src" } // kick off ignite! require(sourceDir + "/cli").run(process.argv) ================================================ FILE: boilerplate/.dependency-cruiser.js ================================================ const metroConfig = require("./metro.config.js") const platforms = ["ios", "android", "web", "native"] const extensions = metroConfig?.resolver?.sourceExts.flatMap((pExt) => platforms.map((platform) => `.${platform}.${pExt}`).concat(`.${pExt}`), ) /** @type {import('dependency-cruiser').IConfiguration} */ module.exports = { forbidden: [ { name: "no-circular", severity: "warn", comment: "This dependency is part of a circular relationship. You might want to revise " + "your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ", from: {}, to: { circular: true, }, }, { name: "no-orphans", comment: "This is an orphan module - it's likely not used (anymore?). Either use it or " + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + "add an exception for it in your dependency-cruiser configuration. By default " + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + "files (.d.ts), tsconfig.json and some of the babel and webpack configs.", severity: "warn", from: { orphan: true, pathNot: [ "(^|/)\\.[^/]+\\.(js|cjs|mjs|ts|json)$", "\\.d\\.ts$", "(^|/)tsconfig\\.json$", "(^|/)(babel|webpack)\\.config\\.(js|cjs|mjs|ts|json)$", "crashReporting\\.ts$", // Boilerplate file for future crash reporting setup "utils/delay\\.ts$", // Utility function for delaying execution ], }, to: {}, }, { name: "no-deprecated-core", comment: "A module depends on a node core module that has been deprecated. Find an alternative - these are " + "bound to exist - node doesn't deprecate lightly.", severity: "warn", from: {}, to: { dependencyTypes: ["core"], path: ["^(punycode)$", "^(domain)$", "^(constants)$", "^(sys)$"], }, }, { name: "not-to-deprecated", comment: "This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " + "version of that module, or find an alternative. Deprecated modules are a security risk.", severity: "warn", from: {}, to: { dependencyTypes: ["deprecated"], }, }, { name: "no-non-package-json", severity: "error", comment: "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + "That's problematic as the package either (1) won't be available on live (2) will be " + "available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " + "in your package.json.", from: {}, to: { dependencyTypes: ["npm-no-pkg", "npm-unknown"], }, }, { name: "not-to-unresolvable", comment: "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " + "module: add it to your package.json. In all other cases you likely already know what to do.", severity: "error", from: {}, to: { couldNotResolve: true, }, }, { name: "no-duplicate-dep-types", comment: "Likely this module depends on an external ('npm') package that occurs more than once " + "in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " + "maintenance problems later on.", severity: "warn", from: {}, to: { moreThanOneDependencyType: true, dependencyTypesNot: ["type-only"], }, }, { name: "not-to-spec", comment: "This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. " + "If there's something in a spec that's of use to other modules, it doesn't have that single " + "responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.", severity: "error", from: { pathNot: "\\.(spec|test)\\.(js|mjs|cjs|ts|tsx)$", }, to: { path: "\\.(spec|test)\\.(js|mjs|cjs|ts|tsx)$", }, }, { name: "not-to-dev-dep", severity: "error", comment: "This module depends on an npm package from the 'devDependencies' section of your " + "package.json. It looks like something that ships to production, though. To prevent problems " + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + "section of your package.json. If this module is development only - add it to the " + "from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration", from: { path: "^(app|src)", pathNot: "\\.(spec|test)\\.(js|mjs|cjs|ts|tsx)$", }, to: { dependencyTypes: ["npm-dev"], pathNot: ["node_modules/@types/"], exoticRequireNot: [ "react-native/Libraries/Utilities/codegenNativeComponent", "react-native/Libraries/Utilities/codegenNativeCommands", ], }, }, { name: "optional-deps-used", severity: "info", comment: "This module depends on an npm package that is declared as an optional dependency " + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + "If you're using an optional dependency here by design - add an exception to your" + "dependency-cruiser configuration.", from: {}, to: { dependencyTypes: ["npm-optional"], }, }, { name: "peer-deps-used", comment: "This module depends on an npm package that is declared as a peer dependency " + "in your package.json. This makes sense if your package is e.g. a plugin, but in " + "other cases - maybe not so much. If the use of a peer dependency is intentional " + "add an exception to your dependency-cruiser configuration.", severity: "warn", from: {}, to: { dependencyTypes: ["npm-peer"], }, }, ], options: { doNotFollow: { path: "node_modules", }, tsPreCompilationDeps: true, tsConfig: { fileName: "tsconfig.json", }, enhancedResolveOptions: { exportsFields: ["exports"], conditionNames: ["import", "require", "node", "default"], // React Native / Metro bundler support for platform-specific extensions // See: https://reactnative.dev/docs/platform-specific-code // See: https://github.com/sverweij/dependency-cruiser/issues/511 extensions, }, reporterOptions: { dot: { collapsePattern: "node_modules/(@[^/]+/[^/]+|[^/]+)", }, archi: { collapsePattern: "^(app|src|test)/[^/]+", }, text: { highlightFocused: true, }, }, }, } ================================================ FILE: boilerplate/.eslintignore ================================================ node_modules ios android .expo .vscode ignite/ignite.json package.json .eslintignore ================================================ FILE: boilerplate/.eslintrc.js ================================================ // https://docs.expo.dev/guides/using-eslint/ module.exports = { root: true, extends: [ "plugin:@typescript-eslint/recommended", "plugin:react/recommended", "plugin:react-native/all", // `expo` must come after `standard` or its globals configuration will be overridden "expo", // `jsx-runtime` must come after `expo` or it will be overridden "plugin:react/jsx-runtime", "prettier", ], plugins: ["reactotron", "prettier"], rules: { "prettier/prettier": "error", // typescript-eslint "@typescript-eslint/array-type": 0, "@typescript-eslint/ban-ts-comment": 0, "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-unused-vars": [ "error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_", }, ], "@typescript-eslint/no-var-requires": 0, "@typescript-eslint/no-require-imports": 0, "@typescript-eslint/no-empty-object-type": 0, // eslint "no-use-before-define": 0, "no-restricted-imports": [ "error", { paths: [ // Prefer named exports from 'react' instead of importing `React` { name: "react", importNames: ["default"], message: "Import named exports from 'react' instead.", }, { name: "react-native", importNames: ["SafeAreaView"], message: "Use the SafeAreaView from 'react-native-safe-area-context' instead.", }, { name: "react-native", importNames: ["Text", "Button", "TextInput"], message: "Use the custom wrapper component from '@/components'.", }, ], }, ], // react "react/prop-types": 0, // react-native "react-native/no-raw-text": 0, // reactotron "reactotron/no-tron-in-production": "error", // eslint-config-standard overrides "comma-dangle": 0, "no-global-assign": 0, "quotes": 0, "space-before-function-paren": 0, // eslint-import "import/order": [ "error", { "alphabetize": { order: "asc", caseInsensitive: true, }, "newlines-between": "always", "groups": [["builtin", "external"], "internal", "unknown", ["parent", "sibling"], "index"], "distinctGroup": false, "pathGroups": [ { pattern: "react", group: "external", position: "before", }, { pattern: "react-native", group: "external", position: "before", }, { pattern: "expo{,-*}", group: "external", position: "before", }, { pattern: "@/**", group: "unknown", position: "after", }, ], "pathGroupsExcludedImportTypes": ["react", "react-native", "expo", "expo-*"], }, ], "import/newline-after-import": 1, }, } ================================================ FILE: boilerplate/.gitignore ================================================ # OSX # .DS_Store # Xcode # build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata *.xccheckout *.moved-aside DerivedData *.hmap *.ipa *.xcuserstate ios/.xcode.env.local # Android/IntelliJ # build/ .idea .gradle local.properties *.iml *.hprof .cxx/ # node.js # node_modules/ npm-debug.log # BUCK buck-out/ \.buckd/ *.keystore !debug.keystore # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://docs.fastlane.tools/best-practices/source-control/ **/fastlane/report.xml **/fastlane/Preview.html **/fastlane/screenshots **/fastlane/test_output # Bundle artifact *.jsbundle # Ignite-specific items below # You can safely replace everything above this comment with whatever is # in the default .gitignore generated by React-Native CLI # VS Code .vscode # Expo .expo/* bin/Exponent.app /android /ios expo-env.d.ts ## Secrets npm-debug.* *.jks *.p8 *.p12 *.key *.mobileprovision *.orig.* web-build/ # Configurations !env.js /coverage # Dependency Graph Visualizations app-dependency-graph.svg app-dependency-graph.png ================================================ FILE: boilerplate/.maestro/flows/FavoritePodcast.yaml ================================================ # flow: run the login flow and then navigate to the demo podcast list screen, favorite a podcast, and then switch the list to only be favorites. appId: ${MAESTRO_APP_ID} env: FAVORITES_TEXT: "Switch on to only show favorites" # en.demoPodcastListScreen.accessibility.switch onFlowStart: - runFlow: ../shared/_OnFlowStart.yaml --- - runFlow: ../shared/_Login.yaml - tapOn: "Podcast" - assertVisible: "React Native Radio episodes" - tapOn: text: ${FAVORITES_TEXT} - assertVisible: "This looks a bit empty" - tapOn: text: ${FAVORITES_TEXT} # https://maestro.mobile.dev/troubleshooting/known-issues#android-accidental-double-tap retryTapIfNoChange: false - repeat: times: 2 commands: - scroll - copyTextFrom: text: "RNR .*" # assumes all podcast titles start with RNR index: 2 # grab the third one, others might not be fully visible - longPressOn: ${maestro.copiedText} - scrollUntilVisible: element: text: ${FAVORITES_TEXT} direction: UP timeout: 50000 speed: 90 visibilityPercentage: 100 - tapOn: text: ${FAVORITES_TEXT} - assertVisible: ${maestro.copiedText} # @demo remove-file ================================================ FILE: boilerplate/.maestro/flows/Login.yaml ================================================ #flow: Login #intent: # Open up our app and use the default credentials to login # and navigate to the demo screen appId: ${MAESTRO_APP_ID} # the app id of the app we want to test # You can find the appId of an Ignite app in the `app.json` file # as the "package" under the "android" section and "bundleIdentifier" under the "ios" section onFlowStart: - runFlow: ../shared/_OnFlowStart.yaml --- - runFlow: ../shared/_Login.yaml # @demo remove-file ================================================ FILE: boilerplate/.maestro/shared/_Login.yaml ================================================ #flow: Shared _Login #intent: shared login flow for any flow that needs to start with a log in. appId: ${MAESTRO_APP_ID} --- - assertVisible: "Log In" - tapOn: text: "Tap to Log in!" - assertVisible: "Your app, almost ready for launch!" - tapOn: text: "Let's go!" - assertVisible: "Components to jump start your project!" # @demo remove-file ================================================ FILE: boilerplate/.maestro/shared/_OnFlowStart.yaml ================================================ # flow: Shared _OnFlowStart #intent: # launch the app with a completely clear state, wait for animations to settle, # and click through the expo dev screens if needed. # These conditionals slow the app launch down a little but are necessary because the expo # dev server and launch screen are only shown when the new architecture is turned off in expo 53. # So we check to see if we need to connect to the metro server... that loads the app and then we # check if the dev menu is showing and dismiss it if necessary. # Then the app is then launched and ready for the maestro tests to run. # # This flow should be included in every maestro test header as `onFlowStart` to ensure expo screens # are bypassed if necessary. Example: # # appId: ${MAESTRO_APP_ID} # onFlowStart: # - runFlow: ../shared/_OnFlowStart.yaml # --- # [your maestro flow] # appId: ${MAESTRO_APP_ID} --- # launch the app with a clean slate - launchApp: clearState: true clearKeychain: true stopApp: true - waitForAnimationToEnd # conditionally run the dev client flow if the words "Development servers" is present. # this uses the default maestro timeout and moves on if it doesn't see the text. - runFlow: when: visible: 'Development servers' commands: # this regex allows for different hosts and ports - tapOn: "http://.*:.*" - waitForAnimationToEnd - tapOn: "Close" # dismiss the bottom sheet ================================================ FILE: boilerplate/.npmrc ================================================ # I wish we could put this in package.json instead of here strict-peer-dependencies=false ================================================ FILE: boilerplate/.prettierignore ================================================ node_modules ios android .expo .vscode ignite/ignite.json package.json .eslintignore *.ejs ================================================ FILE: boilerplate/.prettierrc ================================================ { "printWidth": 100, "semi": false, "singleQuote": false, "trailingComma": "all", "quoteProps": "consistent" } ================================================ FILE: boilerplate/README.md ================================================ # Welcome to your new ignited app! > The latest and greatest boilerplate for Infinite Red opinions This is the boilerplate that [Infinite Red](https://infinite.red) uses as a way to test bleeding-edge changes to our React Native stack. - [Quick start documentation](https://github.com/infinitered/ignite/blob/master/docs/boilerplate/Boilerplate.md) - [Full documentation](https://github.com/infinitered/ignite/blob/master/docs/README.md) ## Getting Started ```bash pnpm run pnpm run start ``` To 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: ```bash pnpm run build:ios:sim # build for ios simulator pnpm run build:ios:device # build for ios device pnpm run build:ios:prod # build for ios device ``` ### `./assets` This 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`: ```tree assets ├── icons └── images ``` **icons** This 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. Ignite 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). **images** This 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. Another 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). How to use your `icon` or `image` assets: ```typescript import { Image } from 'react-native'; const MyComponent = () => { return ( ); }; ``` ## Running Maestro end-to-end tests Follow our [Maestro Setup](https://ignitecookbook.com/docs/recipes/MaestroSetup) recipe. ## Next Steps ### Ignite Cookbook [Ignite Cookbook](https://ignitecookbook.com/) is an easy way for developers to browse and share code snippets (or “recipes”) that actually work. ### Upgrade Ignite boilerplate Read our [Upgrade Guide](https://ignitecookbook.com/docs/recipes/UpdatingIgnite) to learn how to upgrade your Ignite project. ## Community ⭐️ 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). 💬 Join us on [Slack](https://join.slack.com/t/infiniteredcommunity/shared_invite/zt-1f137np4h-zPTq_CbaRFUOR_glUFs2UA) to discuss. 📰 Make our Editor-in-chief happy by [reading the React Native Newsletter](https://reactnativenewsletter.com/). ================================================ FILE: boilerplate/app/app.tsx ================================================ /* eslint-disable import/first */ /** * Welcome to the main entry point of the app. In this file, we'll * be kicking off our app. * * Most 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 here. * * The app navigation resides in ./app/navigators, so head over there * if you're interested in adding screens and navigators. */ if (__DEV__) { // Load Reactotron in development only. // Note that you must be using metro's `inlineRequires` for this to work. // If you turn it off in metro.config.js, you'll have to manually import it. require("./devtools/ReactotronConfig.ts") } import "./utils/gestureHandler" import { useEffect, useState } from "react" import { useFonts } from "expo-font" import * as Linking from "expo-linking" import { KeyboardProvider } from "react-native-keyboard-controller" import { initialWindowMetrics, SafeAreaProvider } from "react-native-safe-area-context" import { AuthProvider } from "./context/AuthContext" // @demo remove-current-line import { initI18n } from "./i18n" import { AppNavigator } from "./navigators/AppNavigator" import { useNavigationPersistence } from "./navigators/navigationUtilities" import { ThemeProvider } from "./theme/context" import { customFontsToLoad } from "./theme/typography" import { loadDateFnsLocale } from "./utils/formatDate" import * as storage from "./utils/storage" export const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE" // Web linking configuration const prefix = Linking.createURL("/") const config = { screens: { Login: { path: "", }, Welcome: "welcome", Demo: { screens: { DemoShowroom: { path: "showroom/:queryIndex?/:itemIndex?", }, DemoDebug: "debug", DemoPodcastList: "podcast", DemoCommunity: "community", }, }, }, } /** * This is the root component of our app. * @param {AppProps} props - The props for the `App` component. * @returns {JSX.Element} The rendered `App` component. */ export function App() { const { initialNavigationState, onNavigationStateChange, isRestored: isNavigationStateRestored, } = useNavigationPersistence(storage, NAVIGATION_PERSISTENCE_KEY) const [areFontsLoaded, fontLoadError] = useFonts(customFontsToLoad) const [isI18nInitialized, setIsI18nInitialized] = useState(false) useEffect(() => { initI18n() .then(() => setIsI18nInitialized(true)) .then(() => loadDateFnsLocale()) }, []) // Before we show the app, we have to wait for our state to be ready. // In the meantime, don't render anything. This will be the background // color set in native by rootView's background color. // In iOS: application:didFinishLaunchingWithOptions: // In Android: https://stackoverflow.com/a/45838109/204044 // You can replace with your own loading component if you wish. if (!isNavigationStateRestored || !isI18nInitialized || (!areFontsLoaded && !fontLoadError)) { return null } const linking = { prefixes: [prefix], config, } // otherwise, we're ready to render the app return ( {/* @demo remove-block-start */} {/* @demo remove-block-end */} {/* @demo remove-block-start */} {/* @demo remove-block-end */} ) } ================================================ FILE: boilerplate/app/components/AutoImage.tsx ================================================ import { useLayoutEffect, useState } from "react" import { Image, ImageProps, ImageURISource, Platform, PixelRatio } from "react-native" export interface AutoImageProps extends ImageProps { /** * How wide should the image be? */ maxWidth?: number /** * How tall should the image be? */ maxHeight?: number headers?: { [key: string]: string } } /** * A hook that will return the scaled dimensions of an image based on the * provided dimensions' aspect ratio. If no desired dimensions are provided, * it will return the original dimensions of the remote image. * * 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. * @param {number} remoteUri - The URI of the remote image. * @param {number} dimensions - The desired dimensions of the image. If not provided, the original dimensions will be returned. * @returns {[number, number]} - The scaled dimensions of the image. */ export function useAutoImage( remoteUri: string, headers?: { [key: string]: string }, dimensions?: [maxWidth?: number, maxHeight?: number], ): [width: number, height: number] { const [[remoteWidth, remoteHeight], setRemoteImageDimensions] = useState([0, 0]) const remoteAspectRatio = remoteWidth / remoteHeight const [maxWidth, maxHeight] = dimensions ?? [] useLayoutEffect(() => { if (!remoteUri) return if (!headers) { Image.getSize(remoteUri, (w, h) => setRemoteImageDimensions([w, h])) } else { Image.getSizeWithHeaders(remoteUri, headers, (w, h) => setRemoteImageDimensions([w, h])) } }, [remoteUri, headers]) if (Number.isNaN(remoteAspectRatio)) return [0, 0] if (maxWidth && maxHeight) { const aspectRatio = Math.min(maxWidth / remoteWidth, maxHeight / remoteHeight) return [ PixelRatio.roundToNearestPixel(remoteWidth * aspectRatio), PixelRatio.roundToNearestPixel(remoteHeight * aspectRatio), ] } else if (maxWidth) { return [maxWidth, PixelRatio.roundToNearestPixel(maxWidth / remoteAspectRatio)] } else if (maxHeight) { return [PixelRatio.roundToNearestPixel(maxHeight * remoteAspectRatio), maxHeight] } else { return [remoteWidth, remoteHeight] } } /** * An Image component that automatically sizes a remote or data-uri image. * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/AutoImage/} * @param {AutoImageProps} props - The props for the `AutoImage` component. * @returns {JSX.Element} The rendered `AutoImage` component. */ export function AutoImage(props: AutoImageProps) { const { maxWidth, maxHeight, ...ImageProps } = props const source = props.source as ImageURISource const headers = source?.headers const [width, height] = useAutoImage( Platform.select({ web: (source?.uri as string) ?? (source as string), default: source?.uri as string, }), headers, [maxWidth, maxHeight], ) return } ================================================ FILE: boilerplate/app/components/Button.tsx ================================================ import { ComponentType } from "react" import { Pressable, PressableProps, PressableStateCallbackType, StyleProp, TextStyle, ViewStyle, } from "react-native" import { useAppTheme } from "@/theme/context" import { $styles } from "@/theme/styles" import type { ThemedStyle, ThemedStyleArray } from "@/theme/types" import { Text, TextProps } from "./Text" type Presets = "default" | "filled" | "reversed" export interface ButtonAccessoryProps { style: StyleProp pressableState: PressableStateCallbackType disabled?: boolean } export interface ButtonProps extends PressableProps { /** * Text which is looked up via i18n. */ tx?: TextProps["tx"] /** * The text to display if not using `tx` or nested components. */ text?: TextProps["text"] /** * Optional options to pass to i18n. Useful for interpolation * as well as explicitly setting locale or translation fallbacks. */ txOptions?: TextProps["txOptions"] /** * An optional style override useful for padding & margin. */ style?: StyleProp /** * An optional style override for the "pressed" state. */ pressedStyle?: StyleProp /** * An optional style override for the button text. */ textStyle?: StyleProp /** * An optional style override for the button text when in the "pressed" state. */ pressedTextStyle?: StyleProp /** * An optional style override for the button text when in the "disabled" state. */ disabledTextStyle?: StyleProp /** * One of the different types of button presets. */ preset?: Presets /** * An optional component to render on the right side of the text. * Example: `RightAccessory={(props) => }` */ RightAccessory?: ComponentType /** * An optional component to render on the left side of the text. * Example: `LeftAccessory={(props) => }` */ LeftAccessory?: ComponentType /** * Children components. */ children?: React.ReactNode /** * disabled prop, accessed directly for declarative styling reasons. * https://reactnative.dev/docs/pressable#disabled */ disabled?: boolean /** * An optional style override for the disabled state */ disabledStyle?: StyleProp } /** * A component that allows users to take actions and make choices. * Wraps the Text component with a Pressable component. * @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Button/} * @param {ButtonProps} props - The props for the `Button` component. * @returns {JSX.Element} The rendered `Button` component. * @example * } /> ) } // #region Styles const $listContentContainer: ThemedStyle = ({ spacing }) => ({ paddingHorizontal: spacing.lg, paddingTop: spacing.lg + spacing.xl, paddingBottom: spacing.lg, }) const $heading: ThemedStyle = ({ spacing }) => ({ marginBottom: spacing.md, }) const $item: ThemedStyle = ({ colors, spacing }) => ({ padding: spacing.md, marginTop: spacing.md, minHeight: 120, backgroundColor: colors.palette.neutral100, }) const $itemThumbnail: ThemedStyle = ({ spacing }) => ({ marginTop: spacing.sm, borderRadius: 50, alignSelf: "flex-start", }) const $toggle: ThemedStyle = ({ spacing }) => ({ marginTop: spacing.md, }) const $labelStyle: TextStyle = { textAlign: "left", } const $iconContainer: ThemedStyle = ({ spacing }) => ({ height: ICON_SIZE, width: ICON_SIZE, marginEnd: spacing.sm, }) const $metadata: ThemedStyle = ({ colors, spacing }) => ({ color: colors.textDim, marginTop: spacing.xs, }) const $metadataText: ThemedStyle = ({ colors, spacing }) => ({ color: colors.textDim, marginEnd: spacing.md, marginBottom: spacing.xs, }) const $favoriteButton: ThemedStyle = ({ colors, spacing }) => ({ borderRadius: 17, marginTop: spacing.md, justifyContent: "flex-start", backgroundColor: colors.palette.neutral300, borderColor: colors.palette.neutral300, paddingHorizontal: spacing.md, paddingTop: spacing.xxxs, paddingBottom: 0, minHeight: 32, alignSelf: "flex-start", }) const $unFavoriteButton: ThemedStyle = ({ colors }) => ({ borderColor: colors.palette.primary100, backgroundColor: colors.palette.primary100, }) const $emptyState: ThemedStyle = ({ spacing }) => ({ marginTop: spacing.xxl, }) const $emptyStateImage: ImageStyle = { transform: [{ scaleX: isRTL ? -1 : 1 }], } // #endregion // @demo remove-file ================================================ FILE: boilerplate/app/screens/DemoShowroomScreen/DemoDivider.tsx ================================================ /* eslint-disable react-native/no-inline-styles */ import { StyleProp, View, ViewStyle } from "react-native" import type { ThemedStyle } from "@/theme/types" import { useAppTheme } from "@/theme/context" interface DemoDividerProps { type?: "vertical" | "horizontal" size?: number style?: StyleProp line?: boolean } /** * @param {DemoDividerProps} props - The props for the `DemoDivider` component. * @returns {JSX.Element} The rendered `DemoDivider` component. */ export function DemoDivider(props: DemoDividerProps) { const { type = "horizontal", size = 10, line = false, style: $styleOverride } = props const { themed } = useAppTheme() return ( {line && ( )} ) } const $divider: ViewStyle = { flexGrow: 0, flexShrink: 0, } const $line: ThemedStyle = ({ colors }) => ({ backgroundColor: colors.border, position: "absolute", left: "50%", top: "50%", }) // @demo remove-file ================================================ FILE: boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx ================================================ import { FC, ReactElement, useCallback, useEffect, useRef, useState } from "react" import { FlatList, Image, ImageStyle, Platform, SectionList, TextStyle, View, ViewStyle, } from "react-native" import { Link, RouteProp, useRoute } from "@react-navigation/native" import { Drawer } from "react-native-drawer-layout" import { ListItem } from "@/components/ListItem" import { Screen } from "@/components/Screen" import { Text } from "@/components/Text" import { TxKeyPath, isRTL } from "@/i18n" import { translate } from "@/i18n/translate" import { DemoTabParamList, DemoTabScreenProps } from "@/navigators/navigationTypes" import type { ThemedStyle } from "@/theme/types" import { useAppTheme } from "@/theme/context" import { $styles } from "@/theme/styles" import { useSafeAreaInsetsStyle } from "@/utils/useSafeAreaInsetsStyle" import * as Demos from "./demos" import { DrawerIconButton } from "./DrawerIconButton" import SectionListWithKeyboardAwareScrollView from "./SectionListWithKeyboardAwareScrollView" const logo = require("@assets/images/logo.png") interface DemoListItem { item: { name: string; useCases: string[] } sectionIndex: number handleScroll?: (sectionIndex: number, itemIndex?: number) => void } const slugify = (str: string) => str .toLowerCase() .trim() .replace(/[^\w\s-]/g, "") .replace(/[\s_-]+/g, "-") .replace(/^-+|-+$/g, "") /** * Type-safe utility to check if an unknown object has a valid string property. * This is particularly useful in React 19 where props are typed as unknown by default. * The function safely narrows down the type by checking both property existence and type. * @param props - The unknown props to check. * @param propName - The name of the property to check. * @returns Whether the property is a valid string. */ function hasValidStringProp(props: unknown, propName: string): boolean { return ( props !== null && typeof props === "object" && propName in props && typeof (props as Record)[propName] === "string" ) } const WebListItem: FC = ({ item, sectionIndex }) => { const sectionSlug = item.name.toLowerCase() const { themed } = useAppTheme() return ( {item.name} {item.useCases.map((u) => { const itemSlug = slugify(u) return ( {u} ) })} ) } const NativeListItem: FC = ({ item, sectionIndex, handleScroll }) => { const { themed } = useAppTheme() return ( handleScroll?.(sectionIndex)} preset="bold" style={themed($menuContainer)} > {item.name} {item.useCases.map((u, index) => ( handleScroll?.(sectionIndex, index)} text={u} rightIcon={isRTL ? "caretLeft" : "caretRight"} /> ))} ) } const ShowroomListItem = Platform.select({ web: WebListItem, default: NativeListItem }) const isAndroid = Platform.OS === "android" export const DemoShowroomScreen: FC> = function DemoShowroomScreen(_props) { const [open, setOpen] = useState(false) const timeout = useRef>(null) const listRef = useRef(null) const menuRef = useRef>(null) const route = useRoute>() const params = route.params const { themed, theme } = useAppTheme() const toggleDrawer = useCallback(() => { if (!open) { setOpen(true) } else { setOpen(false) } }, [open]) const handleScroll = useCallback((sectionIndex: number, itemIndex = 0) => { try { listRef.current?.scrollToLocation({ animated: true, itemIndex, sectionIndex, viewPosition: 0.25, }) } catch (e) { console.error(e) } }, []) // handle Web links useEffect(() => { if (params !== undefined && Object.keys(params).length > 0) { const demoValues = Object.values(Demos) const findSectionIndex = demoValues.findIndex( (x) => x.name.toLowerCase() === params.queryIndex, ) let findItemIndex = 0 if (params.itemIndex) { try { findItemIndex = demoValues[findSectionIndex].data({ themed, theme }).findIndex((u) => { if (hasValidStringProp(u.props, "name")) { return ( slugify(translate((u.props as { name: TxKeyPath }).name)) === params.itemIndex ) } return false }) } catch (err) { console.error(err) } } handleScroll(findSectionIndex, findItemIndex) } }, [handleScroll, params, theme, themed]) const scrollToIndexFailed = (info: { index: number highestMeasuredFrameIndex: number averageItemLength: number }) => { listRef.current?.getScrollResponder()?.scrollToEnd() timeout.current = setTimeout( () => listRef.current?.scrollToLocation({ animated: true, itemIndex: info.index, sectionIndex: 0, }), 50, ) } useEffect(() => { return () => { if (timeout.current) { clearTimeout(timeout.current) } } }, []) const $drawerInsets = useSafeAreaInsetsStyle(["top"]) return ( setOpen(true)} onClose={() => setOpen(false)} drawerType="back" drawerPosition={isRTL ? "right" : "left"} renderDrawerContent={() => ( ref={menuRef} contentContainerStyle={themed($listContentContainer)} data={Object.values(Demos).map((d) => ({ name: d.name, useCases: d.data({ theme, themed }).map((u) => { if (hasValidStringProp(u.props, "name")) { return translate((u.props as { name: TxKeyPath }).name) } return "" }), }))} keyExtractor={(item) => item.name} renderItem={({ item, index: sectionIndex }) => ( )} /> )} > ({ name: d.name, description: d.description, data: [d.data({ theme, themed })], }))} renderItem={({ item, index: sectionIndex }) => ( {item.map((demo: ReactElement, demoIndex: number) => ( {demo} ))} )} renderSectionFooter={() => } ListHeaderComponent={ } onScrollToIndexFailed={scrollToIndexFailed} renderSectionHeader={({ section }) => { return ( {section.name} {translate(section.description)} ) }} /> ) } const $drawer: ThemedStyle = ({ colors }) => ({ backgroundColor: colors.background, flex: 1, }) const $listContentContainer: ThemedStyle = ({ spacing }) => ({ paddingHorizontal: spacing.lg, }) const $sectionListContentContainer: ThemedStyle = ({ spacing }) => ({ paddingHorizontal: spacing.lg, }) const $heading: ThemedStyle = ({ spacing }) => ({ marginBottom: spacing.xxxl, }) const $logoImage: ImageStyle = { height: 42, width: 77, } const $logoContainer: ThemedStyle = ({ spacing }) => ({ alignSelf: "flex-start", justifyContent: "center", height: 56, paddingHorizontal: spacing.lg, }) const $menuContainer: ThemedStyle = ({ spacing }) => ({ paddingBottom: spacing.xs, paddingTop: spacing.lg, }) const $demoItemName: ThemedStyle = ({ spacing }) => ({ fontSize: 24, marginBottom: spacing.md, }) const $demoItemDescription: ThemedStyle = ({ spacing }) => ({ marginBottom: spacing.xxl, }) const $demoUseCasesSpacer: ThemedStyle = ({ spacing }) => ({ paddingBottom: spacing.xxl, }) // @demo remove-file ================================================ FILE: boilerplate/app/screens/DemoShowroomScreen/DemoUseCase.tsx ================================================ import { ReactNode } from "react" import { TextStyle, View, ViewStyle } from "react-native" import { Text } from "@/components/Text" import type { TxKeyPath } from "@/i18n" import { translate } from "@/i18n/translate" import type { ThemedStyle } from "@/theme/types" import { useAppTheme } from "@/theme/context" import { $styles } from "@/theme/styles" interface DemoUseCaseProps { name: TxKeyPath description?: TxKeyPath layout?: "column" | "row" itemStyle?: ViewStyle children: ReactNode } /** * @param {DemoUseCaseProps} props - The props for the `DemoUseCase` component. * @returns {JSX.Element} The rendered `DemoUseCase` component. */ export function DemoUseCase(props: DemoUseCaseProps) { const { name, description, children, layout = "column", itemStyle = {} } = props const { themed } = useAppTheme() return ( {translate(name)} {description && {translate(description)}} {children} ) } const $description: ThemedStyle = ({ spacing }) => ({ marginTop: spacing.md, }) const $item: ThemedStyle = ({ colors, spacing }) => ({ backgroundColor: colors.palette.neutral100, borderRadius: 8, padding: spacing.lg, marginVertical: spacing.md, }) const $name: ThemedStyle = ({ typography }) => ({ fontFamily: typography.primary.bold, }) // @demo remove-file ================================================ FILE: boilerplate/app/screens/DemoShowroomScreen/DrawerIconButton.tsx ================================================ import { Pressable, PressableProps, ViewStyle, Platform } from "react-native" import { useDrawerProgress } from "react-native-drawer-layout" import Animated, { interpolate, interpolateColor, useAnimatedStyle } from "react-native-reanimated" import { isRTL } from "@/i18n" import { useAppTheme } from "@/theme/context" interface DrawerIconButtonProps extends PressableProps {} const AnimatedPressable = Animated.createAnimatedComponent(Pressable) /** * @param {DrawerIconButtonProps} props - The props for the `DrawerIconButton` component. * @returns {JSX.Element} The rendered `DrawerIconButton` component. */ export function DrawerIconButton(props: DrawerIconButtonProps) { const { ...PressableProps } = props const progress = useDrawerProgress() const isWeb = Platform.OS === "web" const { theme: { colors }, themed, } = useAppTheme() const animatedContainerStyles = useAnimatedStyle(() => { const translateX = interpolate(progress.value, [0, 1], [0, isRTL ? 60 : -60]) return { transform: [{ translateX }], } }) const animatedTopBarStyles = useAnimatedStyle(() => { const backgroundColor = interpolateColor(progress.value, [0, 1], [colors.text, colors.tint]) const marginStart = interpolate(progress.value, [0, 1], [0, -11.5]) const rotate = interpolate(progress.value, [0, 1], [0, isRTL ? 45 : -45]) const marginBottom = interpolate(progress.value, [0, 1], [0, -2]) const width = interpolate(progress.value, [0, 1], [18, 12]) const marginHorizontal = isWeb && isRTL ? { marginRight: marginStart } : { marginLeft: marginStart, } return { ...marginHorizontal, backgroundColor, marginBottom, width, transform: [{ rotate: `${rotate}deg` }], } }) const animatedMiddleBarStyles = useAnimatedStyle(() => { const backgroundColor = interpolateColor(progress.value, [0, 1], [colors.text, colors.tint]) const width = interpolate(progress.value, [0, 1], [18, 16]) return { backgroundColor, width, } }) const animatedBottomBarStyles = useAnimatedStyle(() => { const marginTop = interpolate(progress.value, [0, 1], [4, 2]) const backgroundColor = interpolateColor(progress.value, [0, 1], [colors.text, colors.tint]) const marginStart = interpolate(progress.value, [0, 1], [0, -11.5]) const rotate = interpolate(progress.value, [0, 1], [0, isRTL ? -45 : 45]) const width = interpolate(progress.value, [0, 1], [18, 12]) const marginHorizontal = isWeb && isRTL ? { marginRight: marginStart } : { marginLeft: marginStart, } return { ...marginHorizontal, backgroundColor, width, marginTop, transform: [{ rotate: `${rotate}deg` }], } }) return ( ) } const barHeight = 2 const $container: ViewStyle = { alignItems: "center", height: 56, justifyContent: "center", width: 56, } const $topBar: ViewStyle = { height: barHeight, } const $middleBar: ViewStyle = { height: barHeight, marginTop: 4, } const $bottomBar: ViewStyle = { height: barHeight, } // @demo remove-file ================================================ FILE: boilerplate/app/screens/DemoShowroomScreen/SectionListWithKeyboardAwareScrollView.tsx ================================================ import { forwardRef, ReactElement, ReactNode, useCallback } from "react" import { ScrollViewProps, SectionList, SectionListProps } from "react-native" import { KeyboardAwareScrollView } from "react-native-keyboard-controller" import { DEFAULT_BOTTOM_OFFSET } from "@/components/Screen" type SectionType = { name: string description: string data: ItemType[] } type SectionListWithKeyboardAwareScrollViewProps = SectionListProps & { /* Optional function to pass a custom scroll component */ renderScrollComponent?: (props: ScrollViewProps) => ReactNode /* 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 */ bottomOffset?: number /* The sections to be rendered in the list */ sections: SectionType[] /* Function to render the header for each section */ renderSectionHeader: ({ section }: { section: SectionType }) => React.ReactNode } function SectionListWithKeyboardAwareScrollView( { renderScrollComponent, bottomOffset = DEFAULT_BOTTOM_OFFSET, contentContainerStyle, ...props }: SectionListWithKeyboardAwareScrollViewProps, ref: React.Ref>, ): ReactElement { const defaultRenderScrollComponent = useCallback( (props: ScrollViewProps) => ( ), [contentContainerStyle, bottomOffset], ) return ( ) } export default forwardRef(SectionListWithKeyboardAwareScrollView) as ( props: SectionListWithKeyboardAwareScrollViewProps & { ref?: React.Ref> }, ) => ReactElement // @demo remove-file ================================================ FILE: boilerplate/app/screens/DemoShowroomScreen/demos/DemoAutoImage.tsx ================================================ /* eslint-disable react/jsx-key, react-native/no-inline-styles */ import { Image, ImageStyle, TextStyle, View, ViewStyle } from "react-native" import { AutoImage } from "@/components/AutoImage" import { Text } from "@/components/Text" import { translate } from "@/i18n/translate" import type { ThemedStyle } from "@/theme/types" import { $styles } from "@/theme/styles" import { DemoDivider } from "../DemoDivider" import { Demo } from "./types" import { DemoUseCase } from "../DemoUseCase" const $imageContainer: ViewStyle = { alignItems: "center", } const $aspectRatioDescription: ThemedStyle = ({ spacing }) => ({ textAlign: "center", width: "100%", marginTop: spacing.xs, }) const $aspectRatioWidthExampleContainer: ViewStyle = { justifyContent: "space-between", } const $aspectRatioHeightExampleContainer: ViewStyle = { alignItems: "stretch", justifyContent: "space-between", height: 130, } const $aspectRatioBox: ThemedStyle = (theme) => ({ borderRadius: 4, borderWidth: 3, borderColor: theme.colors.palette.secondary300, backgroundColor: theme.colors.palette.neutral800, }) export const DemoAutoImage: Demo = { name: "AutoImage", description: "demoAutoImage:description", data: ({ theme, themed }) => [ , , {translate("demoAutoImage:useCase.scaledToFitDimensions.heightAuto")} {translate("demoAutoImage:useCase.scaledToFitDimensions.widthAuto")} {translate("demoAutoImage:useCase.scaledToFitDimensions.bothManual")} , ], } // @demo remove-file ================================================ FILE: boilerplate/app/screens/DemoShowroomScreen/demos/DemoButton.tsx ================================================ /* eslint-disable react/jsx-key */ import { ImageStyle, TextStyle, View, ViewStyle } from "react-native" import { Button } from "@/components/Button" import { Icon } from "@/components/Icon" import { Text } from "@/components/Text" import { translate } from "@/i18n/translate" import type { ThemedStyle } from "@/theme/types" import { DemoDivider } from "../DemoDivider" import { Demo } from "./types" import { DemoUseCase } from "../DemoUseCase" const $iconStyle: ImageStyle = { width: 30, height: 30 } const $customButtonStyle: ThemedStyle = ({ colors }) => ({ backgroundColor: colors.error, height: 100, }) const $customButtonPressedStyle: ThemedStyle = ({ colors }) => ({ backgroundColor: colors.error, }) const $customButtonTextStyle: ThemedStyle = ({ colors, typography }) => ({ color: colors.error, fontFamily: typography.primary.bold, textDecorationLine: "underline", textDecorationColor: colors.error, }) const $customButtonPressedTextStyle: ThemedStyle = ({ colors }) => ({ color: colors.palette.neutral100, }) const $customButtonRightAccessoryStyle: ThemedStyle = ({ colors }) => ({ width: "53%", height: "200%", backgroundColor: colors.error, position: "absolute", top: 0, right: 0, }) const $customButtonPressedRightAccessoryStyle: ThemedStyle = ({ colors }) => ({ tintColor: colors.palette.neutral100, }) const $disabledOpacity: ViewStyle = { opacity: 0.5 } const $disabledButtonTextStyle: ThemedStyle = ({ colors }) => ({ color: colors.palette.neutral100, textDecorationColor: colors.palette.neutral100, }) export const DemoButton: Demo = { name: "Button", description: "demoButton:description", data: ({ themed }) => [ , , , , ], } // @demo remove-file ================================================ FILE: boilerplate/app/screens/DemoShowroomScreen/demos/DemoCard.tsx ================================================ /* eslint-disable react/jsx-key, react-native/no-inline-styles */ import { AutoImage } from "@/components/AutoImage" import { Button } from "@/components/Button" import { Card } from "@/components/Card" import { Icon } from "@/components/Icon" import { DemoDivider } from "../DemoDivider" import { Demo } from "./types" import { DemoUseCase } from "../DemoUseCase" export const DemoCard: Demo = { name: "Card", description: "demoCard:description", data: ({ theme }) => [ , , , } /> } ContentComponent={