Showing preview only (1,055K chars total). Download the full file or copy to clipboard to get everything.
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:<YOURGITHUBUSER>/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
<current version here>
$ 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
<!-- Add your PR description here -->
- Issues: <!-- e.g. "fixes #9999", "related: #9999", etc -->
## Screenshots
<!-- If not applicable, delete this whole section -->
<!-- If you’re submitting code changes related to a visible feature, please include before-and-after screenshots or videos. -->
<!-- If GH's auto attachment previews too large, trying resizing it with: <img src="filename-from-github-upload" width="300" /> -->
| Before | After |
| -------------------------------- | ------------------------------- |
| [Insert before screenshot/video] | [Insert after screenshot/video] |
## Checklist
<!-- if an item doesn't apply, just delete it -->
- [ ] `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
================================================
<p align="center"><img src="https://user-images.githubusercontent.com/1479215/206780298-2b98221d-9c57-4cd3-866a-cf85ec1ddd9e.jpg" alt="Ignite README Splash Image" /></p>
# Ignite - the battle-tested React Native boilerplate
<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>


[](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!
<table>
<tr>
<td width="50%">
<figure>
<a href="https://www.youtube.com/watch?v=KOSvDlFyg20">
<img src="https://img.youtube.com/vi/KOSvDlFyg20/sddefault.jpg" alt="Getting Started with Ignite" width="100%" /><br />
<figcaption><strong>Getting Started with Ignite</strong></figcaption>
</a>
</figure>
</td>
<td>
<figure>
<a href="https://www.youtube.com/watch?v=dNWkJOpD6YE&list=PLFHvL21g9bk0XOO9XK6d6S9w1jBU6Dz_U&index=16">
<img src="https://img.youtube.com/vi/dNWkJOpD6YE/sddefault.jpg" alt="Sweetening React Native Development With Ignite" width="100%" /><br />
<figcaption><strong>Ignite talk at Chain React 2024 - Robin Heinze</strong></figcaption>
</a>
</figure>
</td>
</tr>
</table>
## [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/<yourapp>.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 (
<Image source={require('assets/images/my_image.png')} />
);
};
```
## 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 (
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<KeyboardProvider>
{/* @demo remove-block-start */}
<AuthProvider>
{/* @demo remove-block-end */}
<ThemeProvider>
<AppNavigator
linking={linking}
initialState={initialNavigationState}
onStateChange={onNavigationStateChange}
/>
</ThemeProvider>
{/* @demo remove-block-start */}
</AuthProvider>
{/* @demo remove-block-end */}
</KeyboardProvider>
</SafeAreaProvider>
)
}
================================================
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 <Image {...ImageProps} style={[{ width, height }, props.style]} />
}
================================================
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<any>
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<ViewStyle>
/**
* An optional style override for the "pressed" state.
*/
pressedStyle?: StyleProp<ViewStyle>
/**
* An optional style override for the button text.
*/
textStyle?: StyleProp<TextStyle>
/**
* An optional style override for the button text when in the "pressed" state.
*/
pressedTextStyle?: StyleProp<TextStyle>
/**
* An optional style override for the button text when in the "disabled" state.
*/
disabledTextStyle?: StyleProp<TextStyle>
/**
* 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) => <View {...props} />}`
*/
RightAccessory?: ComponentType<ButtonAccessoryProps>
/**
* An optional component to render on the left side of the text.
* Example: `LeftAccessory={(props) => <View {...props} />}`
*/
LeftAccessory?: ComponentType<ButtonAccessoryProps>
/**
* 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<ViewStyle>
}
/**
* 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
* <Button
* tx="common:ok"
* style={styles.button}
* textStyle={styles.buttonText}
* onPress={handleButtonPress}
* />
*/
export function Button(props: ButtonProps) {
const {
tx,
text,
txOptions,
style: $viewStyleOverride,
pressedStyle: $pressedViewStyleOverride,
textStyle: $textStyleOverride,
pressedTextStyle: $pressedTextStyleOverride,
disabledTextStyle: $disabledTextStyleOverride,
children,
RightAccessory,
LeftAccessory,
disabled,
disabledStyle: $disabledViewStyleOverride,
...rest
} = props
const { themed } = useAppTheme()
const preset: Presets = props.preset ?? "default"
/**
* @param {PressableStateCallbackType} root0 - The root object containing the pressed state.
* @param {boolean} root0.pressed - The pressed state.
* @returns {StyleProp<ViewStyle>} The view style based on the pressed state.
*/
function $viewStyle({ pressed }: PressableStateCallbackType): StyleProp<ViewStyle> {
return [
themed($viewPresets[preset]),
$viewStyleOverride,
!!pressed && themed([$pressedViewPresets[preset], $pressedViewStyleOverride]),
!!disabled && $disabledViewStyleOverride,
]
}
/**
* @param {PressableStateCallbackType} root0 - The root object containing the pressed state.
* @param {boolean} root0.pressed - The pressed state.
* @returns {StyleProp<TextStyle>} The text style based on the pressed state.
*/
function $textStyle({ pressed }: PressableStateCallbackType): StyleProp<TextStyle> {
return [
themed($textPresets[preset]),
$textStyleOverride,
!!pressed && themed([$pressedTextPresets[preset], $pressedTextStyleOverride]),
!!disabled && $disabledTextStyleOverride,
]
}
return (
<Pressable
style={$viewStyle}
accessibilityRole="button"
accessibilityState={{ disabled: !!disabled }}
{...rest}
disabled={disabled}
>
{(state) => (
<>
{!!LeftAccessory && (
<LeftAccessory style={$leftAccessoryStyle} pressableState={state} disabled={disabled} />
)}
<Text tx={tx} text={text} txOptions={txOptions} style={$textStyle(state)}>
{children}
</Text>
{!!RightAccessory && (
<RightAccessory
style={$rightAccessoryStyle}
pressableState={state}
disabled={disabled}
/>
)}
</>
)}
</Pressable>
)
}
const $baseViewStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({
minHeight: 56,
borderRadius: 4,
justifyContent: "center",
alignItems: "center",
paddingVertical: spacing.sm,
paddingHorizontal: spacing.sm,
overflow: "hidden",
})
const $baseTextStyle: ThemedStyle<TextStyle> = ({ typography }) => ({
fontSize: 16,
lineHeight: 20,
fontFamily: typography.primary.medium,
textAlign: "center",
flexShrink: 1,
flexGrow: 0,
zIndex: 2,
})
const $rightAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({
marginStart: spacing.xs,
zIndex: 1,
})
const $leftAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({
marginEnd: spacing.xs,
zIndex: 1,
})
const $viewPresets: Record<Presets, ThemedStyleArray<ViewStyle>> = {
default: [
$styles.row,
$baseViewStyle,
({ colors }) => ({
borderWidth: 1,
borderColor: colors.palette.neutral400,
backgroundColor: colors.palette.neutral100,
}),
],
filled: [
$styles.row,
$baseViewStyle,
({ colors }) => ({ backgroundColor: colors.palette.neutral300 }),
],
reversed: [
$styles.row,
$baseViewStyle,
({ colors }) => ({ backgroundColor: colors.palette.neutral800 }),
],
}
const $textPresets: Record<Presets, ThemedStyleArray<TextStyle>> = {
default: [$baseTextStyle],
filled: [$baseTextStyle],
reversed: [$baseTextStyle, ({ colors }) => ({ color: colors.palette.neutral100 })],
}
const $pressedViewPresets: Record<Presets, ThemedStyle<ViewStyle>> = {
default: ({ colors }) => ({ backgroundColor: colors.palette.neutral200 }),
filled: ({ colors }) => ({ backgroundColor: colors.palette.neutral400 }),
reversed: ({ colors }) => ({ backgroundColor: colors.palette.neutral700 }),
}
const $pressedTextPresets: Record<Presets, ThemedStyle<TextStyle>> = {
default: () => ({ opacity: 0.9 }),
filled: () => ({ opacity: 0.9 }),
reversed: () => ({ opacity: 0.9 }),
}
================================================
FILE: boilerplate/app/components/Card.tsx
================================================
import { ComponentType, Fragment, ReactElement } from "react"
import {
StyleProp,
TextStyle,
TouchableOpacity,
TouchableOpacityProps,
View,
ViewProps,
ViewStyle,
} from "react-native"
import type { ThemedStyle, ThemedStyleArray } from "@/theme/types"
import { useAppTheme } from "@/theme/context"
import { $styles } from "@/theme/styles"
import { Text, TextProps } from "./Text"
type Presets = "default" | "reversed"
interface CardProps extends TouchableOpacityProps {
/**
* One of the different types of text presets.
*/
preset?: Presets
/**
* How the content should be aligned vertically. This is especially (but not exclusively) useful
* when the card is a fixed height but the content is dynamic.
*
* `top` (default) - aligns all content to the top.
* `center` - aligns all content to the center.
* `space-between` - spreads out the content evenly.
* `force-footer-bottom` - aligns all content to the top, but forces the footer to the bottom.
*/
verticalAlignment?: "top" | "center" | "space-between" | "force-footer-bottom"
/**
* Custom component added to the left of the card body.
*/
LeftComponent?: ReactElement
/**
* Custom component added to the right of the card body.
*/
RightComponent?: ReactElement
/**
* The heading text to display if not using `headingTx`.
*/
heading?: TextProps["text"]
/**
* Heading text which is looked up via i18n.
*/
headingTx?: TextProps["tx"]
/**
* Optional heading options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
headingTxOptions?: TextProps["txOptions"]
/**
* Style overrides for heading text.
*/
headingStyle?: StyleProp<TextStyle>
/**
* Pass any additional props directly to the heading Text component.
*/
HeadingTextProps?: TextProps
/**
* Custom heading component.
* Overrides all other `heading*` props.
*/
HeadingComponent?: ReactElement
/**
* The content text to display if not using `contentTx`.
*/
content?: TextProps["text"]
/**
* Content text which is looked up via i18n.
*/
contentTx?: TextProps["tx"]
/**
* Optional content options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
contentTxOptions?: TextProps["txOptions"]
/**
* Style overrides for content text.
*/
contentStyle?: StyleProp<TextStyle>
/**
* Pass any additional props directly to the content Text component.
*/
ContentTextProps?: TextProps
/**
* Custom content component.
* Overrides all other `content*` props.
*/
ContentComponent?: ReactElement
/**
* The footer text to display if not using `footerTx`.
*/
footer?: TextProps["text"]
/**
* Footer text which is looked up via i18n.
*/
footerTx?: TextProps["tx"]
/**
* Optional footer options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
footerTxOptions?: TextProps["txOptions"]
/**
* Style overrides for footer text.
*/
footerStyle?: StyleProp<TextStyle>
/**
* Pass any additional props directly to the footer Text component.
*/
FooterTextProps?: TextProps
/**
* Custom footer component.
* Overrides all other `footer*` props.
*/
FooterComponent?: ReactElement
}
/**
* 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.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Card/}
* @param {CardProps} props - The props for the `Card` component.
* @returns {JSX.Element} The rendered `Card` component.
*/
export function Card(props: CardProps) {
const {
content,
contentTx,
contentTxOptions,
footer,
footerTx,
footerTxOptions,
heading,
headingTx,
headingTxOptions,
ContentComponent,
HeadingComponent,
FooterComponent,
LeftComponent,
RightComponent,
verticalAlignment = "top",
style: $containerStyleOverride,
contentStyle: $contentStyleOverride,
headingStyle: $headingStyleOverride,
footerStyle: $footerStyleOverride,
ContentTextProps,
HeadingTextProps,
FooterTextProps,
...WrapperProps
} = props
const {
themed,
theme: { spacing },
} = useAppTheme()
const preset: Presets = props.preset ?? "default"
const isPressable = !!WrapperProps.onPress
const isHeadingPresent = !!(HeadingComponent || heading || headingTx)
const isContentPresent = !!(ContentComponent || content || contentTx)
const isFooterPresent = !!(FooterComponent || footer || footerTx)
const Wrapper = (isPressable ? TouchableOpacity : View) as ComponentType<
TouchableOpacityProps | ViewProps
>
const HeaderContentWrapper = verticalAlignment === "force-footer-bottom" ? View : Fragment
const $containerStyle: StyleProp<ViewStyle> = [
themed($containerPresets[preset]),
$containerStyleOverride,
]
const $headingStyle = [
themed($headingPresets[preset]),
(isFooterPresent || isContentPresent) && { marginBottom: spacing.xxxs },
$headingStyleOverride,
HeadingTextProps?.style,
]
const $contentStyle = [
themed($contentPresets[preset]),
isHeadingPresent && { marginTop: spacing.xxxs },
isFooterPresent && { marginBottom: spacing.xxxs },
$contentStyleOverride,
ContentTextProps?.style,
]
const $footerStyle = [
themed($footerPresets[preset]),
(isHeadingPresent || isContentPresent) && { marginTop: spacing.xxxs },
$footerStyleOverride,
FooterTextProps?.style,
]
const $alignmentWrapperStyle = [
$alignmentWrapper,
{ justifyContent: $alignmentWrapperFlexOptions[verticalAlignment] },
LeftComponent && { marginStart: spacing.md },
RightComponent && { marginEnd: spacing.md },
]
return (
<Wrapper
style={$containerStyle}
activeOpacity={0.8}
accessibilityRole={isPressable ? "button" : undefined}
{...WrapperProps}
>
{LeftComponent}
<View style={$alignmentWrapperStyle}>
<HeaderContentWrapper>
{HeadingComponent ||
(isHeadingPresent && (
<Text
weight="bold"
text={heading}
tx={headingTx}
txOptions={headingTxOptions}
{...HeadingTextProps}
style={$headingStyle}
/>
))}
{ContentComponent ||
(isContentPresent && (
<Text
weight="normal"
text={content}
tx={contentTx}
txOptions={contentTxOptions}
{...ContentTextProps}
style={$contentStyle}
/>
))}
</HeaderContentWrapper>
{FooterComponent ||
(isFooterPresent && (
<Text
weight="normal"
size="xs"
text={footer}
tx={footerTx}
txOptions={footerTxOptions}
{...FooterTextProps}
style={$footerStyle}
/>
))}
</View>
{RightComponent}
</Wrapper>
)
}
const $containerBase: ThemedStyle<ViewStyle> = (theme) => ({
borderRadius: theme.spacing.md,
padding: theme.spacing.xs,
borderWidth: 1,
shadowColor: theme.colors.palette.neutral800,
shadowOffset: { width: 0, height: 12 },
shadowOpacity: 0.08,
shadowRadius: 12.81,
elevation: 16,
minHeight: 96,
})
const $alignmentWrapper: ViewStyle = {
flex: 1,
alignSelf: "stretch",
}
const $alignmentWrapperFlexOptions = {
"top": "flex-start",
"center": "center",
"space-between": "space-between",
"force-footer-bottom": "space-between",
} as const
const $containerPresets: Record<Presets, ThemedStyleArray<ViewStyle>> = {
default: [
$styles.row,
$containerBase,
(theme) => ({
backgroundColor: theme.colors.palette.neutral100,
borderColor: theme.colors.palette.neutral300,
}),
],
reversed: [
$styles.row,
$containerBase,
(theme) => ({
backgroundColor: theme.colors.palette.neutral800,
borderColor: theme.colors.palette.neutral500,
}),
],
}
const $headingPresets: Record<Presets, ThemedStyleArray<TextStyle>> = {
default: [],
reversed: [(theme) => ({ color: theme.colors.palette.neutral100 })],
}
const $contentPresets: Record<Presets, ThemedStyleArray<TextStyle>> = {
default: [],
reversed: [(theme) => ({ color: theme.colors.palette.neutral100 })],
}
const $footerPresets: Record<Presets, ThemedStyleArray<TextStyle>> = {
default: [],
reversed: [(theme) => ({ color: theme.colors.palette.neutral100 })],
}
================================================
FILE: boilerplate/app/components/EmptyState.tsx
================================================
import { Image, ImageProps, ImageStyle, StyleProp, TextStyle, View, ViewStyle } from "react-native"
import { translate } from "@/i18n/translate"
import type { ThemedStyle } from "@/theme/types"
import { useAppTheme } from "@/theme/context"
import { Button, ButtonProps } from "./Button"
import { Text, TextProps } from "./Text"
const sadFace = require("@assets/images/sad-face.png")
interface EmptyStateProps {
/**
* An optional prop that specifies the text/image set to use for the empty state.
*/
preset?: "generic"
/**
* Style override for the container.
*/
style?: StyleProp<ViewStyle>
/**
* An Image source to be displayed above the heading.
*/
imageSource?: ImageProps["source"]
/**
* Style overrides for image.
*/
imageStyle?: StyleProp<ImageStyle>
/**
* Pass any additional props directly to the Image component.
*/
ImageProps?: Omit<ImageProps, "source">
/**
* The heading text to display if not using `headingTx`.
*/
heading?: TextProps["text"]
/**
* Heading text which is looked up via i18n.
*/
headingTx?: TextProps["tx"]
/**
* Optional heading options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
headingTxOptions?: TextProps["txOptions"]
/**
* Style overrides for heading text.
*/
headingStyle?: StyleProp<TextStyle>
/**
* Pass any additional props directly to the heading Text component.
*/
HeadingTextProps?: TextProps
/**
* The content text to display if not using `contentTx`.
*/
content?: TextProps["text"]
/**
* Content text which is looked up via i18n.
*/
contentTx?: TextProps["tx"]
/**
* Optional content options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
contentTxOptions?: TextProps["txOptions"]
/**
* Style overrides for content text.
*/
contentStyle?: StyleProp<TextStyle>
/**
* Pass any additional props directly to the content Text component.
*/
ContentTextProps?: TextProps
/**
* The button text to display if not using `buttonTx`.
*/
button?: TextProps["text"]
/**
* Button text which is looked up via i18n.
*/
buttonTx?: TextProps["tx"]
/**
* Optional button options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
buttonTxOptions?: TextProps["txOptions"]
/**
* Style overrides for button.
*/
buttonStyle?: ButtonProps["style"]
/**
* Style overrides for button text.
*/
buttonTextStyle?: ButtonProps["textStyle"]
/**
* Called when the button is pressed.
*/
buttonOnPress?: ButtonProps["onPress"]
/**
* Pass any additional props directly to the Button component.
*/
ButtonProps?: ButtonProps
}
interface EmptyStatePresetItem {
imageSource: ImageProps["source"]
heading: TextProps["text"]
content: TextProps["text"]
button: TextProps["text"]
}
/**
* A component to use when there is no data to display. It can be utilized to direct the user what to do next.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/EmptyState/}
* @param {EmptyStateProps} props - The props for the `EmptyState` component.
* @returns {JSX.Element} The rendered `EmptyState` component.
*/
export function EmptyState(props: EmptyStateProps) {
const {
theme,
themed,
theme: { spacing },
} = useAppTheme()
const EmptyStatePresets = {
generic: {
imageSource: sadFace,
heading: translate("emptyStateComponent:generic.heading"),
content: translate("emptyStateComponent:generic.content"),
button: translate("emptyStateComponent:generic.button"),
} as EmptyStatePresetItem,
} as const
const preset = EmptyStatePresets[props.preset ?? "generic"]
const {
button = preset.button,
buttonTx,
buttonOnPress,
buttonTxOptions,
content = preset.content,
contentTx,
contentTxOptions,
heading = preset.heading,
headingTx,
headingTxOptions,
imageSource = preset.imageSource,
style: $containerStyleOverride,
buttonStyle: $buttonStyleOverride,
buttonTextStyle: $buttonTextStyleOverride,
contentStyle: $contentStyleOverride,
headingStyle: $headingStyleOverride,
imageStyle: $imageStyleOverride,
ButtonProps,
ContentTextProps,
HeadingTextProps,
ImageProps,
} = props
const isImagePresent = !!imageSource
const isHeadingPresent = !!(heading || headingTx)
const isContentPresent = !!(content || contentTx)
const isButtonPresent = !!(button || buttonTx)
const $containerStyles = [$containerStyleOverride]
const $imageStyles = [
$image,
(isHeadingPresent || isContentPresent || isButtonPresent) && { marginBottom: spacing.xxxs },
$imageStyleOverride,
ImageProps?.style,
]
const $headingStyles = [
themed($heading),
isImagePresent && { marginTop: spacing.xxxs },
(isContentPresent || isButtonPresent) && { marginBottom: spacing.xxxs },
$headingStyleOverride,
HeadingTextProps?.style,
]
const $contentStyles = [
themed($content),
(isImagePresent || isHeadingPresent) && { marginTop: spacing.xxxs },
isButtonPresent && { marginBottom: spacing.xxxs },
$contentStyleOverride,
ContentTextProps?.style,
]
const $buttonStyles = [
(isImagePresent || isHeadingPresent || isContentPresent) && { marginTop: spacing.xl },
$buttonStyleOverride,
ButtonProps?.style,
]
return (
<View style={$containerStyles}>
{isImagePresent && (
<Image
source={imageSource}
{...ImageProps}
style={$imageStyles}
tintColor={theme.colors.palette.neutral900}
/>
)}
{isHeadingPresent && (
<Text
preset="subheading"
text={heading}
tx={headingTx}
txOptions={headingTxOptions}
{...HeadingTextProps}
style={$headingStyles}
/>
)}
{isContentPresent && (
<Text
text={content}
tx={contentTx}
txOptions={contentTxOptions}
{...ContentTextProps}
style={$contentStyles}
/>
)}
{isButtonPresent && (
<Button
onPress={buttonOnPress}
text={button}
tx={buttonTx}
txOptions={buttonTxOptions}
textStyle={$buttonTextStyleOverride}
{...ButtonProps}
style={$buttonStyles}
/>
)}
</View>
)
}
const $image: ImageStyle = { alignSelf: "center" }
const $heading: ThemedStyle<TextStyle> = ({ spacing }) => ({
textAlign: "center",
paddingHorizontal: spacing.lg,
})
const $content: ThemedStyle<TextStyle> = ({ spacing }) => ({
textAlign: "center",
paddingHorizontal: spacing.lg,
})
================================================
FILE: boilerplate/app/components/Header.tsx
================================================
import { ReactElement } from "react"
import {
StyleProp,
TextStyle,
TouchableOpacity,
TouchableOpacityProps,
View,
ViewStyle,
} from "react-native"
import { isRTL } from "@/i18n"
import { translate } from "@/i18n/translate"
import type { ThemedStyle } from "@/theme/types"
import { useAppTheme } from "@/theme/context"
import { $styles } from "@/theme/styles"
import { ExtendedEdge, useSafeAreaInsetsStyle } from "@/utils/useSafeAreaInsetsStyle"
import { IconTypes, PressableIcon } from "./Icon"
import { Text, TextProps } from "./Text"
export interface HeaderProps {
/**
* The layout of the title relative to the action components.
* - `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.
* - `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.
*/
titleMode?: "center" | "flex"
/**
* Optional title style override.
*/
titleStyle?: StyleProp<TextStyle>
/**
* Optional outer title container style override.
*/
titleContainerStyle?: StyleProp<ViewStyle>
/**
* Optional inner header wrapper style override.
*/
style?: StyleProp<ViewStyle>
/**
* Optional outer header container style override.
*/
containerStyle?: StyleProp<ViewStyle>
/**
* Background color
*/
backgroundColor?: string
/**
* Title text to display if not using `tx` or nested components.
*/
title?: TextProps["text"]
/**
* Title text which is looked up via i18n.
*/
titleTx?: TextProps["tx"]
/**
* Optional options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
titleTxOptions?: TextProps["txOptions"]
/**
* Icon that should appear on the left.
* Can be used with `onLeftPress`.
*/
leftIcon?: IconTypes
/**
* An optional tint color for the left icon
*/
leftIconColor?: string
/**
* Left action text to display if not using `leftTx`.
* Can be used with `onLeftPress`. Overrides `leftIcon`.
*/
leftText?: TextProps["text"]
/**
* Left action text text which is looked up via i18n.
* Can be used with `onLeftPress`. Overrides `leftIcon`.
*/
leftTx?: TextProps["tx"]
/**
* Left action custom ReactElement if the built in action props don't suffice.
* Overrides `leftIcon`, `leftTx` and `leftText`.
*/
LeftActionComponent?: ReactElement
/**
* Optional options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
leftTxOptions?: TextProps["txOptions"]
/**
* What happens when you press the left icon or text action.
*/
onLeftPress?: TouchableOpacityProps["onPress"]
/**
* Icon that should appear on the right.
* Can be used with `onRightPress`.
*/
rightIcon?: IconTypes
/**
* An optional tint color for the right icon
*/
rightIconColor?: string
/**
* Right action text to display if not using `rightTx`.
* Can be used with `onRightPress`. Overrides `rightIcon`.
*/
rightText?: TextProps["text"]
/**
* Right action text text which is looked up via i18n.
* Can be used with `onRightPress`. Overrides `rightIcon`.
*/
rightTx?: TextProps["tx"]
/**
* Right action custom ReactElement if the built in action props don't suffice.
* Overrides `rightIcon`, `rightTx` and `rightText`.
*/
RightActionComponent?: ReactElement
/**
* Optional options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
rightTxOptions?: TextProps["txOptions"]
/**
* What happens when you press the right icon or text action.
*/
onRightPress?: TouchableOpacityProps["onPress"]
/**
* Override the default edges for the safe area.
*/
safeAreaEdges?: ExtendedEdge[]
}
interface HeaderActionProps {
backgroundColor?: string
icon?: IconTypes
iconColor?: string
text?: TextProps["text"]
tx?: TextProps["tx"]
txOptions?: TextProps["txOptions"]
onPress?: TouchableOpacityProps["onPress"]
ActionComponent?: ReactElement
}
/**
* Header that appears on many screens. Will hold navigation buttons and screen title.
* The Header is meant to be used with the `screenOptions.header` option on navigators, routes, or screen components via `navigation.setOptions({ header })`.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Header/}
* @param {HeaderProps} props - The props for the `Header` component.
* @returns {JSX.Element} The rendered `Header` component.
*/
export function Header(props: HeaderProps) {
const {
theme: { colors },
themed,
} = useAppTheme()
const {
backgroundColor = colors.background,
LeftActionComponent,
leftIcon,
leftIconColor,
leftText,
leftTx,
leftTxOptions,
onLeftPress,
onRightPress,
RightActionComponent,
rightIcon,
rightIconColor,
rightText,
rightTx,
rightTxOptions,
safeAreaEdges = ["top"],
title,
titleMode = "center",
titleTx,
titleTxOptions,
titleContainerStyle: $titleContainerStyleOverride,
style: $styleOverride,
titleStyle: $titleStyleOverride,
containerStyle: $containerStyleOverride,
} = props
const $containerInsets = useSafeAreaInsetsStyle(safeAreaEdges)
const titleContent = titleTx ? translate(titleTx, titleTxOptions) : title
return (
<View style={[$container, $containerInsets, { backgroundColor }, $containerStyleOverride]}>
<View style={[$styles.row, $wrapper, $styleOverride]}>
<HeaderAction
tx={leftTx}
text={leftText}
icon={leftIcon}
iconColor={leftIconColor}
onPress={onLeftPress}
txOptions={leftTxOptions}
backgroundColor={backgroundColor}
ActionComponent={LeftActionComponent}
/>
{!!titleContent && (
<View
style={[
$titleWrapperPointerEvents,
titleMode === "center" && themed($titleWrapperCenter),
titleMode === "flex" && $titleWrapperFlex,
$titleContainerStyleOverride,
]}
>
<Text
weight="medium"
size="md"
text={titleContent}
style={[$title, $titleStyleOverride]}
/>
</View>
)}
<HeaderAction
tx={rightTx}
text={rightText}
icon={rightIcon}
iconColor={rightIconColor}
onPress={onRightPress}
txOptions={rightTxOptions}
backgroundColor={backgroundColor}
ActionComponent={RightActionComponent}
/>
</View>
</View>
)
}
/**
* @param {HeaderActionProps} props - The props for the `HeaderAction` component.
* @returns {JSX.Element} The rendered `HeaderAction` component.
*/
function HeaderAction(props: HeaderActionProps) {
const { backgroundColor, icon, text, tx, txOptions, onPress, ActionComponent, iconColor } = props
const { themed } = useAppTheme()
const content = tx ? translate(tx, txOptions) : text
if (ActionComponent) return ActionComponent
if (content) {
return (
<TouchableOpacity
style={themed([$actionTextContainer, { backgroundColor }])}
onPress={onPress}
disabled={!onPress}
activeOpacity={0.8}
>
<Text weight="medium" size="md" text={content} style={themed($actionText)} />
</TouchableOpacity>
)
}
if (icon) {
return (
<PressableIcon
size={24}
icon={icon}
color={iconColor}
onPress={onPress}
containerStyle={themed([$actionIconContainer, { backgroundColor }])}
style={isRTL ? { transform: [{ rotate: "180deg" }] } : {}}
/>
)
}
return <View style={[$actionFillerContainer, { backgroundColor }]} />
}
const $wrapper: ViewStyle = {
height: 56,
alignItems: "center",
justifyContent: "space-between",
}
const $container: ViewStyle = {
width: "100%",
}
const $title: TextStyle = {
textAlign: "center",
}
const $actionTextContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
flexGrow: 0,
alignItems: "center",
justifyContent: "center",
height: "100%",
paddingHorizontal: spacing.md,
zIndex: 2,
})
const $actionText: ThemedStyle<TextStyle> = ({ colors }) => ({
color: colors.tint,
})
const $actionIconContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
flexGrow: 0,
alignItems: "center",
justifyContent: "center",
height: "100%",
paddingHorizontal: spacing.md,
zIndex: 2,
})
const $actionFillerContainer: ViewStyle = {
width: 16,
}
const $titleWrapperPointerEvents: ViewStyle = {
pointerEvents: "none",
}
const $titleWrapperCenter: ThemedStyle<ViewStyle> = ({ spacing }) => ({
alignItems: "center",
justifyContent: "center",
height: "100%",
width: "100%",
position: "absolute",
paddingHorizontal: spacing.xxl,
zIndex: 1,
})
const $titleWrapperFlex: ViewStyle = {
justifyContent: "center",
flexGrow: 1,
}
================================================
FILE: boilerplate/app/components/Icon.tsx
================================================
import {
Image,
ImageStyle,
StyleProp,
TouchableOpacity,
TouchableOpacityProps,
View,
ViewProps,
ViewStyle,
} from "react-native"
import { useAppTheme } from "@/theme/context"
export type IconTypes = keyof typeof iconRegistry
type BaseIconProps = {
/**
* The name of the icon
*/
icon: IconTypes
/**
* An optional tint color for the icon
*/
color?: string
/**
* An optional size for the icon. If not provided, the icon will be sized to the icon's resolution.
*/
size?: number
/**
* Style overrides for the icon image
*/
style?: StyleProp<ImageStyle>
/**
* Style overrides for the icon container
*/
containerStyle?: StyleProp<ViewStyle>
}
type PressableIconProps = Omit<TouchableOpacityProps, "style"> & BaseIconProps
type IconProps = Omit<ViewProps, "style"> & BaseIconProps
/**
* A component to render a registered icon.
* It is wrapped in a <TouchableOpacity />
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Icon/}
* @param {PressableIconProps} props - The props for the `PressableIcon` component.
* @returns {JSX.Element} The rendered `PressableIcon` component.
*/
export function PressableIcon(props: PressableIconProps) {
const {
icon,
color,
size,
style: $imageStyleOverride,
containerStyle: $containerStyleOverride,
...pressableProps
} = props
const { theme } = useAppTheme()
const $imageStyle: StyleProp<ImageStyle> = [
$imageStyleBase,
{ tintColor: color ?? theme.colors.text },
size !== undefined && { width: size, height: size },
$imageStyleOverride,
]
return (
<TouchableOpacity {...pressableProps} style={$containerStyleOverride}>
<Image style={$imageStyle} source={iconRegistry[icon]} />
</TouchableOpacity>
)
}
/**
* A component to render a registered icon.
* It is wrapped in a <View />, use `PressableIcon` if you want to react to input
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Icon/}
* @param {IconProps} props - The props for the `Icon` component.
* @returns {JSX.Element} The rendered `Icon` component.
*/
export function Icon(props: IconProps) {
const {
icon,
color,
size,
style: $imageStyleOverride,
containerStyle: $containerStyleOverride,
...viewProps
} = props
const { theme } = useAppTheme()
const $imageStyle: StyleProp<ImageStyle> = [
$imageStyleBase,
{ tintColor: color ?? theme.colors.text },
size !== undefined && { width: size, height: size },
$imageStyleOverride,
]
return (
<View {...viewProps} style={$containerStyleOverride}>
<Image style={$imageStyle} source={iconRegistry[icon]} />
</View>
)
}
export const iconRegistry = {
back: require("@assets/icons/back.png"),
bell: require("@assets/icons/bell.png"),
caretLeft: require("@assets/icons/caretLeft.png"),
caretRight: require("@assets/icons/caretRight.png"),
check: require("@assets/icons/check.png"),
clap: require("@assets/icons/demo/clap.png"), // @demo remove-current-line
community: require("@assets/icons/demo/community.png"), // @demo remove-current-line
components: require("@assets/icons/demo/components.png"), // @demo remove-current-line
debug: require("@assets/icons/demo/debug.png"), // @demo remove-current-line
github: require("@assets/icons/demo/github.png"), // @demo remove-current-line
heart: require("@assets/icons/demo/heart.png"), // @demo remove-current-line
hidden: require("@assets/icons/hidden.png"),
ladybug: require("@assets/icons/ladybug.png"),
lock: require("@assets/icons/lock.png"),
menu: require("@assets/icons/menu.png"),
more: require("@assets/icons/more.png"),
pin: require("@assets/icons/demo/pin.png"), // @demo remove-current-line
podcast: require("@assets/icons/demo/podcast.png"), // @demo remove-current-line
settings: require("@assets/icons/settings.png"),
slack: require("@assets/icons/demo/slack.png"), // @demo remove-current-line
view: require("@assets/icons/view.png"),
x: require("@assets/icons/x.png"),
}
const $imageStyleBase: ImageStyle = {
resizeMode: "contain",
}
================================================
FILE: boilerplate/app/components/ListItem.tsx
================================================
import { forwardRef, ReactElement } from "react"
import {
StyleProp,
TextStyle,
TouchableOpacity,
TouchableOpacityProps,
View,
ViewStyle,
} from "react-native"
import type { ThemedStyle } from "@/theme/types"
import { useAppTheme } from "@/theme/context"
import { $styles } from "@/theme/styles"
import { Icon, IconTypes } from "./Icon"
import { Text, TextProps } from "./Text"
export interface ListItemProps extends TouchableOpacityProps {
/**
* How tall the list item should be.
* Default: 56
*/
height?: number
/**
* Whether to show the top separator.
* Default: false
*/
topSeparator?: boolean
/**
* Whether to show the bottom separator.
* Default: false
*/
bottomSeparator?: boolean
/**
* Text to display if not using `tx` or nested components.
*/
text?: TextProps["text"]
/**
* Text which is looked up via i18n.
*/
tx?: TextProps["tx"]
/**
* Children components.
*/
children?: TextProps["children"]
/**
* Optional options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
txOptions?: TextProps["txOptions"]
/**
* Optional text style override.
*/
textStyle?: StyleProp<TextStyle>
/**
* Pass any additional props directly to the Text component.
*/
TextProps?: TextProps
/**
* Optional View container style override.
*/
containerStyle?: StyleProp<ViewStyle>
/**
* Optional TouchableOpacity style override.
*/
style?: StyleProp<ViewStyle>
/**
* Icon that should appear on the left.
*/
leftIcon?: IconTypes
/**
* An optional tint color for the left icon
*/
leftIconColor?: string
/**
* Icon that should appear on the right.
*/
rightIcon?: IconTypes
/**
* An optional tint color for the right icon
*/
rightIconColor?: string
/**
* Right action custom ReactElement.
* Overrides `rightIcon`.
*/
RightComponent?: ReactElement
/**
* Left action custom ReactElement.
* Overrides `leftIcon`.
*/
LeftComponent?: ReactElement
}
interface ListItemActionProps {
icon?: IconTypes
iconColor?: string
Component?: ReactElement
size: number
side: "left" | "right"
}
/**
* A styled row component that can be used in FlatList, SectionList, or by itself.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/ListItem/}
* @param {ListItemProps} props - The props for the `ListItem` component.
* @returns {JSX.Element} The rendered `ListItem` component.
*/
export const ListItem = forwardRef<View, ListItemProps>(function ListItem(
props: ListItemProps,
ref,
) {
const {
bottomSeparator,
children,
height = 56,
LeftComponent,
leftIcon,
leftIconColor,
RightComponent,
rightIcon,
rightIconColor,
style,
text,
TextProps,
topSeparator,
tx,
txOptions,
textStyle: $textStyleOverride,
containerStyle: $containerStyleOverride,
...TouchableOpacityProps
} = props
const { themed } = useAppTheme()
const isTouchable =
TouchableOpacityProps.onPress !== undefined ||
TouchableOpacityProps.onPressIn !== undefined ||
TouchableOpacityProps.onPressOut !== undefined ||
TouchableOpacityProps.onLongPress !== undefined
const $textStyles = [$textStyle, $textStyleOverride, TextProps?.style]
const $containerStyles = [
topSeparator && $separatorTop,
bottomSeparator && $separatorBottom,
$containerStyleOverride,
]
const $touchableStyles = [$styles.row, $touchableStyle, { minHeight: height }, style]
const Wrapper = isTouchable ? TouchableOpacity : View
return (
<View ref={ref} style={themed($containerStyles)}>
<Wrapper {...TouchableOpacityProps} style={$touchableStyles}>
<ListItemAction
side="left"
size={height}
icon={leftIcon}
iconColor={leftIconColor}
Component={LeftComponent}
/>
<Text {...TextProps} tx={tx} text={text} txOptions={txOptions} style={themed($textStyles)}>
{children}
</Text>
<ListItemAction
side="right"
size={height}
icon={rightIcon}
iconColor={rightIconColor}
Component={RightComponent}
/>
</Wrapper>
</View>
)
})
/**
* @param {ListItemActionProps} props - The props for the `ListItemAction` component.
* @returns {JSX.Element | null} The rendered `ListItemAction` component.
*/
function ListItemAction(props: ListItemActionProps) {
const { icon, Component, iconColor, size, side } = props
const { themed } = useAppTheme()
const $iconContainerStyles = [$iconContainer]
if (Component) return Component
if (icon !== undefined) {
return (
<Icon
size={24}
icon={icon}
color={iconColor}
containerStyle={themed([
$iconContainerStyles,
side === "left" && $iconContainerLeft,
side === "right" && $iconContainerRight,
{ height: size },
])}
/>
)
}
return null
}
const $separatorTop: ThemedStyle<ViewStyle> = ({ colors }) => ({
borderTopWidth: 1,
borderTopColor: colors.separator,
})
const $separatorBottom: ThemedStyle<ViewStyle> = ({ colors }) => ({
borderBottomWidth: 1,
borderBottomColor: colors.separator,
})
const $textStyle: ThemedStyle<TextStyle> = ({ spacing }) => ({
paddingVertical: spacing.xs,
alignSelf: "center",
flexGrow: 1,
flexShrink: 1,
})
const $touchableStyle: ViewStyle = {
alignItems: "flex-start",
}
const $iconContainer: ViewStyle = {
justifyContent: "center",
alignItems: "center",
flexGrow: 0,
}
const $iconContainerLeft: ThemedStyle<ViewStyle> = ({ spacing }) => ({
marginEnd: spacing.md,
})
const $iconContainerRight: ThemedStyle<ViewStyle> = ({ spacing }) => ({
marginStart: spacing.md,
})
================================================
FILE: boilerplate/app/components/Screen.tsx
================================================
import { ReactNode, useRef, useState } from "react"
import {
KeyboardAvoidingView,
KeyboardAvoidingViewProps,
LayoutChangeEvent,
Platform,
ScrollViewProps,
StyleProp,
View,
ViewStyle,
} from "react-native"
import { useScrollToTop } from "@react-navigation/native"
import { SystemBars, SystemBarsProps, SystemBarStyle } from "react-native-edge-to-edge"
import {
KeyboardAwareScrollView,
type KeyboardAwareScrollViewRef,
} from "react-native-keyboard-controller"
import { useAppTheme } from "@/theme/context"
import { $styles } from "@/theme/styles"
import { ExtendedEdge, useSafeAreaInsetsStyle } from "@/utils/useSafeAreaInsetsStyle"
export const DEFAULT_BOTTOM_OFFSET = 50
interface BaseScreenProps {
/**
* Children components.
*/
children?: ReactNode
/**
* Style for the outer content container useful for padding & margin.
*/
style?: StyleProp<ViewStyle>
/**
* Style for the inner content container useful for padding & margin.
*/
contentContainerStyle?: StyleProp<ViewStyle>
/**
* Override the default edges for the safe area.
*/
safeAreaEdges?: ExtendedEdge[]
/**
* Background color
*/
backgroundColor?: string
/**
* System bar setting. Defaults to dark.
*/
systemBarStyle?: SystemBarStyle
/**
* By how much should we offset the keyboard? Defaults to 0.
*/
keyboardOffset?: number
/**
* By how much we scroll up when the keyboard is shown. Defaults to 50.
*/
keyboardBottomOffset?: number
/**
* Pass any additional props directly to the SystemBars component.
*/
SystemBarsProps?: SystemBarsProps
/**
* Pass any additional props directly to the KeyboardAvoidingView component.
*/
KeyboardAvoidingViewProps?: KeyboardAvoidingViewProps
}
interface FixedScreenProps extends BaseScreenProps {
preset?: "fixed"
}
interface ScrollScreenProps extends BaseScreenProps {
preset?: "scroll"
/**
* Should keyboard persist on screen tap. Defaults to handled.
* Only applies to scroll preset.
*/
keyboardShouldPersistTaps?: "handled" | "always" | "never"
/**
* Pass any additional props directly to the ScrollView component.
*/
ScrollViewProps?: ScrollViewProps
}
interface AutoScreenProps extends Omit<ScrollScreenProps, "preset"> {
preset?: "auto"
/**
* Threshold to trigger the automatic disabling/enabling of scroll ability.
* Defaults to `{ percent: 0.92 }`.
*/
scrollEnabledToggleThreshold?: { percent?: number; point?: number }
}
export type ScreenProps = ScrollScreenProps | FixedScreenProps | AutoScreenProps
const isIos = Platform.OS === "ios"
type ScreenPreset = "fixed" | "scroll" | "auto"
/**
* @param {ScreenPreset?} preset - The preset to check.
* @returns {boolean} - Whether the preset is non-scrolling.
*/
function isNonScrolling(preset?: ScreenPreset) {
return !preset || preset === "fixed"
}
/**
* Custom hook that handles the automatic enabling/disabling of scroll ability based on the content size and screen size.
* @param {UseAutoPresetProps} props - The props for the `useAutoPreset` hook.
* @returns {{boolean, Function, Function}} - The scroll state, and the `onContentSizeChange` and `onLayout` functions.
*/
function useAutoPreset(props: AutoScreenProps): {
scrollEnabled: boolean
onContentSizeChange: (w: number, h: number) => void
onLayout: (e: LayoutChangeEvent) => void
} {
const { preset, scrollEnabledToggleThreshold } = props
const { percent = 0.92, point = 0 } = scrollEnabledToggleThreshold || {}
const scrollViewHeight = useRef<null | number>(null)
const scrollViewContentHeight = useRef<null | number>(null)
const [scrollEnabled, setScrollEnabled] = useState(true)
function updateScrollState() {
if (scrollViewHeight.current === null || scrollViewContentHeight.current === null) return
// check whether content fits the screen then toggle scroll state according to it
const contentFitsScreen = (function () {
if (point) {
return scrollViewContentHeight.current < scrollViewHeight.current - point
} else {
return scrollViewContentHeight.current < scrollViewHeight.current * percent
}
})()
// content is less than the size of the screen, so we can disable scrolling
if (scrollEnabled && contentFitsScreen) setScrollEnabled(false)
// content is greater than the size of the screen, so let's enable scrolling
if (!scrollEnabled && !contentFitsScreen) setScrollEnabled(true)
}
/**
* @param {number} w - The width of the content.
* @param {number} h - The height of the content.
*/
function onContentSizeChange(w: number, h: number) {
// update scroll-view content height
scrollViewContentHeight.current = h
updateScrollState()
}
/**
* @param {LayoutChangeEvent} e = The layout change event.
*/
function onLayout(e: LayoutChangeEvent) {
const { height } = e.nativeEvent.layout
// update scroll-view height
scrollViewHeight.current = height
updateScrollState()
}
// update scroll state on every render
if (preset === "auto") updateScrollState()
return {
scrollEnabled: preset === "auto" ? scrollEnabled : true,
onContentSizeChange,
onLayout,
}
}
/**
* @param {ScreenProps} props - The props for the `ScreenWithoutScrolling` component.
* @returns {JSX.Element} - The rendered `ScreenWithoutScrolling` component.
*/
function ScreenWithoutScrolling(props: ScreenProps) {
const { style, contentContainerStyle, children, preset } = props
return (
<View style={[$outerStyle, style]}>
<View style={[$innerStyle, preset === "fixed" && $justifyFlexEnd, contentContainerStyle]}>
{children}
</View>
</View>
)
}
/**
* @param {ScreenProps} props - The props for the `ScreenWithScrolling` component.
* @returns {JSX.Element} - The rendered `ScreenWithScrolling` component.
*/
function ScreenWithScrolling(props: ScreenProps) {
const {
children,
keyboardShouldPersistTaps = "handled",
keyboardBottomOffset = DEFAULT_BOTTOM_OFFSET,
contentContainerStyle,
ScrollViewProps,
style,
} = props as ScrollScreenProps
const ref = useRef<KeyboardAwareScrollViewRef>(null)
const { scrollEnabled, onContentSizeChange, onLayout } = useAutoPreset(props as AutoScreenProps)
// Add native behavior of pressing the active tab to scroll to the top of the content
// More info at: https://reactnavigation.org/docs/use-scroll-to-top/
useScrollToTop(ref)
return (
<KeyboardAwareScrollView
bottomOffset={keyboardBottomOffset}
{...{ keyboardShouldPersistTaps, scrollEnabled, ref }}
{...ScrollViewProps}
onLayout={(e) => {
onLayout(e)
ScrollViewProps?.onLayout?.(e)
}}
onContentSizeChange={(w: number, h: number) => {
onContentSizeChange(w, h)
ScrollViewProps?.onContentSizeChange?.(w, h)
}}
style={[$outerStyle, ScrollViewProps?.style, style]}
contentContainerStyle={[
$innerStyle,
ScrollViewProps?.contentContainerStyle,
contentContainerStyle,
]}
>
{children}
</KeyboardAwareScrollView>
)
}
/**
* Represents a screen component that provides a consistent layout and behavior for different screen presets.
* The `Screen` component can be used with different presets such as "fixed", "scroll", or "auto".
* It handles safe area insets, status bar settings, keyboard avoiding behavior, and scrollability based on the preset.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Screen/}
* @param {ScreenProps} props - The props for the `Screen` component.
* @returns {JSX.Element} The rendered `Screen` component.
*/
export function Screen(props: ScreenProps) {
const {
theme: { colors },
themeContext,
} = useAppTheme()
const {
backgroundColor,
KeyboardAvoidingViewProps,
keyboardOffset = 0,
safeAreaEdges,
SystemBarsProps,
systemBarStyle,
} = props
const $containerInsets = useSafeAreaInsetsStyle(safeAreaEdges)
return (
<View
style={[
$containerStyle,
{ backgroundColor: backgroundColor || colors.background },
$containerInsets,
]}
>
<SystemBars
style={systemBarStyle || (themeContext === "dark" ? "light" : "dark")}
{...SystemBarsProps}
/>
<KeyboardAvoidingView
behavior={isIos ? "padding" : "height"}
keyboardVerticalOffset={keyboardOffset}
{...KeyboardAvoidingViewProps}
style={[$styles.flex1, KeyboardAvoidingViewProps?.style]}
>
{isNonScrolling(props.preset) ? (
<ScreenWithoutScrolling {...props} />
) : (
<ScreenWithScrolling {...props} />
)}
</KeyboardAvoidingView>
</View>
)
}
const $containerStyle: ViewStyle = {
flex: 1,
height: "100%",
width: "100%",
}
const $outerStyle: ViewStyle = {
flex: 1,
height: "100%",
width: "100%",
}
const $justifyFlexEnd: ViewStyle = {
justifyContent: "flex-end",
}
const $innerStyle: ViewStyle = {
justifyContent: "flex-start",
alignItems: "stretch",
}
================================================
FILE: boilerplate/app/components/Text.test.tsx
================================================
import { NavigationContainer } from "@react-navigation/native"
import { render } from "@testing-library/react-native"
import { Text } from "./Text"
import { ThemeProvider } from "../theme/context"
/* This is an example component test using react-native-testing-library. For more
* information on how to write your own, see the documentation here:
* https://callstack.github.io/react-native-testing-library/ */
const testText = "Test string"
describe("Text", () => {
it("should render the component", () => {
const { getByText } = render(
<ThemeProvider>
<NavigationContainer>
<Text text={testText} />
</NavigationContainer>
</ThemeProvider>,
)
expect(getByText(testText)).toBeDefined()
})
})
================================================
FILE: boilerplate/app/components/Text.tsx
================================================
import { ReactNode, forwardRef, ForwardedRef } from "react"
// eslint-disable-next-line no-restricted-imports
import { StyleProp, Text as RNText, TextProps as RNTextProps, TextStyle } from "react-native"
import { TOptions } from "i18next"
import { isRTL, TxKeyPath } from "@/i18n"
import { translate } from "@/i18n/translate"
import type { ThemedStyle, ThemedStyleArray } from "@/theme/types"
import { useAppTheme } from "@/theme/context"
import { typography } from "@/theme/typography"
type Sizes = keyof typeof $sizeStyles
type Weights = keyof typeof typography.primary
type Presets = "default" | "bold" | "heading" | "subheading" | "formLabel" | "formHelper"
export interface TextProps extends RNTextProps {
/**
* Text which is looked up via i18n.
*/
tx?: TxKeyPath
/**
* The text to display if not using `tx` or nested components.
*/
text?: string
/**
* Optional options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
txOptions?: TOptions
/**
* An optional style override useful for padding & margin.
*/
style?: StyleProp<TextStyle>
/**
* One of the different types of text presets.
*/
preset?: Presets
/**
* Text weight modifier.
*/
weight?: Weights
/**
* Text size modifier.
*/
size?: Sizes
/**
* Children components.
*/
children?: ReactNode
}
/**
* For your text displaying needs.
* This component is a HOC over the built-in React Native one.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Text/}
* @param {TextProps} props - The props for the `Text` component.
* @returns {JSX.Element} The rendered `Text` component.
*/
export const Text = forwardRef(function Text(props: TextProps, ref: ForwardedRef<RNText>) {
const { weight, size, tx, txOptions, text, children, style: $styleOverride, ...rest } = props
const { themed } = useAppTheme()
const i18nText = tx && translate(tx, txOptions)
const content = i18nText || text || children
const preset: Presets = props.preset ?? "default"
const $styles: StyleProp<TextStyle> = [
$rtlStyle,
themed($presets[preset]),
weight && $fontWeightStyles[weight],
size && $sizeStyles[size],
$styleOverride,
]
return (
<RNText {...rest} style={$styles} ref={ref}>
{content}
</RNText>
)
})
const $sizeStyles = {
xxl: { fontSize: 36, lineHeight: 44 } satisfies TextStyle,
xl: { fontSize: 24, lineHeight: 34 } satisfies TextStyle,
lg: { fontSize: 20, lineHeight: 32 } satisfies TextStyle,
md: { fontSize: 18, lineHeight: 26 } satisfies TextStyle,
sm: { fontSize: 16, lineHeight: 24 } satisfies TextStyle,
xs: { fontSize: 14, lineHeight: 21 } satisfies TextStyle,
xxs: { fontSize: 12, lineHeight: 18 } satisfies TextStyle,
}
const $fontWeightStyles = Object.entries(typography.primary).reduce((acc, [weight, fontFamily]) => {
return { ...acc, [weight]: { fontFamily } }
}, {}) as Record<Weights, TextStyle>
const $baseStyle: ThemedStyle<TextStyle> = (theme) => ({
...$sizeStyles.sm,
...$fontWeightStyles.normal,
color: theme.colors.text,
})
const $presets: Record<Presets, ThemedStyleArray<TextStyle>> = {
default: [$baseStyle],
bold: [$baseStyle, { ...$fontWeightStyles.bold }],
heading: [
$baseStyle,
{
...$sizeStyles.xxl,
...$fontWeightStyles.bold,
},
],
subheading: [$baseStyle, { ...$sizeStyles.lg, ...$fontWeightStyles.medium }],
formLabel: [$baseStyle, { ...$fontWeightStyles.medium }],
formHelper: [$baseStyle, { ...$sizeStyles.sm, ...$fontWeightStyles.normal }],
}
const $rtlStyle: TextStyle = isRTL ? { writingDirection: "rtl" } : {}
================================================
FILE: boilerplate/app/components/TextField.tsx
================================================
import { ComponentType, forwardRef, Ref, useImperativeHandle, useRef } from "react"
import {
ImageStyle,
StyleProp,
// eslint-disable-next-line no-restricted-imports
TextInput,
TextInputProps,
TextStyle,
TouchableOpacity,
View,
ViewStyle,
} from "react-native"
import { isRTL } from "@/i18n"
import { translate } from "@/i18n/translate"
import type { ThemedStyle, ThemedStyleArray } from "@/theme/types"
import { useAppTheme } from "@/theme/context"
import { $styles } from "@/theme/styles"
import { Text, TextProps } from "./Text"
export interface TextFieldAccessoryProps {
style: StyleProp<ViewStyle | TextStyle | ImageStyle>
status: TextFieldProps["status"]
multiline: boolean
editable: boolean
}
export interface TextFieldProps extends Omit<TextInputProps, "ref"> {
/**
* A style modifier for different input states.
*/
status?: "error" | "disabled"
/**
* The label text to display if not using `labelTx`.
*/
label?: TextProps["text"]
/**
* Label text which is looked up via i18n.
*/
labelTx?: TextProps["tx"]
/**
* Optional label options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
labelTxOptions?: TextProps["txOptions"]
/**
* Pass any additional props directly to the label Text component.
*/
LabelTextProps?: TextProps
/**
* The helper text to display if not using `helperTx`.
*/
helper?: TextProps["text"]
/**
* Helper text which is looked up via i18n.
*/
helperTx?: TextProps["tx"]
/**
* Optional helper options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
helperTxOptions?: TextProps["txOptions"]
/**
* Pass any additional props directly to the helper Text component.
*/
HelperTextProps?: TextProps
/**
* The placeholder text to display if not using `placeholderTx`.
*/
placeholder?: TextProps["text"]
/**
* Placeholder text which is looked up via i18n.
*/
placeholderTx?: TextProps["tx"]
/**
* Optional placeholder options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
placeholderTxOptions?: TextProps["txOptions"]
/**
* Optional input style override.
*/
style?: StyleProp<TextStyle>
/**
* Style overrides for the container
*/
containerStyle?: StyleProp<ViewStyle>
/**
* Style overrides for the input wrapper
*/
inputWrapperStyle?: StyleProp<ViewStyle>
/**
* An optional component to render on the right side of the input.
* Example: `RightAccessory={(props) => <Icon icon="ladybug" containerStyle={props.style} color={props.editable ? colors.textDim : colors.text} />}`
* Note: It is a good idea to memoize this.
*/
RightAccessory?: ComponentType<TextFieldAccessoryProps>
/**
* An optional component to render on the left side of the input.
* Example: `LeftAccessory={(props) => <Icon icon="ladybug" containerStyle={props.style} color={props.editable ? colors.textDim : colors.text} />}`
* Note: It is a good idea to memoize this.
*/
LeftAccessory?: ComponentType<TextFieldAccessoryProps>
}
/**
* A component that allows for the entering and editing of text.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/TextField/}
* @param {TextFieldProps} props - The props for the `TextField` component.
* @returns {JSX.Element} The rendered `TextField` component.
*/
export const TextField = forwardRef(function TextField(props: TextFieldProps, ref: Ref<TextInput>) {
const {
labelTx,
label,
labelTxOptions,
placeholderTx,
placeholder,
placeholderTxOptions,
helper,
helperTx,
helperTxOptions,
status,
RightAccessory,
LeftAccessory,
HelperTextProps,
LabelTextProps,
style: $inputStyleOverride,
containerStyle: $containerStyleOverride,
inputWrapperStyle: $inputWrapperStyleOverride,
...TextInputProps
} = props
const input = useRef<TextInput>(null)
const {
themed,
theme: { colors },
} = useAppTheme()
const disabled = TextInputProps.editable === false || status === "disabled"
const placeholderContent = placeholderTx
? translate(placeholderTx, placeholderTxOptions)
: placeholder
const $containerStyles = [$containerStyleOverride]
const $labelStyles = [$labelStyle, LabelTextProps?.style]
const $inputWrapperStyles = [
$styles.row,
$inputWrapperStyle,
status === "error" && { borderColor: colors.error },
TextInputProps.multiline && { minHeight: 112 },
LeftAccessory && { paddingStart: 0 },
RightAccessory && { paddingEnd: 0 },
$inputWrapperStyleOverride,
]
const $inputStyles: ThemedStyleArray<TextStyle> = [
$inputStyle,
disabled && { color: colors.textDim },
isRTL && { textAlign: "right" as TextStyle["textAlign"] },
TextInputProps.multiline && { height: "auto" },
$inputStyleOverride,
]
const $helperStyles = [
$helperStyle,
status === "error" && { color: colors.error },
HelperTextProps?.style,
]
/**
*
*/
function focusInput() {
if (disabled) return
input.current?.focus()
}
useImperativeHandle(ref, () => input.current as TextInput)
return (
<TouchableOpacity
activeOpacity={1}
style={$containerStyles}
onPress={focusInput}
accessibilityState={{ disabled }}
>
{!!(label || labelTx) && (
<Text
preset="formLabel"
text={label}
tx={labelTx}
txOptions={labelTxOptions}
{...LabelTextProps}
style={themed($labelStyles)}
/>
)}
<View style={themed($inputWrapperStyles)}>
{!!LeftAccessory && (
<LeftAccessory
style={themed($leftAccessoryStyle)}
status={status}
editable={!disabled}
multiline={TextInputProps.multiline ?? false}
/>
)}
<TextInput
ref={input}
underlineColorAndroid={colors.transparent}
textAlignVertical="top"
placeholder={placeholderContent}
placeholderTextColor={colors.textDim}
{...TextInputProps}
editable={!disabled}
style={themed($inputStyles)}
/>
{!!RightAccessory && (
<RightAccessory
style={themed($rightAccessoryStyle)}
status={status}
editable={!disabled}
multiline={TextInputProps.multiline ?? false}
/>
)}
</View>
{!!(helper || helperTx) && (
<Text
preset="formHelper"
text={helper}
tx={helperTx}
txOptions={helperTxOptions}
{...HelperTextProps}
style={themed($helperStyles)}
/>
)}
</TouchableOpacity>
)
})
const $labelStyle: ThemedStyle<TextStyle> = ({ spacing }) => ({
marginBottom: spacing.xs,
})
const $inputWrapperStyle: ThemedStyle<ViewStyle> = ({ colors }) => ({
alignItems: "flex-start",
borderWidth: 1,
borderRadius: 4,
backgroundColor: colors.palette.neutral200,
borderColor: colors.palette.neutral400,
overflow: "hidden",
})
const $inputStyle: ThemedStyle<TextStyle> = ({ colors, typography, spacing }) => ({
flex: 1,
alignSelf: "stretch",
fontFamily: typography.primary.normal,
color: colors.text,
fontSize: 16,
height: 24,
// https://github.com/facebook/react-native/issues/21720#issuecomment-532642093
paddingVertical: 0,
paddingHorizontal: 0,
marginVertical: spacing.xs,
marginHorizontal: spacing.sm,
})
const $helperStyle: ThemedStyle<TextStyle> = ({ spacing }) => ({
marginTop: spacing.xs,
})
const $rightAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({
marginEnd: spacing.xs,
height: 40,
justifyContent: "center",
alignItems: "center",
})
const $leftAccessoryStyle: ThemedStyle<ViewStyle> = ({ spacing }) => ({
marginStart: spacing.xs,
height: 40,
justifyContent: "center",
alignItems: "center",
})
================================================
FILE: boilerplate/app/components/Toggle/Checkbox.tsx
================================================
import { useEffect, useRef, useCallback } from "react"
import { Image, ImageStyle, Animated, StyleProp, View, ViewStyle } from "react-native"
import { useAppTheme } from "@/theme/context"
import { $styles } from "@/theme/styles"
import { iconRegistry, IconTypes } from "../Icon"
import { $inputOuterBase, BaseToggleInputProps, ToggleProps, Toggle } from "./Toggle"
export interface CheckboxToggleProps extends Omit<ToggleProps<CheckboxInputProps>, "ToggleInput"> {
/**
* Optional style prop that affects the Image component.
*/
inputDetailStyle?: ImageStyle
/**
* Checkbox-only prop that changes the icon used for the "on" state.
*/
icon?: IconTypes
}
interface CheckboxInputProps extends BaseToggleInputProps<CheckboxToggleProps> {
icon?: CheckboxToggleProps["icon"]
}
/**
* @param {CheckboxToggleProps} props - The props for the `Checkbox` component.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Checkbox}
* @returns {JSX.Element} The rendered `Checkbox` component.
*/
export function Checkbox(props: CheckboxToggleProps) {
const { icon, ...rest } = props
const checkboxInput = useCallback(
(toggleProps: CheckboxInputProps) => <CheckboxInput {...toggleProps} icon={icon} />,
[icon],
)
return <Toggle accessibilityRole="checkbox" {...rest} ToggleInput={checkboxInput} />
}
function CheckboxInput(props: CheckboxInputProps) {
const {
on,
status,
disabled,
icon = "check",
outerStyle: $outerStyleOverride,
innerStyle: $innerStyleOverride,
detailStyle: $detailStyleOverride,
} = props
const {
theme: { colors },
} = useAppTheme()
const opacity = useRef(new Animated.Value(0))
useEffect(() => {
Animated.timing(opacity.current, {
toValue: on ? 1 : 0,
duration: 300,
useNativeDriver: true,
}).start()
}, [on])
const offBackgroundColor = [
disabled && colors.palette.neutral400,
status === "error" && colors.errorBackground,
colors.palette.neutral200,
].filter(Boolean)[0]
const outerBorderColor = [
disabled && colors.palette.neutral400,
status === "error" && colors.error,
!on && colors.palette.neutral800,
colors.palette.secondary500,
].filter(Boolean)[0]
const onBackgroundColor = [
disabled && colors.transparent,
status === "error" && colors.errorBackground,
colors.palette.secondary500,
].filter(Boolean)[0]
const iconTintColor = [
disabled && colors.palette.neutral600,
status === "error" && colors.error,
colors.palette.accent100,
].filter(Boolean)[0]
return (
<View
style={[
$inputOuter,
{ backgroundColor: offBackgroundColor, borderColor: outerBorderColor },
$outerStyleOverride,
]}
>
<Animated.View
style={[
$styles.toggleInner,
{ backgroundColor: onBackgroundColor },
$innerStyleOverride,
{ opacity: opacity.current },
]}
>
<Image
source={icon ? iconRegistry[icon] : iconRegistry.check}
style={[
$checkboxDetail,
!!iconTintColor && { tintColor: iconTintColor },
$detailStyleOverride as ImageStyle,
]}
/>
</Animated.View>
</View>
)
}
const $checkboxDetail: ImageStyle = {
width: 20,
height: 20,
resizeMode: "contain",
}
const $inputOuter: StyleProp<ViewStyle> = [$inputOuterBase, { borderRadius: 4 }]
================================================
FILE: boilerplate/app/components/Toggle/Radio.tsx
================================================
import { useEffect, useRef } from "react"
import { StyleProp, View, ViewStyle, Animated } from "react-native"
import { useAppTheme } from "@/theme/context"
import { $styles } from "@/theme/styles"
import { $inputOuterBase, BaseToggleInputProps, ToggleProps, Toggle } from "./Toggle"
export interface RadioToggleProps extends Omit<ToggleProps<RadioInputProps>, "ToggleInput"> {
/**
* Optional style prop that affects the dot View.
*/
inputDetailStyle?: ViewStyle
}
interface RadioInputProps extends BaseToggleInputProps<RadioToggleProps> {}
/**
* @param {RadioToggleProps} props - The props for the `Radio` component.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Radio}
* @returns {JSX.Element} The rendered `Radio` component.
*/
export function Radio(props: RadioToggleProps) {
return <Toggle accessibilityRole="radio" {...props} ToggleInput={RadioInput} />
}
function RadioInput(props: RadioInputProps) {
const {
on,
status,
disabled,
outerStyle: $outerStyleOverride,
innerStyle: $innerStyleOverride,
detailStyle: $detailStyleOverride,
} = props
const {
theme: { colors },
} = useAppTheme()
const opacity = useRef(new Animated.Value(0))
useEffect(() => {
Animated.timing(opacity.current, {
toValue: on ? 1 : 0,
duration: 300,
useNativeDriver: true,
}).start()
}, [on])
const offBackgroundColor = [
disabled && colors.palette.neutral400,
status === "error" && colors.errorBackground,
colors.palette.neutral200,
].filter(Boolean)[0]
const outerBorderColor = [
disabled && colors.palette.neutral400,
status === "error" && colors.error,
!on && colors.palette.neutral800,
colors.palette.secondary500,
].filter(Boolean)[0]
const onBackgroundColor = [
disabled && colors.transparent,
status === "error" && colors.errorBackground,
colors.palette.neutral100,
].filter(Boolean)[0]
const dotBackgroundColor = [
disabled && colors.palette.neutral600,
status === "error" && colors.error,
colors.palette.secondary500,
].filter(Boolean)[0]
return (
<View
style={[
$inputOuter,
{ backgroundColor: offBackgroundColor, borderColor: outerBorderColor },
$outerStyleOverride,
]}
>
<Animated.View
style={[
$styles.toggleInner,
{ backgroundColor: onBackgroundColor },
$innerStyleOverride,
{ opacity: opacity.current },
]}
>
<View
style={[$radioDetail, { backgroundColor: dotBackgroundColor }, $detailStyleOverride]}
/>
</Animated.View>
</View>
)
}
const $radioDetail: ViewStyle = {
width: 12,
height: 12,
borderRadius: 6,
}
const $inputOuter: StyleProp<ViewStyle> = [$inputOuterBase, { borderRadius: 12 }]
================================================
FILE: boilerplate/app/components/Toggle/Switch.tsx
================================================
import { useEffect, useMemo, useRef, useCallback } from "react"
import { Animated, Image, ImageStyle, Platform, StyleProp, View, ViewStyle } from "react-native"
import { iconRegistry } from "@/components/Icon"
import { isRTL } from "@/i18n"
import type { ThemedStyle } from "@/theme/types"
import { useAppTheme } from "@/theme/context"
import { $styles } from "@/theme/styles"
import { $inputOuterBase, BaseToggleInputProps, Toggle, ToggleProps } from "./Toggle"
export interface SwitchToggleProps extends Omit<ToggleProps<SwitchInputProps>, "ToggleInput"> {
/**
* Switch-only prop that adds a text/icon label for on/off states.
*/
accessibilityMode?: "text" | "icon"
/**
* Optional style prop that affects the knob View.
* Note: `width` and `height` rules should be points (numbers), not percentages.
*/
inputDetailStyle?: Omit<ViewStyle, "width" | "height"> & { width?: number; height?: number }
}
interface SwitchInputProps extends BaseToggleInputProps<SwitchToggleProps> {
accessibilityMode?: SwitchToggleProps["accessibilityMode"]
}
/**
* @param {SwitchToggleProps} props - The props for the `Switch` component.
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/Switch}
* @returns {JSX.Element} The rendered `Switch` component.
*/
export function Switch(props: SwitchToggleProps) {
const { accessibilityMode, ...rest } = props
const switchInput = useCallback(
(toggleProps: SwitchInputProps) => (
<SwitchInput {...toggleProps} accessibilityMode={accessibilityMode} />
),
[accessibilityMode],
)
return <Toggle accessibilityRole="switch" {...rest} ToggleInput={switchInput} />
}
function SwitchInput(props: SwitchInputProps) {
const {
on,
status,
disabled,
outerStyle: $outerStyleOverride,
innerStyle: $innerStyleOverride,
detailStyle: $detailStyleOverride,
} = props
const {
theme: { colors },
themed,
} = useAppTheme()
const animate = useRef(new Animated.Value(on ? 1 : 0)) // Initial value is set based on isActive
const opacity = useRef(new Animated.Value(0))
useEffect(() => {
Animated.timing(animate.current, {
toValue: on ? 1 : 0,
duration: 300,
useNativeDriver: true, // Enable native driver for smoother animations
}).start()
}, [on])
useEffect(() => {
Animated.timing(opacity.current, {
toValue: on ? 1 : 0,
duration: 300,
useNativeDriver: true,
}).start()
}, [on])
const knobSizeFallback = 2
const knobWidth = [$detailStyleOverride?.width, $switchDetail?.width, knobSizeFallback].find(
(v) => typeof v === "number",
)
const knobHeight = [$detailStyleOverride?.height, $switchDetail?.height, knobSizeFallback].find(
(v) => typeof v === "number",
)
const offBackgroundColor = [
disabled && colors.palette.neutral400,
status === "error" && colors.errorBackground,
colors.palette.neutral300,
].filter(Boolean)[0]
const onBackgroundColor = [
disabled && colors.transparent,
status === "error" && colors.errorBackground,
colors.palette.secondary500,
].filter(Boolean)[0]
const knobBackgroundColor = (function () {
if (on) {
return [
$detailStyleOverride?.backgroundColor,
status === "error" && colors.error,
disabled && colors.palette.neutral600,
colors.palette.neutral100,
].filter(Boolean)[0]
} else {
return [
$innerStyleOverride?.backgroundColor,
disabled && colors.palette.neutral600,
status === "error" && colors.error,
colors.palette.neutral200,
].filter(Boolean)[0]
}
})()
const rtlAdjustment = isRTL ? -1 : 1
const $themedSwitchInner = useMemo(() => themed([$styles.toggleInner, $switchInner]), [themed])
const offsetLeft = ($innerStyleOverride?.paddingStart ||
$innerStyleOverride?.paddingLeft ||
$themedSwitchInner?.paddingStart ||
$themedSwitchInner?.paddingLeft ||
0) as number
const offsetRight = ($innerStyleOverride?.paddingEnd ||
$innerStyleOverride?.paddingRight ||
$themedSwitchInner?.paddingEnd ||
$themedSwitchInner?.paddingRight ||
0) as number
const outputRange =
Platform.OS === "web"
? isRTL
? [+(knobWidth || 0) + offsetRight, offsetLeft]
: [offsetLeft, +(knobWidth || 0) + offsetRight]
: [rtlAdjustment * offsetLeft, rtlAdjustment * (+(knobWidth || 0) + offsetRight)]
const $animatedSwitchKnob = animate.current.interpolate({
inputRange: [0, 1],
outputRange,
})
return (
<View style={[$inputOuter, { backgroundColor: offBackgroundColor }, $outerStyleOverride]}>
<Animated.View
style={[
$themedSwitchInner,
{ backgroundColor: onBackgroundColor },
$innerStyleOverride,
{ opacity: opacity.current },
]}
/>
<SwitchAccessibilityLabel {...props} role="on" />
<SwitchAccessibilityLabel {...props} role="off" />
<Animated.View
style={[
$switchDetail,
$detailStyleOverride,
{ transform: [{ translateX: $animatedSwitchKnob }] },
{ width: knobWidth, height: knobHeight },
{ backgroundColor: knobBackgroundColor },
]}
/>
</View>
)
}
/**
* @param {ToggleInputProps & { role: "on" | "off" }} props - The props for the `SwitchAccessibilityLabel` component.
* @returns {JSX.Element} The rendered `SwitchAccessibilityLabel` component.
*/
function SwitchAccessibilityLabel(props: SwitchInputProps & { role: "on" | "off" }) {
const { on, disabled, status, accessibilityMode, role, innerStyle, detailStyle } = props
const {
theme: { colors },
} = useAppTheme()
if (!accessibilityMode) return null
const shouldLabelBeVisible = (on && role === "on") || (!on && role === "off")
const $switchAccessibilityStyle: StyleProp<ViewStyle> = [
$switchAccessibility,
role === "off" && { end: "5%" },
role === "on" && { left: "5%" },
]
const color = (function () {
if (disabled) return colors.palette.neutral600
if (status === "error") return colors.error
if (!on) return innerStyle?.backgroundColor || colors.palette.secondary500
return detailStyle?.backgroundColor || colors.palette.neutral100
})()
return (
<View style={$switchAccessibilityStyle}>
{accessibilityMode === "text" && shouldLabelBeVisible && (
<View
style={[
role === "on" && $switchAccessibilityLine,
role === "on" && { backgroundColor: color },
role === "off" && $switchAccessibilityCircle,
role === "off" && { borderColor: color },
]}
/>
)}
{accessibilityMode === "icon" && shouldLabelBeVisible && (
<Image
style={[$switchAccessibilityIcon, { tintColor: color }]}
source={role === "off" ? iconRegistry.hidden : iconRegistry.view}
/>
)}
</View>
)
}
const $inputOuter: StyleProp<ViewStyle> = [
$inputOuterBase,
{ height: 32, width: 56, borderRadius: 16, borderWidth: 0 },
]
const $switchInner: ThemedStyle<ViewStyle> = ({ colors }) => ({
borderColor: colors.transparent,
position: "absolute",
paddingStart: 4,
paddingEnd: 4,
})
const $switchDetail: SwitchToggleProps["inputDetailStyle"] = {
borderRadius: 12,
position: "absolute",
width: 24,
height: 24,
}
const $switchAccessibility: ViewStyle = {
width: "40%",
justifyContent: "center",
alignItems: "center",
}
const $switchAccessibilityIcon: ImageStyle = {
width: 14,
height: 14,
resizeMode: "contain",
}
const $switchAccessibilityLine: ViewStyle = {
width: 2,
height: 12,
}
const $switchAccessibilityCircle: ViewStyle = {
borderWidth: 2,
width: 12,
height: 12,
borderRadius: 6,
}
================================================
FILE: boilerplate/app/components/Toggle/Toggle.tsx
================================================
import { ComponentType, FC, useMemo } from "react"
import {
GestureResponderEvent,
ImageStyle,
StyleProp,
SwitchProps,
TextInputProps,
TextStyle,
TouchableOpacity,
TouchableOpacityProps,
View,
ViewProps,
ViewStyle,
} from "react-native"
import type { ThemedStyle } from "@/theme/types"
import { useAppTheme } from "@/theme/context"
import { $styles } from "@/theme/styles"
import { Text, TextProps } from "../Text"
export interface ToggleProps<T> extends Omit<TouchableOpacityProps, "style"> {
/**
* A style modifier for different input states.
*/
status?: "error" | "disabled"
/**
* If false, input is not editable. The default value is true.
*/
editable?: TextInputProps["editable"]
/**
* The value of the field. If true the component will be turned on.
*/
value?: boolean
/**
* Invoked with the new value when the value changes.
*/
onValueChange?: SwitchProps["onValueChange"]
/**
* Style overrides for the container
*/
containerStyle?: StyleProp<ViewStyle>
/**
* Style overrides for the input wrapper
*/
inputWrapperStyle?: StyleProp<ViewStyle>
/**
* Optional input wrapper style override.
* This gives the inputs their size, shape, "off" background-color, and outer border.
*/
inputOuterStyle?: ViewStyle
/**
* Optional input style override.
* This gives the inputs their inner characteristics and "on" background-color.
*/
inputInnerStyle?: ViewStyle
/**
* Optional detail style override.
* See Checkbox, Radio, and Switch for more details
*/
inputDetailStyle?: ViewStyle
/**
* The position of the label relative to the action component.
* Default: right
*/
labelPosition?: "left" | "right"
/**
* The label text to display if not using `labelTx`.
*/
label?: TextProps["text"]
/**
* Label text which is looked up via i18n.
*/
labelTx?: TextProps["tx"]
/**
* Optional label options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
labelTxOptions?: TextProps["txOptions"]
/**
* Style overrides for label text.
*/
labelStyle?: StyleProp<TextStyle>
/**
* Pass any additional props directly to the label Text component.
*/
LabelTextProps?: TextProps
/**
* The helper text to display if not using `helperTx`.
*/
helper?: TextProps["text"]
/**
* Helper text which is looked up via i18n.
*/
helperTx?: TextProps["tx"]
/**
* Optional helper options to pass to i18n. Useful for interpolation
* as well as explicitly setting locale or translation fallbacks.
*/
helperTxOptions?: TextProps["txOptions"]
/**
* Pass any additional props directly to the helper Text component.
*/
HelperTextProps?: TextProps
/**
* The input control for the type of toggle component
*/
ToggleInput: FC<BaseToggleInputProps<T>>
}
export interface BaseToggleInputProps<T> {
on: boolean
status: ToggleProps<T>["status"]
disabled: boolean
outerStyle: ViewStyle
innerStyle: ViewStyle
detailStyle: ViewStyle | ImageStyle
}
/**
* 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.
* @param {ToggleProps} props - The props for the `Toggle` component.
* @returns {JSX.Element} The rendered `Toggle` component.
*/
export function Toggle<T>(props: ToggleProps<T>) {
const {
editable = true,
status,
value,
onPress,
onValueChange,
labelPosition = "right",
helper,
helperTx,
helperTxOptions,
HelperTextProps,
containerStyle: $containerStyleOverride,
inputWrapperStyle: $inputWrapperStyleOverride,
ToggleInput,
accessibilityRole,
...WrapperProps
} = props
const {
theme: { colors },
themed,
} = useAppTheme()
const disabled = editable === false || status === "disabled" || props.disabled
const Wrapper = useMemo(
() => (disabled ? View : TouchableOpacity) as ComponentType<TouchableOpacityProps | ViewProps>,
[disabled],
)
const $containerStyles = [$containerStyleOverride]
const $inputWrapperStyles = [$styles.row, $inputWrapper, $inputWrapperStyleOverride]
const $helperStyles = themed([
$helper,
status === "error" && { color: colors.error },
HelperTextProps?.style,
])
/**
* @param {GestureResponderEvent} e - The event object.
*/
function handlePress(e: GestureResponderEvent) {
if (disabled) return
onValueChange?.(!value)
onPress?.(e)
}
return (
<Wrapper
activeOpacity={1}
accessibilityRole={accessibilityRole}
accessibilityState={{ checked: value, disabled }}
{...WrapperProps}
style={$containerStyles}
onPress={handlePress}
>
<View style={$inputWrapperStyles}>
{labelPosition === "left" && <FieldLabel<T> {...props} labelPosition={labelPosition} />}
<ToggleInput
on={!!value}
disabled={!!disabled}
status={status}
outerStyle={props.inputOuterStyle ?? {}}
innerStyle={props.inputInnerStyle ?? {}}
detailStyle={props.inputDetailStyle ?? {}}
/>
{labelPosition === "right" && <FieldLabel<T> {...props} labelPosition={labelPosition} />}
</View>
{!!(helper || helperTx) && (
<Text
preset="formHelper"
text={helper}
tx={helperTx}
txOptions={helperTxOptions}
{...HelperTextProps}
style={$helperStyles}
/>
)}
</Wrapper>
)
}
/**
* @param {ToggleProps} props - The props for the `FieldLabel` component.
* @returns {JSX.Element} The rendered `FieldLabel` component.
*/
function FieldLabel<T>(props: ToggleProps<T>) {
const {
status,
label,
labelTx,
labelTxOptions,
LabelTextProps,
labelPosition,
labelStyle: $labelStyleOverride,
} = props
const {
theme: { colors },
themed,
} = useAppTheme()
if (!label && !labelTx && !LabelTextProps?.children) return null
const $labelStyle = themed([
$label,
status === "error" && { color: colors.error },
labelPosition === "right" && $labelRight,
labelPosition === "left" && $labelLeft,
$labelStyleOverride,
LabelTextProps?.style,
])
return (
<Text
preset="formLabel"
text={label}
tx={labelTx}
txOptions={labelTxOptions}
{...LabelTextProps}
style={$labelStyle}
/>
)
}
const $inputWrapper: ViewStyle = {
alignItems: "center",
}
export const $inputOuterBase: ViewStyle = {
height: 24,
width: 24,
borderWidth: 2,
alignItems: "center",
overflow: "hidden",
flexGrow: 0,
flexShrink: 0,
justifyContent: "space-between",
flexDirection: "row",
}
const $helper: ThemedStyle<TextStyle> = ({ spacing }) => ({
marginTop: spacing.xs,
})
const $label: TextStyle = {
flex: 1,
}
const $labelRight: ThemedStyle<TextStyle> = ({ spacing }) => ({
marginStart: spacing.md,
})
const $labelLeft: ThemedStyle<TextStyle> = ({ spacing }) => ({
marginEnd: spacing.md,
})
================================================
FILE: boilerplate/app/config/config.base.ts
================================================
export interface ConfigBaseProps {
persistNavigation: "always" | "dev" | "prod" | "never"
catchErrors: "always" | "dev" | "prod" | "never"
exitRoutes: string[]
}
export type PersistNavigationConfig = ConfigBaseProps["persistNavigation"]
const BaseConfig: ConfigBaseProps = {
// This feature is particularly useful in development mode, but
// can be used in production as well if you prefer.
persistNavigation: "dev",
/**
* Only enable if we're catching errors in the right environment
*/
catchErrors: "always",
/**
* This is a list of all the route names that will exit the app if the back button
* is pressed while in that screen. Only affects Android.
*/
exitRoutes: ["Welcome"],
}
export default BaseConfig
================================================
FILE: boilerplate/app/config/config.dev.ts
================================================
/**
* These are configuration settings for the dev environment.
*
* Do not include API secrets in this file or anywhere in your JS.
*
* https://reactnative.dev/docs/security#storing-sensitive-info
*/
export default {
API_URL: "https://api.rss2json.com/v1/",
}
================================================
FILE: boilerplate/app/config/config.prod.ts
================================================
/**
* These are configuration settings for the production environment.
*
* Do not include API secrets in this file or anywhere in your JS.
*
* https://reactnative.dev/docs/security#storing-sensitive-info
*/
export default {
API_URL: "https://api.rss2json.com/v1/",
}
================================================
FILE: boilerplate/app/config/index.ts
================================================
/**
* This file imports configuration objects from either the config.dev.js file
* or the config.prod.js file depending on whether we are in __DEV__ or not.
*
* Note 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.
*
* If 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.
*
* Read more here: https://reactnative.dev/docs/security#storing-sensitive-info
*/
import BaseConfig from "./config.base"
import DevConfig from "./config.dev"
import ProdConfig from "./config.prod"
let ExtraConfig = ProdConfig
if (__DEV__) {
ExtraConfig = DevConfig
}
const Config = { ...BaseConfig, ...ExtraConfig }
export default Config
================================================
FILE: boilerplate/app/context/AuthContext.tsx
================================================
import { createContext, FC, PropsWithChildren, useCallback, useContext, useMemo } from "react"
import { useMMKVString } from "react-native-mmkv"
export type AuthContextType = {
isAuthenticated: boolean
authToken?: string
authEmail?: string
setAuthToken: (token?: string) => void
setAuthEmail: (email: string) => void
logout: () => void
validationError: string
}
export const AuthContext = createContext<AuthContextType | null>(null)
export interface AuthProviderProps {}
export const AuthProvider: FC<PropsWithChildren<AuthProviderProps>> = ({ children }) => {
const [authToken, setAuthToken] = useMMKVString("AuthProvider.authToken")
const [authEmail, setAuthEmail] = useMMKVString("AuthProvider.authEmail")
const logout = useCallback(() => {
setAuthToken(undefined)
setAuthEmail("")
}, [setAuthEmail, setAuthToken])
const validationError = useMemo(() => {
if (!authEmail || authEmail.length === 0) return "can't be blank"
if (authEmail.length < 6) return "must be at least 6 characters"
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(authEmail)) return "must be a valid email address"
return ""
}, [authEmail])
const value = {
isAuthenticated: !!authToken,
authToken,
authEmail,
setAuthToken,
setAuthEmail,
logout,
validationError,
}
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
}
export const useAuth = () => {
const context = useContext(AuthContext)
if (!context) throw new Error("useAuth must be used within an AuthProvider")
return context
}
// @demo remove-file
================================================
FILE: boilerplate/app/context/EpisodeContext.tsx
================================================
import {
createContext,
FC,
PropsWithChildren,
useCallback,
useContext,
useMemo,
useState,
} from "react"
import { translate } from "@/i18n/translate"
import { api } from "@/services/api"
import type { EpisodeItem } from "@/services/api/types"
import { formatDate } from "@/utils/formatDate"
export type EpisodeContextType = {
totalEpisodes: number
totalFavorites: number
episodesForList: EpisodeItem[]
fetchEpisodes: () => Promise<void>
favoritesOnly: boolean
toggleFavoritesOnly: () => void
hasFavorite: (episode: EpisodeItem) => boolean
toggleFavorite: (episode: EpisodeItem) => void
}
export const EpisodeContext = createContext<EpisodeContextType | null>(null)
export interface EpisodeProviderProps {}
export const EpisodeProvider: FC<PropsWithChildren<EpisodeProviderProps>> = ({ children }) => {
const [episodes, setEpisodes] = useState<EpisodeItem[]>([])
const [favorites, setFavorites] = useState<string[]>([])
const [favoritesOnly, setFavoritesOnly] = useState<boolean>(false)
const fetchEpisodes = useCallback(async () => {
const response = await api.getEpisodes()
if (response.kind === "ok") {
setEpisodes(response.episodes)
} else {
console.error(`Error fetching episodes: ${JSON.stringify(response)}`)
}
}, [])
const toggleFavoritesOnly = useCallback(() => {
setFavoritesOnly((prev) => !prev)
}, [])
const toggleFavorite = useCallback(
(episode: EpisodeItem) => {
if (favorites.some((fav) => fav === episode.guid)) {
setFavorites((prev) => prev.filter((fav) => fav !== episode.guid))
} else {
setFavorites((prev) => [...prev, episode.guid])
}
},
[favorites],
)
const hasFavorite = useCallback(
(episode: EpisodeItem) => favorites.some((fav) => fav === episode.guid),
[favorites],
)
const episodesForList = useMemo(() => {
return favoritesOnly ? episodes.filter((episode) => favorites.includes(episode.guid)) : episodes
}, [episodes, favorites, favoritesOnly])
const value = {
totalEpisodes: episodes.length,
totalFavorites: favorites.length,
episodesForList,
fetchEpisodes,
favoritesOnly,
toggleFavoritesOnly,
hasFavorite,
toggleFavorite,
}
return <EpisodeContext.Provider value={value}>{children}</EpisodeContext.Provider>
}
export const useEpisodes = () => {
const context = useContext(EpisodeContext)
if (!context) throw new Error("useEpisodes must be used within an EpisodeProvider")
return context
}
// A helper hook to extract and format episode details
export const useEpisode = (episode: EpisodeItem) => {
const { hasFavorite } = useEpisodes()
const isFavorite = hasFavorite(episode)
let datePublished
try {
const formatted = formatDate(episode.pubDate)
datePublished = {
textLabel: formatted,
accessibilityLabel: translate("demoPodcastListScreen:accessibility.publishLabel", {
date: formatted,
}),
}
} catch {
datePublished = { textLabel: "", accessibilityLabel: "" }
}
const seconds = Number(episode.enclosure?.duration ?? 0)
const h = Math.floor(seconds / 3600)
const m = Math.floor((seconds % 3600) / 60)
const s = Math.floor((seconds % 3600) % 60)
const duration = {
textLabel: `${h > 0 ? `${h}:` : ""}${m > 0 ? `${m}:` : ""}${s}`,
accessibilityLabel: translate("demoPodcastListScreen:accessibility.durationLabel", {
hours: h,
minutes: m,
seconds: s,
}),
}
const trimmedTitle = episode.title?.trim()
const titleMatches = trimmedTitle?.match(/^(RNR.*\d)(?: - )(.*$)/)
const parsedTitleAndSubtitle =
titleMatches && titleMatches.length === 3
? { title: titleMatches[1], subtitle: titleMatches[2] }
: { title: trimmedTitle, subtitle: "" }
return {
isFavorite,
datePublished,
duration,
parsedTitleAndSubtitle,
}
}
// @demo remove-file
================================================
FILE: boilerplate/app/devtools/ReactotronClient.ts
================================================
/**
* This file is loaded in React Native and exports the RN version
* of Reactotron's client.
*
* Web is loaded from ReactotronClient.web.ts.
*/
import Reactotron from "reactotron-react-native"
export { Reactotron }
================================================
FILE: boilerplate/app/devtools/ReactotronClient.web.ts
================================================
/**
* This file is loaded in web and exports the React.js version
* of Reactotron's client.
*
* React Native is loaded from ReactotronClient.ts.
*
* If your project does not need web support, you can delete this file and
* remove reactotron-react-js from your package.json dependencies.
*/
import Reactotron from "reactotron-react-js"
export { Reactotron }
================================================
FILE: boilerplate/app/devtools/ReactotronConfig.ts
================================================
/**
* This file does the setup for integration with Reactotron, which is a
* free desktop app for inspecting and debugging your React Native app.
* @see https://github.com/infinitered/reactotron
*/
import { Platform, NativeModules } from "react-native"
import { ArgType } from "reactotron-core-client"
import { ReactotronReactNative } from "reactotron-react-native"
import mmkvPlugin from "reactotron-react-native-mmkv"
import { goBack, resetRoot, navigate } from "@/navigators/navigationUtilities"
import { storage } from "@/utils/storage"
import { Reactotron } from "./ReactotronClient"
const reactotron = Reactotron.configure({
name: require("../../package.json").name,
onConnect: () => {
/** since this file gets hot reloaded, let's clear the past logs every time we connect */
Reactotron.clear()
},
})
reactotron.use(mmkvPlugin<ReactotronReactNative>({ storage }))
if (Platform.OS !== "web") {
reactotron.useReactNative({
networking: {
ignoreUrls: /symbolicate/,
},
})
}
/**
* Reactotron allows you to define custom commands that you can run
* from Reactotron itself, and they will run in your app.
*
* Define them in the section below with `onCustomCommand`. Use your
* creativity -- this is great for development to quickly and easily
* get your app into the state you want.
*
* NOTE: If you edit this file while running the app, you will need to do a full refresh
* or else your custom commands won't be registered correctly.
*/
reactotron.onCustomCommand({
title: "Show Dev Menu",
description: "Opens the React Native dev menu",
command: "showDevMenu",
handler: () => {
Reactotron.log("Showing React Native dev menu")
NativeModules.DevMenu.show()
},
})
reactotron.onCustomCommand({
title: "Reset Navigation State",
description: "Resets the navigation state",
command: "resetNavigation",
handler: () => {
Reactotron.log("resetting navigation state")
resetRoot({ index: 0, routes: [] })
},
})
reactotron.onCustomCommand<[{ name: "route"; type: ArgType.String }]>({
command: "navigateTo",
handler: (args) => {
const { route } = args ?? {}
if (route) {
Reactotron.log(`Navigating to: ${route}`)
// @ts-ignore
navigate(route as any) // this should be tied to the navigator, but since this is for debugging, we can navigate to illegal routes
} else {
Reactotron.log("Could not navigate. No route provided.")
}
},
title: "Navigate To Screen",
description: "Navigates to a screen by name.",
args: [{ name: "route", type: ArgType.String }],
})
reactotron.onCustomCommand({
title: "Go Back",
description: "Goes back",
command: "goBack",
handler: () => {
Reactotron.log("Going back")
goBack()
},
})
/**
* We're going to add `console.tron` to the Reactotron object.
* Now, anywhere in our app in development, we can use Reactotron like so:
*
* ```
* if (__DEV__) {
* console.tron.display({
* name: 'JOKE',
* preview: 'What's the best thing about Switzerland?',
* value: 'I don't know, but the flag is a big plus!',
* important: true
* })
* }
* ```
*
* Use this power responsibly! :)
*/
console.tron = reactotron
/**
* We tell typescript about our dark magic
*
* You can also import Reactotron yourself from ./reactotronClient
* and use it directly, like Reactotron.log('hello world')
*/
declare global {
interface Console {
/**
* Reactotron client for logging, displaying, measuring performance, and more.
* @see https://github.com/infinitered/reactotron
* @example
* if (__DEV__) {
* console.tron.display({
* name: 'JOKE',
* preview: 'What's the best thing about Switzerland?',
* value: 'I don't know, but the flag is a big plus!',
* important: true
* })
* }
*/
tron: typeof reactotron
}
}
/**
* Now that we've setup all our Reactotron configuration, let's connect!
*/
reactotron.connect()
================================================
FILE: boilerplate/app/i18n/ar.ts
================================================
import demoAr from "./demo-ar" // @demo remove-current-line
import { Translations } from "./en"
const ar: Translations = {
common: {
ok: "نعم",
cancel: "حذف",
back: "خلف",
logOut: "تسجيل خروج", // @demo remove-current-line
},
welcomeScreen: {
postscript:
"ربما لا يكون هذا هو الشكل الذي يبدو عليه تطبيقك مالم يمنحك المصمم هذه الشاشات وشحنها في هذه الحالة",
readyForLaunch: "تطبيقك تقريبا جاهز للتشغيل",
exciting: "اوه هذا مثير",
letsGo: "لنذهب", // @demo remove-current-line
},
errorScreen: {
title: "هناك خطأ ما",
friendlySubtitle:
"هذه هي الشاشة التي سيشاهدها المستخدمون في عملية الانتاج عند حدوث خطأ. سترغب في تخصيص هذه الرسالة ( الموجودة في 'ts.en/i18n/app') وربما التخطيط ايضاً ('app/screens/ErrorScreen'). إذا كنت تريد إزالة هذا بالكامل، تحقق من 'app/app.tsp' من اجل عنصر <ErrorBoundary>.",
reset: "اعادة تعيين التطبيق",
traceTitle: "خطأ من مجموعة %{name}", // @demo remove-current-line
},
emptyStateComponent: {
generic: {
heading: "فارغة جداً....حزين",
content: "لا توجد بيانات حتى الآن. حاول النقر فوق الزر لتحديث التطبيق او اعادة تحميله.",
button: "لنحاول هذا مرّة أخرى",
},
},
// @demo remove-block-start
errors: {
invalidEmail: "عنوان البريد الالكتروني غير صالح",
},
loginScreen: {
logIn: "تسجيل الدخول",
enterDetails:
".ادخل التفاصيل الخاصة بك ادناه لفتح معلومات سرية للغاية. لن تخمن ابداً ما الذي ننتظره. او ربما ستفعل انها انها ليست علم الصواريخ",
emailFieldLabel: "البريد الالكتروني",
passwordFieldLabel: "كلمة السر",
emailFieldPlaceholder: "ادخل بريدك الالكتروني",
passwordFieldPlaceholder: "كلمة السر هنا فائقة السر",
tapToLogIn: "انقر لتسجيل الدخول!",
hint: "(: تلميح: يمكنك استخدام اي عنوان بريد الكتروني وكلمة السر المفضلة لديك",
},
demoNavigator: {
componentsTab: "عناصر",
debugTab: "تصحيح",
communityTab: "واصل اجتماعي",
podcastListTab: "البودكاست",
},
demoCommunityScreen: {
title: "تواصل مع المجتمع",
tagLine:
"قم بالتوصيل لمنتدى Infinite Red الذي يضم تفاعل المهندسين المحلّيين ورفع مستوى تطوير تطبيقك معنا",
joinUsOnSlackTitle: "انضم الينا على Slack",
joinUsOnSlack:
"هل ترغب في وجود مكان للتواصل مع مهندسي React Native حول العالم؟ الانضمام الى المحادثة في سلاك المجتمع الاحمر اللانهائي! مجتمعناالمتنامي هو مساحةآمنة لطرح الاسئلة والتعلم من الآخرين وتنمية شبكتك.",
joinSlackLink: "انضم الي مجتمع Slack",
makeIgniteEvenBetterTitle: "اجعل Ignite افضل",
makeIgniteEvenBetter:
"هل لديك فكرة لجعل Ignite افضل؟ نحن سعداء لسماع ذلك! نحن نبحث دائماً عن الآخرين الذين يرغبون في مساعدتنا في بناء افضل الادوات المحلية التفاعلية المتوفرة هناك. انضم الينا عبر GitHub للانضمام الينا في بناء مستقبل Ignite",
contributeToIgniteLink: "ساهم في Ignite",
theLatestInReactNativeTitle: "الاحدث في React Native",
theLatestInReactNative: "نخن هنا لنبقيك محدثاً على جميع React Native التي تعرضها",
reactNativeRadioLink: "راديو React Native",
reactNativeNewsletterLink: "نشرة اخبار React Native",
reactNativeLiveLink: "مباشر React Native",
chainReactConferenceLink: "مؤتمر Chain React",
hireUsTitle: "قم بتوظيف Infinite Red لمشروعك القادم",
hireUs:
"سواء كان الامر يتعلّق بتشغيل مشروع كامل او اعداد الفرق بسرعة من خلال التدريب العلمي لدينا، يمكن ان يساعد Infinite Red اللامتناهي في اي مشروع محلي يتفاعل معه.",
hireUsLink: "ارسل لنا رسالة",
},
demoShowroomScreen: {
jumpStart: "مكونات او عناصر لبدء مشروعك",
lorem2Sentences:
"عامل الناس بأخلاقك لا بأخلاقهم. عامل الناس بأخلاقك لا بأخلاقهم. عامل الناس بأخلاقك لا بأخلاقهم",
demoHeaderTxExample: "ياي",
demoViaTxProp: "عبر `tx` Prop",
demoViaSpecifiedTxProp: "Prop `{{prop}}Tx` عبر",
},
demoDebugScreen: {
howTo: "كيف",
title: "التصحيح",
tagLine: "مبروك، لديك نموذج اصلي متقدم للغاية للتفاعل هنا. الاستفادة من هذه النمذجة",
reactotron: "Reactotron ارسل إلى",
reportBugs: "الابلاغ عن اخطاء",
demoList: "قائمة تجريبية",
demoPodcastList: "قائمة البودكاست التجريبي",
androidReactotronHint:
"اذا لم ينجح ذللك، فتأكد من تشغيل تطبيق الحاسوب الخاص Reactotron، وقم بتشغيل عكس adb tcp:9090 \ntcp:9090 من جهازك الطرفي ، واعد تحميل التطبيق",
iosReactotronHint:
"اذا لم ينجح ذلك، فتأكد من تشغيل تطبيق الحاسوب الخاص ب Reactotron وأعد تحميل التطبيق",
macosReactotronHint: "اذا لم ينجح ذلك، فتأكد من تشغيل الحاسوب ب Reactotron وأعد تحميل التطبيق",
webReactotronHint: "اذا لم ينجح ذلك، فتأكد من تشغيل الحاسوب ب Reactotron وأعد تحميل التطبيق",
windowsReactotronHint:
"اذا لم ينجح ذلك، فتأكد من تشغيل الحاسوب ب Reactotron وأعد تحميل التطبيق",
},
demoPodcastListScreen: {
title: "حلقات إذاعية React Native",
onlyFavorites: "المفضلة فقط",
favoriteButton: "المفضل",
unfavoriteButton: "غير مفضل",
accessibility: {
cardHint: "انقر مرّتين للاستماع على الحلقة. انقر مرّتين وانتظر لتفعيل {{action}} هذه الحلقة.",
switch: "قم بالتبديل لاظهار المفضّلة فقط.",
favoriteAction: "تبديل المفضلة",
favoriteIcon: "الحلقة الغير مفضّلة",
unfavoriteIcon: "الحلقة المفضّلة",
publishLabel: "نشرت {{date}}",
durationLabel: "المدّة: {{hours}} ساعات {{minutes}} دقائق {{seconds}} ثواني",
},
noFavoritesEmptyState: {
heading: "هذا يبدو فارغاً بعض الشيء.",
content:
"لم تتم اضافة اي مفضلات حتى الان. اضغط على القلب في إحدى الحلقات لإضافته الى المفضلة.",
},
},
// @demo remove-block-start
...demoAr,
// @demo remove-block-end
}
export default ar
================================================
FILE: boilerplate/app/i18n/demo-ar.ts
================================================
import { DemoTranslations } from "./demo-en"
export const demoAr: DemoTranslations = {
demoIcon: {
description:
"مكون لعرض أيقونة مسجلة.يتم تغليفه في <TouchableOpacity> يتم توفير 'OnPress'، وإلا يتم توفير <View",
useCase: {
icons: {
name: "Icons",
description: "قائمة الرموز المسجلة داخل المكون.",
},
size: {
name: "Size",
description: "هناك حجم الدعامة.",
},
color: {
name: "لون",
description: "هناك لون الدعامة.",
},
styling: {
name: "التصميم",
description: "يمكن تصميم المكون بسهولة.",
},
},
},
demoTextField: {
description: "TextField يسمح المكون بإدخال النص وتحريره.",
useCase: {
statuses: {
name: "الحالات",
description:
"هناك حالة مماثلة ل 'preset' في المكونات الأخرى، ولكنها تؤثر على وظيفة المكون ايضاً.",
noStatus: {
label: "لا يوجد حالات",
helper: "هذه هي الحالة الافتراضية",
placeholder: "النص يذهب هنا",
},
error: {
label: "حالة الخطأ",
helper: "الحالة التي يجب استخدامها عند وجود خطأ",
placeholder: "النص يذهب هنا",
},
disabled: {
label: "حالة الإعاقة",
helper: "يعطل إمكانية التحرير ويكتم النص",
placeholder: "النص يذهب هنا",
},
},
passingContent: {
name: "محتوى عابر",
description: "هناك عدة طرق مختلفة لتمرير المحتوى",
viaLabel: {
labelTx: "عبر 'label' الدعامة",
helper: "عبر 'helper' الدعامة",
placeholder: "عبر 'placeholder' الدعامة",
},
rightAccessory: {
label: "RightAccessory",
helper: "هذه الدعامة تأخذ دالة تقوم بإرجاع عنصر React",
},
leftAccessory: {
label: "LeftAccessory",
helper: "هذه الدعامة تأخذ دالة تقوم بإرجاع عنصر React",
},
supportsMultiline: {
label: "يدعم Multiline",
helper: "يتيح إدخالا اطول للنص متعدد الأسطر.",
},
},
styling: {
name: "التصميم",
description: "يمكن تصميم المكون بسهولة",
styleInput: {
label: "أسلوب الإدخال",
helper: "عبر دعامة 'Style'",
},
styleInputWrapper: {
label: "غلاف ادخال النمط",
helper: "عبر دعامة 'InputWrapperStyle'",
},
styleContainer: {
label: "حاوية النمط",
helper: "عبر دعامة 'containerstyle'",
},
styleLabel: {
label: "تسمية النمط والمساعد",
helper: "عبر أسلوب الدعامة 'LabelTextProps' & 'HelperTextProps'",
},
styleAccessories: {
label: "اكسسورات الاناقة",
helper: "عبر أسلوب الدعامة 'RightAccessory' & 'LeftAccessory'",
},
},
},
},
demoToggle: {
description:
"يقوم بعرض ادخال منطقي.هذا مكون خاضع للتحكم ويتطلب استدعاء OnValueChanger الذي يقوم بتحديث خاصية القيمة حتى يعكس المكون إجراءات المستخدم. إذا لم يتم تحديث خاصية القيمة، فسيستمر المكون في عرض خاصية القيمة المقدمة بدلا من النتيجة المتوقعة لأي إجراءات مستخدم.",
useCase: {
variants: {
name: "المتغيرات",
description:
"تدعم المكونات عددا قليلا من المتغيرات المختلفة. اذا كانت هناك حاجة إلى تخصيص كبير لمتغير معين، فيمكن إعادة صياغته بسهولة. الافتراضي هو 'checkbox'",
checkbox: {
label: "'checkbox' متغير",
helper: "يمكن استخدامه كمدخل تشغيل \\ إيقاف واحد",
},
radio: {
label: "'radio' متغير",
helper: "استخدام هذا عندما يكون لديك خيارات متعددة",
},
switch: {
label: "'switch' متغير",
helper: "مدخل تشغيل/إيقاف أكثر بروزا. يتمتع بدعم إمكانية الوصول بشكل أفضل.",
},
},
statuses: {
name: "الحالات",
description:
"هناك دعامة حالة مشابهة ل 'preset' في المكونات الأخرى، لكنها تؤثر على وظائف المكونات ايضاً",
noStatus: "لا توجد حالات- هذا هو الوضع الافتراضي",
errorStatus: "حالة الخطأ - استخدمها عندما يكون هناك خطأ",
disabledStatus: "حالة معطلة- تعطيل إمكانية التحرير وكتم صوت الإدخال",
},
passingContent: {
name: "محتوى عابر",
description: "هناك عدة طرق مختلفة لتمرير المحتوى",
useCase: {
checkBox: {
label: "عبر دعامة 'labelTx'",
helper: "عبر دعامة 'helpertx'",
},
checkBoxMultiLine: {
helper: "يدعم خطوط متعددة-Nulla provident consectetur labore sunt ea labore ",
},
radioChangeSides: {
helper: "يمكنك تغيير الجانبين - Laborum labore adipisicing in eu ipsum deserunt.",
},
customCheckBox: {
label: "مرر أيقونة مربع الاختيار المخصص",
},
switch: {
label: "يمكن قراءة المفاتيح كنص",
helper:
"بشكل افتراضي، لا يستخدم هذا الخيار \"text' نظرا لأنه اعتمادا على الخط، قد تبدو الأحرف التي يتم تشغيلها/ايقافها غريبة. قم بالتخصيص حسب الحاجة",
},
switchAid: {
label: "او بمساعدة أيقونة",
},
},
},
styling: {
name: "التصميم",
description: "يمكن تصميم المكون بسهولة",
outerWrapper: "١- تصميم الغلاف الخارجي للإدخال",
innerWrapper: "٢- تصميم الغلاف الداخلي للإدخال",
inputDetail: "٣- تصميم تفاصيل الإدخال",
labelTx: "يمكنك ايضاً تصميم الملصق labelTx",
styleContainer: "او، قم بتصميم الحاوية بأكملها",
},
},
},
demoButton: {
description:
"مكون يسمح للمستخدمين بإتخاذ الإجراءات والاختيارات. يلف مكون النص بمكون قابل للضغط",
useCase: {
presets: {
name: "الإعدادات المسبقة",
description: "هناك عدد قليل من الإعدادات المسبقة التي تم تكوينها مسبقاً",
},
passingContent: {
name: "محتوى عابر",
description: "هناك عدة طرق مختلفة لتمرير المحتوى",
viaTextProps: "عبر الدعامة 'text'- Billum In",
children: "أولاد- Irure Reprehenderit",
rightAccessory: "RightAccessory - Duis Quis",
leftAccessory: "LeftAccessory - Duis Proident",
nestedChildren: "الأطفال المتداخلون-\tprovident genial",
nestedChildren2: "Ullamco cupidatat officia exercitation velit non ullamco nisi..",
nestedChildren3: "Occaecat aliqua irure proident veniam.",
multiLine:
"Multiline - consequat veniam veniam reprehenderit. Fugiat id nisi quis duis sunt proident mollit dolor mollit adipisicing proident deserunt.",
},
styling: {
name: "التصميم",
description: "يمكن تصميم المكون بسهولة",
styleContainer: "حاوية الأسلوب- الإثارة",
styleText: "نص النمط- ِEa Anim",
styleAccessories: "اكسسوارات الاناقة - enim ea id fugiat anim ad.",
pressedState: "نمط الحالة المضغوطة - fugiat anim",
},
disabling: {
name: "تعطيل",
description: "يمكن تعطيل المكون، وتصميمه بناء على ذلك. سيتم تعطيل سلوك الضغط",
standard: "إبطال - معيار",
filled: "إبطال - مملوء",
reversed: "إبطال- معكوس",
accessory: "نمط الملحق المعطل",
textStyle: "نمط النص المعطل",
},
},
},
demoListItem: {
description: "مكون صف مصمم يمكن استخدامه في FlatList او SectionList او بمفرده",
useCase: {
height: {
name: "علو",
description: "يمكن ان يكون الصف بارتفاعات مختلفة",
defaultHeight: "الارتفاع الافتراضي (56px)",
customHeight: "ارتفاع مخصص عبر دعامة 'height'",
textHeight:
"الارتفاع يتم تحديده من خلال محتوى النص - Reprehenderit incididunt deserunt do do ea labore.",
longText: "تحديد النص إلى سطر واحد - Reprehenderit incididunt deserunt do do ea labore.",
},
separators: {
name: "الفواصل",
description: "الفاصل/ المقسم مهيّأ مسبقاً وهو اختياري",
topSeparator: "فقط فاصل علوي",
topAndBottomSeparator: "الفواصل العلوية والسفلية",
bottomSeparator: "فقط فاصل سفلي",
},
icons: {
name: "الأيقونات",
description: "يمكنك تخصيص الرموز على اليسار أو اليمين",
leftIcon: "أيقونة اليسار",
rightIcon: "أيقونة اليمين",
leftRightIcons: "أيقونة اليمين واليسار",
},
customLeftRight: {
name: "مكونات مخصصة لليسار /اليمين",
description: "اذا كنت بحاجة إلى مخصص لليسار/اليمين فيمكنك تمريره",
customLeft: "مكون يسار مخصص",
customRight: "مكون يمين مخصص",
},
passingContent: {
name: "محتوى عابر",
description: "هناك عدة طرق مختلفة لتمرير المحتوى",
text: "عبر دعامة 'text' - reprehenderit sint",
children: "أولاد- mostrud mollit",
nestedChildren1: "الأولاد المتداخلون - proident veniam.",
nestedChildren2: "Ullamco cupidatat officia exercitation velit non ullamco nisi..",
},
listIntegration: {
name: "دمج مع/ FlatList",
description: "يمكن دمج المكون بسهولة مع واجهة القائمة المفضلة لديك",
},
styling: {
name: "التصميم",
description: "يمكن تصميم المكون بسهولة.",
styledText: "نص مصمم",
styledContainer: "حاوية مصممة (فواصل)",
tintedIcons: "أيقونات ملونة",
},
},
},
demoCard: {
description:
"البطاقات مفيدة لعرض المعلومات ذات الصلة بطريقة محددة. اذا كان ListItem يعرض المحتوى أفقياً، فيمكن استخدام البطاقة لعرض المحتوى رأسياً.",
useCase: {
presets: {
name: "الإعدادات المسبقة",
description: "هناك عدد قليل من الإعدادات المسبقة التي تم تكوينها مسبقاً",
default: {
heading: "الأعداد المسبق الافتراضي ( تقصير)",
content: "Incididunt magna ut aliquip consectetur mollit dolor.",
footer: "Consectetur nulla non aliquip velit.",
},
reversed: {
heading: "الأعداد المسبق المعكوس",
content: "Reprehenderit occaecat proident amet id laboris.",
footer: "Consectetur tempor ea non labore anim .",
},
},
verticalAlignment: {
name: "انحياز عمودي",
description:
"اعتمادا على ما هو مطلوب، تأتي البطاقة مهيأة مسبقاً باستراتيجيات محاذاة مختلفة",
top: {
heading: "قمة (تقصير)",
content: "يتم محاذاة كل محتوى تلقائياً إلى الأعلى",
footer: "حتى التذييل",
},
center: {
heading: "مركز",
content: "يتم تركيز المحتوى بالنسبة لارتفاع البطاقة",
footer: "أنا ايضاً!",
},
spaceBetween: {
heading: "مسافة بين الكلمات",
content: "يتم توزيع جميع المحتويات بالتساوي",
footer: "أنا حيث أريد ان أكون",
},
reversed: {
heading: "Force Footer Bottom",
content: "يؤدي هذا إلى دفع التذييل إلى المكان الذي ينتمي اليه.",
footer: "أنا وحد جداًهنا",
},
},
passingContent: {
name: "محتوى عابر",
description: "هناك عدة طرق مختلفة لتمرير المحتوى.",
heading: "عبر دعم 'heading'",
content: "عبر دعم 'content'",
footer: "أنا وحيد هنا.",
},
customComponent: {
name: "مكونات مخصصة",
description:
"يمكن استبدال اي من المكونات المعدة مسبقاً بمكوناتك الخاصة. يمكنك ايضاً اضافة مكونات إضافية.",
rightComponent: "RightComponent",
leftComponent: "LeftComponent",
},
style: {
name: "التصميم",
description: "يمكن تصميم المكون بسهولة.",
heading: "صمم العنوان",
content: "صمم المحتوى",
footer: "صمم التذييل",
},
},
},
demoAutoImage: {
description: "مكون صورة يحدد حجم الصورة البعيدة او صورة data-uri",
useCase: {
remoteUri: {
name: "عن بعد URI",
},
base64Uri: {
name: "Base64 URI",
},
scaledToFitDimensions: {
name: "تم قياسها لتناسب الأبعاد",
description:
" توفيرعرض 'maxWidth' و\\او 'maxHeight' ، سيتم عرض الصورة بنسبة عرض الى ارتفاع. كيف يختلف هذا عن 'resizeMode': 'contain'? اولاً،يمكنك تحديد حجم جانب واحد فقط. (ليس كلاهما). ثانياً، سيتم تغيير الصورة لتناسب الأبعاد المطلوبة بدلاً من مجرد احتوائها داخل حاوية الصورة الخاصة بها.",
heightAuto: " عرض : ٦٠ / طول: auto",
widthAuto: "عرض: auto / طول: ٣٢",
bothManual: "عرض :٦٠ / طول : ٦٠",
},
},
},
demoText: {
description:
"لتلبية احتياجاتك في عرض النصوص. هذا المكون عبارة عن HOC فوق المكون المدمج Native React.",
useCase: {
presets: {
name: "الإعدادات المسبقة",
description: "هناك عدد قليل من الإعدادات المسبقة التي تم تكوينها مسبقاً.",
default:
"الأعداد المسبق الافتراضي - Cillum eu laboris in labore. Excepteur mollit tempor reprehenderit fugiat elit et eu consequat laborum.",
bold: "bold preset - Tempor et ullamco cupidatat in officia. Nulla ea duis elit id sunt ipsum cillum duis deserunt nostrud ut nostrud id.",
subheading: "subheading preset - In Cupidatat Cillum.",
heading: "heading preset - Voluptate Adipis.",
},
sizes: {
name: "قياسات",
description: "هناك حجم الدعامة",
xs: "xs - Ea ipsum est ea ex sunt.",
sm: "sm - Lorem sunt adipisicin.",
md: "md - Consequat id do lorem.",
lg: "lg - Nostrud ipsum ea.",
xl: "xl - Eiusmod ex excepteur.",
xxl: "xxl - Cillum eu laboris.",
},
weights: {
name: "أوزان",
description: "هناك وزن الدعامة",
light:
"light - Nulla magna incididunt excepteur est occaecat duis culpa dolore cupidatat enim et.",
normal:
"normal - Magna incididunt dolor ut veniam veniam laboris aliqua velit ea incididunt.",
medium: "medium - Non duis laborum quis laboris occaecat culpa cillum.",
semibold: "semiBold - Exercitation magna nostrud pariatur laborum occaecat aliqua.",
bold: "bold - Eiusmod ullamco magna exercitation est excepteur.",
},
passingContent: {
name: "محتوى عابر",
description: "هناك عدة طرق مختلفة لتمرير المحتوى.",
viaText:
"via `text` prop - Billum in aute fugiat proident nisi pariatur est. Cupidatat anim cillum eiusmod ad. Officia eu magna aliquip labore dolore consequat.",
viaTx: "عبر دعامة 'tx'",
children: "childrenreprehenderit eu qui amet veniam consectetur.",
nestedChildren: "الأطفال المتداخلون",
nestedChildren2: "Occaecat aliqua irure proident veniam.",
nestedChildren3: "Ullamco cupidatat officia exercitation velit non ullamco nisi..",
nestedChildren4: "Occaecat aliqua irure proident veniam.",
},
styling: {
name: "التصميم",
description: "يمكن تصميم المكون بسهولة.",
text: "Consequat ullamco veniam velit mollit proident excepteur aliquip id culpa ipsum velit sint nostrud.",
text2:
"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.",
text3:
"Eiusmod occaecat laboris eu ex veniam ipsum adipisicing consectetur. Magna ullamco adipisicing tempor adipisicing.",
},
},
},
demoHeader: {
description: "المكون الذي يظهر على العديد من الشاشات، سيحمل ازرار التنقل وعنوان الشاشة.",
useCase: {
actionIcons: {
name: "أيقونة الإجرائات ",
description: "يمكنك بسهولة تمرير الرموزالى مكونات الاجراء اليسرى او اليمنى.",
leftIconTitle: "الرمز الأيسر",
rightIconTitle: "الرمز الأيمن ",
bothIconsTitle: "كلا الرمزين",
},
actionText: {
name: "نص العمل",
description: "يمكنك بسهولة تمرير النص الى مكونات الاجراء اليسرى او اليمنى.",
leftTxTitle: "عبر 'leftTx' ",
rightTextTitle: "عبر `rightText`",
},
customActionComponents: {
name: "مكونات الاجراء المخصص",
description:
"اذا لم تكن خيارات الرمز او النسكافية، فيمكنك تمرير مكون الاجراء المخصص الخاص بك.",
customLeftActionTitle: "عمل يسار مخصص ",
},
titleModes: {
name: "اوضاع العنوان",
description:
"يمكن اجبار العنوان على البقاء غي المنتصف ولكن قد يتم قطعه اذا كان طويلاً للغاية. يمكنك بشكل اختياري تعديله وفقاً لأزرار الإجراء.",
centeredTitle: "عنوان مركزي",
flexTitle: "عنوان مرن",
},
styling: {
name: "التصميم",
description: "يمكن تصميم المكون بسهولة",
styledTitle: "عنوان مصمم",
styledWrapperTitle: "غلاف مصمم",
tintedIconsTitle: "أيقونات ملونة",
},
},
},
demoEmptyState: {
description:
"مكون يتم استخدامه عندما لا يكون هناك بيانات لعرضها. ويمكن استخدامه لتوجيه المستخدم الى ما يجب فعله بعد ذلك.",
useCase: {
presets: {
name: "الإعدادات المسبقة",
description:
"يمكن إنشاء نص/صورة مختلفة مجموعات. واحد محدد مسبقاً يسمى 'generic'. لاحظ انه لا يوجد اي خيار افتراضي في حال رغبتك في الحصول على كامل EmptyState مخصصة.",
},
passingContent: {
name: "محتوى عابر",
description: "هناك عدة طرق مختلفة لتمرير المحتوى.",
customizeImageHeading: "تخصيص الصورة",
customizeImageContent: "يمكنك تمرير اي مصدر للصورة",
viaHeadingProp: "عبر دعامة 'heading'",
viaContentProp: "عبر دعامة 'content'",
viaButtonProp: "عبر دعامة 'button'",
},
styling: {
name: "التصميم",
description: "يمكن تصميم المكون بسهولة.",
},
},
},
}
export default demoAr
// @demo remove-file
================================================
FILE: boilerplate/app/i18n/demo-en.ts
================================================
export const demoEn = {
demoIcon: {
description:
"A component to render a registered icon. It is wrapped in a <TouchableOpacity /> if `onPress` is provided, otherwise a <View />.",
useCase: {
icons: {
name: "Icons",
description: "List of icons registered inside the component.",
},
size: {
name: "Size",
description: "There's a size prop.",
},
color: {
name: "Color",
description: "There's a color prop.",
},
styling: {
name: "Styling",
description: "The component can be styled easily.",
},
},
},
demoTextField: {
description: "TextField component allows for the entering and editing of text.",
useCase: {
statuses: {
name: "Statuses",
description:
"There is a status prop - similar to `preset` in other components, but affects component functionality as well.",
noStatus: {
label: "No Status",
helper: "This is the default status",
placeholder: "Text goes here",
},
error: {
label: "Error Status",
helper: "Status to use when there is an error",
placeholder: "Text goes here",
},
disabled: {
label: "Disabled Status",
helper: "Disables the editability and mutes text",
placeholder: "Text goes here",
},
},
passingContent: {
name: "Passing Content",
description: "There are a few different ways to pass content.",
viaLabel: {
labelTx: "Via `label` prop",
helper: "Via `helper` prop",
placeholder: "Via `placeholder` prop",
},
rightAccessory: {
label: "RightAccessory",
helper: "This prop takes a function that returns a React element.",
},
leftAccessory: {
label: "LeftAccessory",
helper: "This prop takes a function that returns a React element.",
},
supportsMultiline: {
label: "Supports Multiline",
helper: "Enables a taller input for multiline text.",
},
},
styling: {
name: "Styling",
description: "The component can be styled easily.",
styleInput: {
label: "Style Input",
helper: "Via `style` prop",
},
styleInputWrapper: {
label: "Style Input Wrapper",
helper: "Via `inputWrapperStyle` prop",
},
styleContainer: {
label: "Style Container",
helper: "Via `containerStyle` prop",
},
styleLabel: {
label: "Style Label & Helper",
helper: "Via `LabelTextProps` & `HelperTextProps` style prop",
},
styleAccessories: {
label: "Style Accessories",
helper: "Via `RightAccessory` & `LeftAccessory` style prop",
},
},
},
},
demoToggle: {
description:
"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.",
useCase: {
variants: {
name: "Variants",
description:
"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`.",
checkbox: {
label: "`checkbox` variant",
helper: "This can be used for a single on/off input.",
},
radio: {
label: "`radio` variant",
helper: "Use this when you have multiple options.",
},
switch: {
label: "`switch` variant",
helper: "A more prominent on/off input. Has better accessibility support.",
},
},
statuses: {
name: "Statuses",
description:
"There is a status prop - similar to `preset` in other components, but affects component functionality as well.",
noStatus: "No status - this is the default",
errorStatus: "Error status - use when there is an error",
disabledStatus: "Disabled status - disables the editability and mutes input",
},
passingContent: {
name: "Passing Content",
description: "There are a few different ways to pass content.",
useCase: {
checkBox: {
label: "Via `labelTx` prop",
helper: "Via `helperTx` prop.",
},
checkBoxMultiLine: {
helper: "Supports multiline - Nulla proident consectetur labore sunt ea labore. ",
},
radioChangeSides: {
helper: "You can change sides - Laborum labore adipisicing in eu ipsum deserunt.",
},
customCheckBox: {
label: "Pass in a custom checkbox icon.",
},
switch: {
label: "Switches can be read as text",
helper:
"By default, this option doesn't use `Text` since depending on the font, the on/off characters might look weird. Customize as needed.",
},
switchAid: {
label: "Or aided with an icon",
},
},
},
styling: {
name: "Styling",
description: "The component can be styled easily.",
outerWrapper: "1 - style the input outer wrapper",
innerWrapper: "2 - style the input inner wrapper",
inputDetail: "3 - style the input detail",
labelTx: "You can also style the labelTx",
styleContainer: "Or, style the entire container",
},
},
},
demoButton: {
description:
"A component that allows users to take actions and make choices. Wraps the Text component with a Pressable component.",
useCase: {
presets: {
name: "Presets",
description: "There are a few presets that are preconfigured.",
},
passingContent: {
name: "Passing Content",
description: "There are a few different ways to pass content.",
viaTextProps: "Via `text` Prop - Billum In",
children: "Children - Irure Reprehenderit",
rightAccessory: "RightAccessory - Duis Quis",
leftAccessory: "LeftAccessory - Duis Proident",
nestedChildren: "Nested children - proident veniam.",
nestedChildren2: "Ullamco cupidatat officia exercitation velit non ullamco nisi..",
nestedChildren3: "Occaecat aliqua irure proident veniam.",
multiLine:
"Multiline - consequat veniam veniam reprehenderit. Fugiat id nisi quis duis sunt proident mollit dolor mollit adipisicing proident deserunt.",
},
styling: {
name: "Styling",
description: "The component can be styled easily.",
styleContainer: "Style Container - Exercitation",
styleText: "Style Text - Ea Anim",
styleAccessories: "Style Accessories - enim ea id fugiat anim ad.",
pressedState: "Style Pressed State - fugiat anim",
},
disabling: {
name: "Disabling",
description:
"The component can be disabled, and styled based on that. Press behavior will be disabled.",
standard: "Disabled - standard",
filled: "Disabled - filled",
reversed: "Disabled - reversed",
accessory: "Disabled accessory style",
textStyle: "Disabled text style",
},
},
},
demoListItem: {
description: "A styled row component that can be used in FlatList, SectionList, or by itself.",
useCase: {
height: {
name: "Height",
description: "The row can be different heights.",
defaultHeight: "Default height (56px)",
customHeight: "Custom height via `height` prop",
textHeight:
"Height determined by text content - Reprehenderit incididunt deserunt do do ea labore.",
longText:
"Limit long text to one line - Reprehenderit incididunt deserunt do do ea labore.",
},
separators: {
name: "Separators",
description: "The separator / divider is preconfigured and optional.",
topSeparator: "Only top separator",
topAndBottomSeparator: "Top and bottom separators",
bottomSeparator: "Only bottom separator",
},
icons: {
name: "Icons",
description: "You can customize the icons on the left or right.",
leftIcon: "Left icon",
rightIcon: "Right Icon",
leftRightIcons: "Left & Right Icons",
},
customLeftRight: {
name: "Custom Left/Right Components",
description: "If you need a custom left/right component, you can pass it in.",
customLeft: "Custom left component",
customRight: "Custom right component",
},
passingContent: {
name: "Passing Content",
description: "There are a few different ways to pass content.",
text: "Via `text` prop - reprehenderit sint",
children: "Children - mostrud mollit",
nestedChildren1: "Nested children - proident veniam.",
nestedChildren2: "Ullamco cupidatat officia exercitation velit non ullamco nisi..",
},
listIntegration: {
name: "Integrating w/ FlatList",
description: "The component can be easily integrated with your favorite list interface.",
},
styling: {
name: "Styling",
description: "The component can be styled easily.",
styledText: "Styled Text",
styledContainer: "Styled Container (separators)",
tintedIcons: "Tinted Icons",
},
},
},
demoCard: {
description:
"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.",
useCase: {
presets: {
name: "Presets",
description: "There are a few presets that are preconfigured.",
default: {
heading: "Default Preset (default)",
content: "Incididunt magna ut aliquip consectetur mollit dolor.",
footer: "Consectetur nulla non aliquip velit.",
},
reversed: {
heading: "Reversed Preset",
content: "Reprehenderit occaecat proident amet id laboris.",
footer: "Consectetur tempor ea non labore anim .",
},
},
verticalAlignment: {
name: "Vertical Alignment",
description:
"Depending on what's required, the card comes preconfigured with different alignment strategies.",
top: {
heading: "Top (default)",
content: "All content is automatically aligned to the top.",
footer: "Even the footer",
},
center: {
heading: "Center",
content: "Content is centered relative to the card's height.",
footer: "Me too!",
},
spaceBetween: {
heading: "Space Between",
content: "All content is spaced out evenly.",
footer: "I am where I want to be.",
},
reversed: {
heading: "Force Footer Bottom",
content: "This pushes the footer where it belongs.",
footer: "I'm so lonely down here.",
},
},
passingContent: {
name: "Passing Content",
description: "There are a few different ways to pass content.",
heading: "Via `heading` Prop",
content: "Via `content` Prop",
footer: "I'm so lonely down here.",
},
customComponent: {
name: "Custom Components",
description:
"Any of the preconfigured components can be replaced with your own. You can also add additional ones.",
rightComponent: "RightComponent",
leftComponent: "LeftComponent",
},
style: {
name: "Styling",
description: "The component can be styled easily.",
heading: "Style the Heading",
content: "Style the Content",
footer: "Style the Footer",
},
},
},
demoAutoImage: {
description: "An Image component that automatically sizes a remote or data-uri image.",
useCase: {
remoteUri: { name: "Remote URI" },
base64Uri: { name: "Base64 URI" },
scaledToFitDimensions: {
name: "Scaled to Fit Dimensions",
description:
"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.",
heightAuto: "width: 60 / height: auto",
widthAuto: "width: auto / height: 32",
bothManual: "width: 60 / height: 60",
},
},
},
demoText: {
descrip
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
SYMBOL INDEX (309 symbols across 87 files)
FILE: boilerplate/app/app.tsx
constant NAVIGATION_PERSISTENCE_KEY (line 36) | const NAVIGATION_PERSISTENCE_KEY = "NAVIGATION_STATE"
function App (line 64) | function App() {
FILE: boilerplate/app/components/AutoImage.tsx
type AutoImageProps (line 4) | interface AutoImageProps extends ImageProps {
function useAutoImage (line 30) | function useAutoImage(
function AutoImage (line 74) | function AutoImage(props: AutoImageProps) {
FILE: boilerplate/app/components/Button.tsx
type Presets (line 17) | type Presets = "default" | "filled" | "reversed"
type ButtonAccessoryProps (line 19) | interface ButtonAccessoryProps {
type ButtonProps (line 25) | interface ButtonProps extends PressableProps {
function Button (line 102) | function Button(props: ButtonProps) {
FILE: boilerplate/app/components/Card.tsx
type Presets (line 18) | type Presets = "default" | "reversed"
type CardProps (line 20) | interface CardProps extends TouchableOpacityProps {
function Card (line 130) | function Card(props: CardProps) {
FILE: boilerplate/app/components/EmptyState.tsx
type EmptyStateProps (line 12) | interface EmptyStateProps {
type EmptyStatePresetItem (line 106) | interface EmptyStatePresetItem {
function EmptyState (line 119) | function EmptyState(props: EmptyStateProps) {
FILE: boilerplate/app/components/Header.tsx
type HeaderProps (line 21) | interface HeaderProps {
type HeaderActionProps (line 133) | interface HeaderActionProps {
function Header (line 151) | function Header(props: HeaderProps) {
function HeaderAction (line 238) | function HeaderAction(props: HeaderActionProps) {
FILE: boilerplate/app/components/Icon.tsx
type IconTypes (line 14) | type IconTypes = keyof typeof iconRegistry
type BaseIconProps (line 16) | type BaseIconProps = {
type PressableIconProps (line 43) | type PressableIconProps = Omit<TouchableOpacityProps, "style"> & BaseIco...
type IconProps (line 44) | type IconProps = Omit<ViewProps, "style"> & BaseIconProps
function PressableIcon (line 53) | function PressableIcon(props: PressableIconProps) {
function Icon (line 86) | function Icon(props: IconProps) {
FILE: boilerplate/app/components/ListItem.tsx
type ListItemProps (line 18) | interface ListItemProps extends TouchableOpacityProps {
type ListItemActionProps (line 95) | interface ListItemActionProps {
function ListItemAction (line 184) | function ListItemAction(props: ListItemActionProps) {
FILE: boilerplate/app/components/Screen.tsx
constant DEFAULT_BOTTOM_OFFSET (line 23) | const DEFAULT_BOTTOM_OFFSET = 50
type BaseScreenProps (line 25) | interface BaseScreenProps {
type FixedScreenProps (line 68) | interface FixedScreenProps extends BaseScreenProps {
type ScrollScreenProps (line 71) | interface ScrollScreenProps extends BaseScreenProps {
type AutoScreenProps (line 84) | interface AutoScreenProps extends Omit<ScrollScreenProps, "preset"> {
type ScreenProps (line 93) | type ScreenProps = ScrollScreenProps | FixedScreenProps | AutoScreenProps
type ScreenPreset (line 97) | type ScreenPreset = "fixed" | "scroll" | "auto"
function isNonScrolling (line 103) | function isNonScrolling(preset?: ScreenPreset) {
function useAutoPreset (line 112) | function useAutoPreset(props: AutoScreenProps): {
function ScreenWithoutScrolling (line 177) | function ScreenWithoutScrolling(props: ScreenProps) {
function ScreenWithScrolling (line 192) | function ScreenWithScrolling(props: ScreenProps) {
function Screen (line 243) | function Screen(props: ScreenProps) {
FILE: boilerplate/app/components/Text.tsx
type Sizes (line 12) | type Sizes = keyof typeof $sizeStyles
type Weights (line 13) | type Weights = keyof typeof typography.primary
type Presets (line 14) | type Presets = "default" | "bold" | "heading" | "subheading" | "formLabe...
type TextProps (line 16) | interface TextProps extends RNTextProps {
FILE: boilerplate/app/components/TextField.tsx
type TextFieldAccessoryProps (line 22) | interface TextFieldAccessoryProps {
type TextFieldProps (line 29) | interface TextFieldProps extends Omit<TextInputProps, "ref"> {
function focusInput (line 178) | function focusInput() {
FILE: boilerplate/app/components/Toggle/Checkbox.tsx
type CheckboxToggleProps (line 10) | interface CheckboxToggleProps extends Omit<ToggleProps<CheckboxInputProp...
type CheckboxInputProps (line 21) | interface CheckboxInputProps extends BaseToggleInputProps<CheckboxToggle...
function Checkbox (line 29) | function Checkbox(props: CheckboxToggleProps) {
function CheckboxInput (line 38) | function CheckboxInput(props: CheckboxInputProps) {
FILE: boilerplate/app/components/Toggle/Radio.tsx
type RadioToggleProps (line 9) | interface RadioToggleProps extends Omit<ToggleProps<RadioInputProps>, "T...
type RadioInputProps (line 16) | interface RadioInputProps extends BaseToggleInputProps<RadioToggleProps> {}
function Radio (line 23) | function Radio(props: RadioToggleProps) {
function RadioInput (line 27) | function RadioInput(props: RadioInputProps) {
FILE: boilerplate/app/components/Toggle/Switch.tsx
type SwitchToggleProps (line 12) | interface SwitchToggleProps extends Omit<ToggleProps<SwitchInputProps>, ...
type SwitchInputProps (line 24) | interface SwitchInputProps extends BaseToggleInputProps<SwitchToggleProp...
function Switch (line 33) | function Switch(props: SwitchToggleProps) {
function SwitchInput (line 44) | function SwitchInput(props: SwitchInputProps) {
function SwitchAccessibilityLabel (line 176) | function SwitchAccessibilityLabel(props: SwitchInputProps & { role: "on"...
FILE: boilerplate/app/components/Toggle/Toggle.tsx
type ToggleProps (line 22) | interface ToggleProps<T> extends Omit<TouchableOpacityProps, "style"> {
type BaseToggleInputProps (line 111) | interface BaseToggleInputProps<T> {
function Toggle (line 126) | function Toggle<T>(props: ToggleProps<T>) {
function FieldLabel (line 216) | function FieldLabel<T>(props: ToggleProps<T>) {
FILE: boilerplate/app/config/config.base.ts
type ConfigBaseProps (line 1) | interface ConfigBaseProps {
type PersistNavigationConfig (line 7) | type PersistNavigationConfig = ConfigBaseProps["persistNavigation"]
FILE: boilerplate/app/context/AuthContext.tsx
type AuthContextType (line 4) | type AuthContextType = {
type AuthProviderProps (line 16) | interface AuthProviderProps {}
FILE: boilerplate/app/context/EpisodeContext.tsx
type EpisodeContextType (line 16) | type EpisodeContextType = {
type EpisodeProviderProps (line 29) | interface EpisodeProviderProps {}
FILE: boilerplate/app/devtools/ReactotronConfig.ts
type Console (line 118) | interface Console {
FILE: boilerplate/app/i18n/demo-en.ts
type DemoTranslations (line 460) | type DemoTranslations = typeof demoEn
FILE: boilerplate/app/i18n/en.ts
type Translations (line 130) | type Translations = typeof en
FILE: boilerplate/app/i18n/index.ts
type TxKeyPath (line 65) | type TxKeyPath = RecursiveKeyOf<Translations>
type RecursiveKeyOf (line 68) | type RecursiveKeyOf<TObj extends object> = {
type RecursiveKeyOfInner (line 72) | type RecursiveKeyOfInner<TObj extends object> = {
type RecursiveKeyOfHandleValue (line 76) | type RecursiveKeyOfHandleValue<
FILE: boilerplate/app/i18n/translate.ts
function translate (line 28) | function translate(key: TxKeyPath, options?: TOptions): string {
FILE: boilerplate/app/navigators/DemoNavigator.tsx
function DemoNavigator (line 26) | function DemoNavigator() {
FILE: boilerplate/app/navigators/navigationTypes.ts
type DemoTabParamList (line 11) | type DemoTabParamList = {
type AppStackParamList (line 19) | type AppStackParamList = {
type AppStackScreenProps (line 27) | type AppStackScreenProps<T extends keyof AppStackParamList> = NativeStac...
type DemoTabScreenProps (line 32) | type DemoTabScreenProps<T extends keyof DemoTabParamList> = CompositeScr...
type NavigationProps (line 37) | interface NavigationProps extends Partial<
FILE: boilerplate/app/navigators/navigationUtilities.ts
type Storage (line 16) | type Storage = typeof storage
function getActiveRouteName (line 35) | function getActiveRouteName(state: NavigationState | PartialState<Naviga...
function useBackButtonHandler (line 54) | function useBackButtonHandler(canExit: (routeName: string) => boolean) {
function navigationRestoredDefaultState (line 103) | function navigationRestoredDefaultState(persistNavigation: PersistNaviga...
function useNavigationPersistence (line 118) | function useNavigationPersistence(storage: Storage, persistenceKey: stri...
function navigate (line 178) | function navigate(name: unknown, params?: unknown) {
function goBack (line 191) | function goBack() {
function resetRoot (line 202) | function resetRoot(
FILE: boilerplate/app/screens/DemoDebugScreen.tsx
function openLinkInBrowser (line 28) | function openLinkInBrowser(url: string) {
FILE: boilerplate/app/screens/DemoPodcastListScreen.tsx
constant ICON_SIZE (line 41) | const ICON_SIZE = 14
function manualRefresh (line 75) | async function manualRefresh() {
FILE: boilerplate/app/screens/DemoShowroomScreen/DemoDivider.tsx
type DemoDividerProps (line 7) | interface DemoDividerProps {
function DemoDivider (line 18) | function DemoDivider(props: DemoDividerProps) {
FILE: boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx
type DemoListItem (line 32) | interface DemoListItem {
function hasValidStringProp (line 54) | function hasValidStringProp(props: unknown, propName: string): boolean {
FILE: boilerplate/app/screens/DemoShowroomScreen/DemoUseCase.tsx
type DemoUseCaseProps (line 11) | interface DemoUseCaseProps {
function DemoUseCase (line 23) | function DemoUseCase(props: DemoUseCaseProps) {
FILE: boilerplate/app/screens/DemoShowroomScreen/DrawerIconButton.tsx
type DrawerIconButtonProps (line 8) | interface DrawerIconButtonProps extends PressableProps {}
function DrawerIconButton (line 16) | function DrawerIconButton(props: DrawerIconButtonProps) {
FILE: boilerplate/app/screens/DemoShowroomScreen/SectionListWithKeyboardAwareScrollView.tsx
type SectionType (line 7) | type SectionType<ItemType> = {
type SectionListWithKeyboardAwareScrollViewProps (line 13) | type SectionListWithKeyboardAwareScrollViewProps<ItemType> = SectionList...
function SectionListWithKeyboardAwareScrollView (line 24) | function SectionListWithKeyboardAwareScrollView<ItemType = any>(
FILE: boilerplate/app/screens/DemoShowroomScreen/demos/DemoToggle.tsx
function ControlledCheckbox (line 17) | function ControlledCheckbox(props: CheckboxToggleProps) {
function ControlledRadio (line 22) | function ControlledRadio(props: RadioToggleProps) {
function ControlledSwitch (line 27) | function ControlledSwitch(props: SwitchToggleProps) {
FILE: boilerplate/app/screens/DemoShowroomScreen/demos/types.ts
type Demo (line 5) | interface Demo {
FILE: boilerplate/app/screens/ErrorScreen/ErrorBoundary.tsx
type Props (line 5) | interface Props {
type State (line 10) | interface State {
class ErrorBoundary (line 25) | class ErrorBoundary extends Component<Props, State> {
method componentDidCatch (line 29) | componentDidCatch(error: Error, errorInfo: ErrorInfo) {
method shouldComponentUpdate (line 51) | shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<...
method isEnabled (line 56) | isEnabled(): boolean {
method render (line 65) | render() {
FILE: boilerplate/app/screens/ErrorScreen/ErrorDetails.tsx
type ErrorDetailsProps (line 11) | interface ErrorDetailsProps {
function ErrorDetails (line 22) | function ErrorDetails(props: ErrorDetailsProps) {
FILE: boilerplate/app/screens/LoginScreen.tsx
type LoginScreenProps (line 15) | interface LoginScreenProps extends AppStackScreenProps<"Login"> {}
function login (line 40) | function login() {
FILE: boilerplate/app/screens/WelcomeScreen.tsx
type WelcomeScreenProps (line 19) | interface WelcomeScreenProps extends AppStackScreenProps<"Welcome"> {}
function goNext (line 30) | function goNext() {
FILE: boilerplate/app/services/api/apiProblem.ts
type GeneralApiProblem (line 3) | type GeneralApiProblem =
function getGeneralApiProblem (line 46) | function getGeneralApiProblem(response: ApiResponse<any>): GeneralApiPro...
FILE: boilerplate/app/services/api/index.ts
constant DEFAULT_API_CONFIG (line 26) | const DEFAULT_API_CONFIG: ApiConfig = {
class Api (line 35) | class Api {
method constructor (line 42) | constructor(config: ApiConfig = DEFAULT_API_CONFIG) {
method getEpisodes (line 57) | async getEpisodes(): Promise<{ kind: "ok"; episodes: EpisodeItem[] } |...
FILE: boilerplate/app/services/api/types.ts
type EpisodeItem (line 5) | interface EpisodeItem {
type ApiFeedResponse (line 24) | interface ApiFeedResponse {
type ApiConfig (line 40) | interface ApiConfig {
FILE: boilerplate/app/theme/context.tsx
type ThemeContextType (line 31) | type ThemeContextType = {
type ThemeProviderProps (line 41) | interface ThemeProviderProps {
FILE: boilerplate/app/theme/spacingDark.ts
constant SPACING_MULTIPLIER (line 1) | const SPACING_MULTIPLIER = 1.0
FILE: boilerplate/app/theme/types.ts
type ImmutableThemeContextModeT (line 11) | type ImmutableThemeContextModeT = "light" | "dark"
type ThemeContextModeT (line 12) | type ThemeContextModeT = ImmutableThemeContextModeT | undefined
type Colors (line 16) | type Colors = typeof colorsLight | typeof colorsDark
type Spacing (line 18) | type Spacing = typeof spacingLight | typeof spacingDark
type Timing (line 21) | type Timing = typeof timing
type Typography (line 22) | type Typography = typeof typography
type Theme (line 25) | interface Theme {
type ThemedStyle (line 52) | type ThemedStyle<T> = (theme: Theme) => T
type ThemedStyleArray (line 53) | type ThemedStyleArray<T> = (
type AllowedStylesT (line 61) | type AllowedStylesT<T> = ThemedStyle<T> | StyleProp<T> | ThemedStyleArra...
type ThemedFnT (line 64) | type ThemedFnT = <T>(styleOrStyleFn: AllowedStylesT<T>) => T
FILE: boilerplate/app/utils/crashReporting.ts
type ErrorType (line 34) | enum ErrorType {
FILE: boilerplate/app/utils/formatDate.ts
type Options (line 10) | type Options = Parameters<typeof format>[2]
FILE: boilerplate/app/utils/openLinkInBrowser.ts
function openLinkInBrowser (line 6) | function openLinkInBrowser(url: string) {
FILE: boilerplate/app/utils/storage/index.ts
function loadString (line 10) | function loadString(key: string): string | null {
function saveString (line 25) | function saveString(key: string, value: string): boolean {
function load (line 39) | function load<T>(key: string): T | null {
function save (line 55) | function save(key: string, value: unknown): boolean {
function remove (line 69) | function remove(key: string): void {
function clear (line 78) | function clear(): void {
FILE: boilerplate/app/utils/storage/storage.test.ts
constant VALUE_OBJECT (line 3) | const VALUE_OBJECT = { x: 1 }
constant VALUE_STRING (line 4) | const VALUE_STRING = JSON.stringify(VALUE_OBJECT)
FILE: boilerplate/app/utils/useHeader.tsx
function useHeader (line 13) | function useHeader(
FILE: boilerplate/app/utils/useIsMounted.ts
function useIsMounted (line 6) | function useIsMounted() {
FILE: boilerplate/app/utils/useSafeAreaInsetsStyle.ts
type ExtendedEdge (line 3) | type ExtendedEdge = Edge | "start" | "end"
type SafeAreaInsetsStyle (line 19) | type SafeAreaInsetsStyle<
function useSafeAreaInsetsStyle (line 33) | function useSafeAreaInsetsStyle<
FILE: boilerplate/src/app/_layout.tsx
function Root (line 21) | function Root() {
FILE: boilerplate/src/app/index.tsx
function Index (line 3) | function Index() {
FILE: boilerplate/test/i18n.test.ts
constant EXCEPTIONS (line 7) | const EXCEPTIONS: string[] = [
function iterate (line 19) | function iterate(obj, stack, array) {
FILE: boilerplate/types/lib.es5.d.ts
type Falsy (line 16) | type Falsy = false | 0 | "" | null | undefined
type Array (line 18) | interface Array<T> {
FILE: src/assets/index.ts
type AssetName (line 4) | type AssetName = "logo.ascii.txt" | "logo-sm.ascii.txt"
FILE: src/cli.ts
function run (line 4) | async function run(argv) {
FILE: src/commands/cache.ts
type SubCommand (line 44) | type SubCommand = (typeof subcommands)[keyof typeof subcommands]
FILE: src/commands/doctor.ts
function packageInfo (line 71) | async function packageInfo(packagerName: "npm" | "yarn" | "pnpm" | "bun") {
FILE: src/commands/generate.ts
constant SUB_DIR_DELIMITER (line 8) | const SUB_DIR_DELIMITER = "/"
function generate (line 19) | async function generate(toolbox: GluegunToolbox) {
FILE: src/commands/generate/app-icon.ts
function generate (line 15) | async function generate(toolbox: GluegunToolbox) {
FILE: src/commands/generate/splash-screen.ts
function generate (line 19) | async function generate(toolbox: GluegunToolbox) {
FILE: src/commands/issue.ts
constant START_CMD (line 20) | const START_CMD = isMac ? "open" : "start"
FILE: src/commands/new.ts
type Workflow (line 40) | type Workflow = "cng" | "manual"
type Options (line 42) | interface Options {
function buildCliCommand (line 1035) | function buildCliCommand(args: {
function findAndRemoveDependencies (line 1066) | function findAndRemoveDependencies(
FILE: src/commands/remove-demo.ts
constant MATCHING_GLOBS (line 9) | const MATCHING_GLOBS = [
function removeDemoAssets (line 71) | function removeDemoAssets() {
FILE: src/tools/cache.ts
constant MAC (line 13) | const MAC: NodeJS.Platform = "darwin"
constant WINDOWS (line 14) | const WINDOWS: NodeJS.Platform = "win32"
constant LINUX (line 15) | const LINUX: NodeJS.Platform = "linux"
function hash (line 24) | function hash(str: string) {
type TargetsOptions (line 28) | interface TargetsOptions {
type CopyOptions (line 43) | interface CopyOptions {
function copy (line 49) | function copy(options: CopyOptions) {
function rootdir (line 68) | function rootdir(platform: NodeJS.Platform = process.platform) {
function clear (line 73) | function clear() {
FILE: src/tools/demo.ts
constant DEMO_MARKUP_PREFIX (line 3) | const DEMO_MARKUP_PREFIX = "@demo"
function findDemoPatches (line 7) | function findDemoPatches(): string[] {
FILE: src/tools/filesystem-ext.ts
function children (line 11) | function children(path: string, isRelative = false, matching = "*"): str...
FILE: src/tools/flag.ts
function bool (line 11) | function bool(value: unknown): boolean {
function boolFlag (line 20) | function boolFlag(option: unknown): boolean | undefined {
FILE: src/tools/generators.ts
constant NEW_LINE (line 8) | const NEW_LINE = filesystem.eol
type Options (line 10) | type Options = {
function runGenerator (line 15) | function runGenerator(
function validateGenerator (line 43) | function validateGenerator(generator?: string) {
function showGeneratorHelp (line 67) | function showGeneratorHelp(toolbox: GluegunToolbox) {
function showGenerators (line 107) | function showGenerators() {
function updateGenerators (line 133) | function updateGenerators(toolbox: GluegunToolbox) {
function isIgniteProject (line 164) | function isIgniteProject(): boolean {
function cwd (line 168) | function cwd() {
function igniteDir (line 172) | function igniteDir() {
function appDir (line 176) | function appDir() {
function templatesDir (line 185) | function templatesDir() {
function frontMatter (line 189) | function frontMatter(contents: string) {
type Patch (line 204) | type Patch = GluegunPatchingPatchOptions & {
function handlePatches (line 215) | async function handlePatches(data: { patches?: Patch[]; patch?: Patch }) {
function installedGenerators (line 238) | function installedGenerators(): string[] {
type GeneratorCaseOptions (line 246) | type GeneratorCaseOptions = "auto" | "pascal" | "camel" | "kebab" | "sna...
type GeneratorOptions (line 248) | type GeneratorOptions = {
function generateFromTemplate (line 260) | async function generateFromTemplate(
function frontMatterDirectoryDir (line 369) | function frontMatterDirectoryDir(generator: string): string | undefined {
function igniteCliRootDir (line 388) | function igniteCliRootDir(): string {
function sourceDirectory (line 395) | function sourceDirectory(): string {
function availableGenerators (line 402) | function availableGenerators(): string[] {
function installGenerators (line 411) | function installGenerators(generators: string[]): string[] {
type Platforms (line 446) | enum Platforms {
constant APP_ICON_RULESET (line 453) | const APP_ICON_RULESET = {
function validateAppIconGenerator (line 499) | async function validateAppIconGenerator(option: `${Platforms}` | "all", ...
function generateAppIcons (line 573) | async function generateAppIcons(option: `${Platforms}` | "all") {
function validateSplashScreenGenerator (line 767) | async function validateSplashScreenGenerator(
function generateSplashScreen (line 846) | async function generateSplashScreen(options: {
FILE: src/tools/markup.test.ts
constant TEST_MARKUP_PREFIX (line 10) | const TEST_MARKUP_PREFIX = "@test"
FILE: src/tools/markup.ts
type MarkupComments (line 5) | enum MarkupComments {
function removeCurrentLine (line 28) | function removeCurrentLine(contents: string, comment: string): string {
function removeNextLine (line 38) | function removeNextLine(contents: string, comment: string): string {
function replaceNextLine (line 63) | function replaceNextLine(contents: string, comment: string): string {
function removeBlocks (line 81) | function removeBlocks(contents: string, comment: { start: string; end: s...
function updateFile (line 114) | function updateFile(contents: string, markupPrefix: string): string {
constant DEFAULT_MATCHING_GLOBS (line 127) | const DEFAULT_MATCHING_GLOBS = [
function findFiles (line 147) | function findFiles(targetDir: string, matching?: string[]) {
function removeEmptyDirs (line 160) | function removeEmptyDirs({
function deleteFiles (line 194) | async function deleteFiles({
function updateFiles (line 219) | async function updateFiles({
FILE: src/tools/packager.ts
type PackagerName (line 8) | type PackagerName = "npm" | "yarn" | "pnpm" | "bun"
type PackageOptions (line 9) | type PackageOptions = {
type PackageRunOptions (line 16) | type PackageRunOptions = PackageOptions & {
function yarnAvailable (line 29) | function yarnAvailable() {
function pnpmAvailable (line 36) | function pnpmAvailable() {
function bunAvailable (line 43) | function bunAvailable() {
function detectPackager (line 49) | function detectPackager(): PackagerName {
function availablePackagers (line 61) | function availablePackagers(): PackagerName[] {
function addCmd (line 85) | function addCmd(pkg: string, options: PackageRunOptions = packageInstall...
function removeCmd (line 113) | function removeCmd(pkg: string, options: PackageOptions = packageInstall...
function installCmd (line 141) | function installCmd(options: PackageRunOptions) {
type PackageListOutput (line 157) | type PackageListOutput = [string, (string) => [string, string][]]
function list (line 158) | function list(options: PackageOptions = packageListOptions): PackageList...
function runCmd (line 211) | function runCmd(command: string, options: PackageOptions) {
FILE: src/tools/pretty.ts
constant INDENT (line 8) | const INDENT = " "
type Spinner (line 88) | type Spinner = ReturnType<typeof print.spin>
FILE: src/tools/react-native.test.ts
constant EXAMPLE_README (line 6) | const EXAMPLE_README = `
FILE: src/tools/react-native.ts
type CopyBoilerplateOptions (line 15) | type CopyBoilerplateOptions = {
function copyBoilerplate (line 26) | async function copyBoilerplate(toolbox: GluegunToolbox, options: CopyBoi...
function renameReactNativeApp (line 53) | async function renameReactNativeApp(
function replaceMaestroBundleIds (line 188) | async function replaceMaestroBundleIds(
constant EXPO_ROUTER_SCREEN_TEMPLATE (line 234) | const EXPO_ROUTER_SCREEN_TEMPLATE = `---
constant EXPO_ROUTER_ROUTE_TEMPLATE (line 260) | const EXPO_ROUTER_ROUTE_TEMPLATE = `---
constant EXPO_ROUTER_DYNAMIC_ROUTE_TEMPLATE (line 271) | const EXPO_ROUTER_DYNAMIC_ROUTE_TEMPLATE = `import { <%= props.pascalCas...
function createGeneratorTemplate (line 279) | function createGeneratorTemplate(
function refactorExpoRouterReactotronCmds (line 300) | function refactorExpoRouterReactotronCmds(toolbox: GluegunToolbox) {
function updateExpoRouterSrcDir (line 331) | function updateExpoRouterSrcDir(toolbox: GluegunToolbox) {
function updateExpoRouterPackageJson (line 361) | function updateExpoRouterPackageJson(toolbox: GluegunToolbox) {
function cleanupExpoRouterConversion (line 395) | function cleanupExpoRouterConversion(toolbox: GluegunToolbox, targetPath...
function updatePackagerCommandsInReadme (line 410) | function updatePackagerCommandsInReadme(readmePath: string, packagerName...
FILE: src/tools/spawn.ts
type SpawnOptions (line 3) | type SpawnOptions = {
function spawnProgress (line 7) | function spawnProgress(commandLine: string, options: SpawnOptions): Prom...
FILE: src/tools/strip-ansi.ts
function ansiRegex (line 2) | function ansiRegex({ onlyFirst = false } = {}) {
function stripANSI (line 11) | function stripANSI(string) {
FILE: src/tools/validations.ts
type IsError (line 6) | type IsError = (str: string) => boolean
type ErrorMessage (line 7) | type ErrorMessage = (str?: string) => string
type ErrorGuard (line 8) | type ErrorGuard = [IsError, ErrorMessage]
function validateProjectName (line 40) | async function validateProjectName(toolbox: GluegunToolbox): Promise<str...
function validateBundleIdentifier (line 73) | function validateBundleIdentifier(
function validateProjectPath (line 105) | function validateProjectPath(absPath: string, toolbox: GluegunToolbox) {
type ValidationsExports (line 137) | type ValidationsExports = {
FILE: src/types.ts
type CLIType (line 3) | type CLIType = "ignite-classic" | "react-native-cli" | "expo-cli" | "cre...
type CLIOptions (line 5) | type CLIOptions = {
FILE: test/_test-helpers.ts
constant IGNITE (line 12) | const IGNITE = "node " + filesystem.path(__dirname, "..", "bin", "ignite")
type RunOptions (line 17) | type RunOptions = {
type SpawnOptions (line 22) | type SpawnOptions = RunOptions & {
type CommandOutput (line 26) | type CommandOutput = {
function buildCommand (line 31) | function buildCommand(cmd: string, options: RunOptions) {
function run (line 35) | async function run(cmd: string, options: RunOptions = {}): Promise<strin...
function runError (line 40) | async function runError(cmd: string): Promise<string | any> {
function runIgnite (line 50) | async function runIgnite(cmd: string, options: RunOptions = {}): Promise...
function deleteFileIfExists (line 54) | async function deleteFileIfExists(file: string) {
function setUpLogFile (line 62) | async function setUpLogFile(filePath: string): Promise<WriteStream> {
function startSpawnAndLog (line 85) | async function startSpawnAndLog(cmd: string, outputLog: WriteStream): Pr...
function spawnAndLog (line 113) | async function spawnAndLog(cmd: string, options: SpawnOptions): Promise<...
function spawnAndLogIgnite (line 135) | async function spawnAndLogIgnite(
function spawnIgniteAndPrintIfFail (line 144) | async function spawnIgniteAndPrintIfFail(
function generateScreenTemplatePath (line 157) | function generateScreenTemplatePath(pathname: string): string {
function generateRouteTemplatePath (line 161) | function generateRouteTemplatePath(pathname: string): string {
function generateDynamicRouteTemplatePath (line 165) | function generateDynamicRouteTemplatePath(pathname: string): string {
function copyDefaultScreenGenerator (line 169) | function copyDefaultScreenGenerator(tempBoilerplatePath: string): void {
function copyExpoRouterGeneratorTemplates (line 180) | function copyExpoRouterGeneratorTemplates(tempBoilerplatePath: string): ...
function removeScreenGenerator (line 191) | function removeScreenGenerator(tempBoilerplatePath: string): void {
function removeExpoRouterGeneratorTemplates (line 196) | function removeExpoRouterGeneratorTemplates(tempBoilerplatePath: string)...
FILE: test/vanilla/ignite-generate.test.ts
constant BOILERPLATE_PATH (line 12) | const BOILERPLATE_PATH = filesystem.path(__dirname, "../../boilerplate")
FILE: test/vanilla/ignite-new-expo-router.test.ts
constant APP_NAME (line 6) | const APP_NAME = "Foo"
FILE: test/vanilla/ignite-new.test.ts
constant APP_NAME (line 6) | const APP_NAME = "Foo"
function verifySplashScreenColor (line 204) | function verifySplashScreenColor(type: "android" | "ios" | "expo", match...
function checkForLeftoverHelloWorld (line 318) | async function checkForLeftoverHelloWorld(filePath: string) {
FILE: test/vanilla/ignite-remove-demo.test.ts
constant BOILERPLATE_PATH (line 6) | const BOILERPLATE_PATH = filesystem.path(__dirname, "../../boilerplate")
Condensed preview — 276 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,091K chars).
[
{
"path": ".circleci/config.yml",
"chars": 4413,
"preview": "# JavaScript Node CircleCI 2.0 configuration file\n#\n# Check https://circleci.com/docs/2.0/language-javascript/ for more "
},
{
"path": ".dependency-cruiser.js",
"chars": 6389,
"preview": "/** @type {import('dependency-cruiser').IConfiguration} */\nmodule.exports = {\n forbidden: [\n {\n name: \"no-circu"
},
{
"path": ".eslintignore",
"chars": 52,
"preview": "node_modules\n.vscode\n\n**/*.snap\n**/*.txt\nbin/ignite\n"
},
{
"path": ".eslintrc.js",
"chars": 118,
"preview": "const boilerplateLintConfig = require(\"./boilerplate/.eslintrc.js\")\n\nmodule.exports = {\n ...boilerplateLintConfig,\n}\n"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 1791,
"preview": "# Contributing to Ignite CLI\n\nWe welcome all contributors to Ignite CLI! This contributing guide will help you get up an"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 1104,
"preview": "name: Bug report\ndescription: For issues with running ignite on your computer\nlabels: [\"bug\"]\nbody:\n\n - type: markdown\n"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 473,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Discussions\n url: https://github.com/infinitered/ignite/discussi"
},
{
"path": ".github/ISSUE_TEMPLATE/enhancement.md",
"chars": 133,
"preview": "---\nname: Enhancement\nabout: For ignite enhancement suggestions or feature request\ntitle: \"\"\nlabels: \"enhancement\"\nassig"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 1026,
"preview": "## Description\n\n<!-- Add your PR description here -->\n\n- Issues: <!-- e.g. \"fixes #9999\", \"related: #9999\", etc -->\n\n## "
},
{
"path": ".gitignore",
"chars": 845,
"preview": "# 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/"
},
{
"path": ".nvmrc",
"chars": 4,
"preview": "v20\n"
},
{
"path": ".prettierignore",
"chars": 39,
"preview": "/build\n/boilerplate/*\n!/boilerplate/app"
},
{
"path": ".prettierrc",
"chars": 121,
"preview": "{\n \"printWidth\": 100,\n \"semi\": false,\n \"singleQuote\": false,\n \"trailingComma\": \"all\",\n \"quoteProps\": \"consistent\"\n}"
},
{
"path": ".yarnrc.yml",
"chars": 114,
"preview": "enableGlobalCache: false\n\nnodeLinker: node-modules\n\nyarnPath: .yarn/releases/yarn-4.9.1.cjs\n\nenableScripts: false\n"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3215,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "LICENSE",
"chars": 1706,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016-present Infinite Red, Inc.\n\nPermission is hereby granted, free of charge, to a"
},
{
"path": "README.md",
"chars": 10741,
"preview": "<p align=\"center\"><img src=\"https://user-images.githubusercontent.com/1479215/206780298-2b98221d-9c57-4cd3-866a-cf85ec1d"
},
{
"path": "bin/ignite",
"chars": 882,
"preview": "#!/usr/bin/env node\n\n/* tslint:disable */\n\n// speed up `ignite-cli --version` et al\nif ([\"v\", \"version\", \"-v\", \"--v\", \"-"
},
{
"path": "boilerplate/.dependency-cruiser.js",
"chars": 7063,
"preview": "const metroConfig = require(\"./metro.config.js\")\n\nconst platforms = [\"ios\", \"android\", \"web\", \"native\"]\nconst extensions"
},
{
"path": "boilerplate/.eslintignore",
"chars": 85,
"preview": "node_modules\nios\nandroid\n.expo\n.vscode\nignite/ignite.json\npackage.json\n.eslintignore\n"
},
{
"path": "boilerplate/.eslintrc.js",
"chars": 3015,
"preview": "// https://docs.expo.dev/guides/using-eslint/\nmodule.exports = {\n root: true,\n extends: [\n \"plugin:@typescript-esli"
},
{
"path": "boilerplate/.gitignore",
"chars": 1283,
"preview": "# 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*.p"
},
{
"path": "boilerplate/.maestro/flows/FavoritePodcast.yaml",
"chars": 1151,
"preview": "# flow: run the login flow and then navigate to the demo podcast list screen, favorite a podcast, and then switch the li"
},
{
"path": "boilerplate/.maestro/flows/Login.yaml",
"chars": 451,
"preview": "#flow: Login\n#intent:\n# Open up our app and use the default credentials to login\n# and navigate to the demo screen\n\nappI"
},
{
"path": "boilerplate/.maestro/shared/_Login.yaml",
"chars": 351,
"preview": "#flow: Shared _Login\n#intent: shared login flow for any flow that needs to start with a log in.\nappId: ${MAESTRO_APP_ID}"
},
{
"path": "boilerplate/.maestro/shared/_OnFlowStart.yaml",
"chars": 1420,
"preview": "# flow: Shared _OnFlowStart\n#intent:\n# launch the app with a completely clear state, wait for animations to settle,\n# an"
},
{
"path": "boilerplate/.npmrc",
"chars": 89,
"preview": "# I wish we could put this in package.json instead of here\nstrict-peer-dependencies=false"
},
{
"path": "boilerplate/.prettierignore",
"chars": 91,
"preview": "node_modules\nios\nandroid\n.expo\n.vscode\nignite/ignite.json\npackage.json\n.eslintignore\n*.ejs\n"
},
{
"path": "boilerplate/.prettierrc",
"chars": 120,
"preview": "{\n \"printWidth\": 100,\n \"semi\": false,\n \"singleQuote\": false,\n \"trailingComma\": \"all\",\n \"quoteProps\": \"consistent\"\n}"
},
{
"path": "boilerplate/README.md",
"chars": 3134,
"preview": "# Welcome to your new ignited app!\n\n> The latest and greatest boilerplate for Infinite Red opinions\n\nThis is the boilerp"
},
{
"path": "boilerplate/app/app.tsx",
"chars": 3761,
"preview": "/* eslint-disable import/first */\n/**\n * Welcome to the main entry point of the app. In this file, we'll\n * be kicking o"
},
{
"path": "boilerplate/app/components/AutoImage.tsx",
"chars": 3177,
"preview": "import { useLayoutEffect, useState } from \"react\"\nimport { Image, ImageProps, ImageURISource, Platform, PixelRatio } fro"
},
{
"path": "boilerplate/app/components/Button.tsx",
"chars": 7160,
"preview": "import { ComponentType } from \"react\"\nimport {\n Pressable,\n PressableProps,\n PressableStateCallbackType,\n StyleProp,"
},
{
"path": "boilerplate/app/components/Card.tsx",
"chars": 8844,
"preview": "import { ComponentType, Fragment, ReactElement } from \"react\"\nimport {\n StyleProp,\n TextStyle,\n TouchableOpacity,\n T"
},
{
"path": "boilerplate/app/components/EmptyState.tsx",
"chars": 6887,
"preview": "import { Image, ImageProps, ImageStyle, StyleProp, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { translate "
},
{
"path": "boilerplate/app/components/Header.tsx",
"chars": 9218,
"preview": "import { ReactElement } from \"react\"\nimport {\n StyleProp,\n TextStyle,\n TouchableOpacity,\n TouchableOpacityProps,\n V"
},
{
"path": "boilerplate/app/components/Icon.tsx",
"chars": 4209,
"preview": "import {\n Image,\n ImageStyle,\n StyleProp,\n TouchableOpacity,\n TouchableOpacityProps,\n View,\n ViewProps,\n ViewSty"
},
{
"path": "boilerplate/app/components/ListItem.tsx",
"chars": 5895,
"preview": "import { forwardRef, ReactElement } from \"react\"\nimport {\n StyleProp,\n TextStyle,\n TouchableOpacity,\n TouchableOpaci"
},
{
"path": "boilerplate/app/components/Screen.tsx",
"chars": 9156,
"preview": "import { ReactNode, useRef, useState } from \"react\"\nimport {\n KeyboardAvoidingView,\n KeyboardAvoidingViewProps,\n Layo"
},
{
"path": "boilerplate/app/components/Text.test.tsx",
"chars": 751,
"preview": "import { NavigationContainer } from \"@react-navigation/native\"\nimport { render } from \"@testing-library/react-native\"\n\ni"
},
{
"path": "boilerplate/app/components/Text.tsx",
"chars": 3713,
"preview": "import { ReactNode, forwardRef, ForwardedRef } from \"react\"\n// eslint-disable-next-line no-restricted-imports\nimport { S"
},
{
"path": "boilerplate/app/components/TextField.tsx",
"chars": 8107,
"preview": "import { ComponentType, forwardRef, Ref, useImperativeHandle, useRef } from \"react\"\nimport {\n ImageStyle,\n StyleProp,\n"
},
{
"path": "boilerplate/app/components/Toggle/Checkbox.tsx",
"chars": 3481,
"preview": "import { useEffect, useRef, useCallback } from \"react\"\nimport { Image, ImageStyle, Animated, StyleProp, View, ViewStyle "
},
{
"path": "boilerplate/app/components/Toggle/Radio.tsx",
"chars": 2871,
"preview": "import { useEffect, useRef } from \"react\"\nimport { StyleProp, View, ViewStyle, Animated } from \"react-native\"\n\nimport { "
},
{
"path": "boilerplate/app/components/Toggle/Switch.tsx",
"chars": 7819,
"preview": "import { useEffect, useMemo, useRef, useCallback } from \"react\"\nimport { Animated, Image, ImageStyle, Platform, StylePro"
},
{
"path": "boilerplate/app/components/Toggle/Toggle.tsx",
"chars": 7283,
"preview": "import { ComponentType, FC, useMemo } from \"react\"\nimport {\n GestureResponderEvent,\n ImageStyle,\n StyleProp,\n Switch"
},
{
"path": "boilerplate/app/config/config.base.ts",
"chars": 750,
"preview": "export interface ConfigBaseProps {\n persistNavigation: \"always\" | \"dev\" | \"prod\" | \"never\"\n catchErrors: \"always\" | \"d"
},
{
"path": "boilerplate/app/config/config.dev.ts",
"chars": 268,
"preview": "/**\n * These are configuration settings for the dev environment.\n *\n * Do not include API secrets in this file or anywhe"
},
{
"path": "boilerplate/app/config/config.prod.ts",
"chars": 275,
"preview": "/**\n * These are configuration settings for the production environment.\n *\n * Do not include API secrets in this file or"
},
{
"path": "boilerplate/app/config/index.ts",
"chars": 976,
"preview": "/**\n * This file imports configuration objects from either the config.dev.js file\n * or the config.prod.js file dependin"
},
{
"path": "boilerplate/app/context/AuthContext.tsx",
"chars": 1593,
"preview": "import { createContext, FC, PropsWithChildren, useCallback, useContext, useMemo } from \"react\"\nimport { useMMKVString } "
},
{
"path": "boilerplate/app/context/EpisodeContext.tsx",
"chars": 3906,
"preview": "import {\n createContext,\n FC,\n PropsWithChildren,\n useCallback,\n useContext,\n useMemo,\n useState,\n} from \"react\"\n"
},
{
"path": "boilerplate/app/devtools/ReactotronClient.ts",
"chars": 223,
"preview": "/**\n * This file is loaded in React Native and exports the RN version\n * of Reactotron's client.\n *\n * Web is loaded fro"
},
{
"path": "boilerplate/app/devtools/ReactotronClient.web.ts",
"chars": 366,
"preview": "/**\n * This file is loaded in web and exports the React.js version\n * of Reactotron's client.\n *\n * React Native is load"
},
{
"path": "boilerplate/app/devtools/ReactotronConfig.ts",
"chars": 3975,
"preview": "/**\n * This file does the setup for integration with Reactotron, which is a\n * free desktop app for inspecting and debug"
},
{
"path": "boilerplate/app/i18n/ar.ts",
"chars": 5522,
"preview": "import demoAr from \"./demo-ar\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst ar: Translations "
},
{
"path": "boilerplate/app/i18n/demo-ar.ts",
"chars": 17423,
"preview": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoAr: DemoTranslations = {\n demoIcon: {\n description:\n "
},
{
"path": "boilerplate/app/i18n/demo-en.ts",
"chars": 18332,
"preview": "export const demoEn = {\n demoIcon: {\n description:\n \"A component to render a registered icon. It is wrapped in "
},
{
"path": "boilerplate/app/i18n/demo-es.ts",
"chars": 20322,
"preview": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoEs: DemoTranslations = {\n demoIcon: {\n description:\n "
},
{
"path": "boilerplate/app/i18n/demo-fr.ts",
"chars": 19726,
"preview": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoFr: DemoTranslations = {\n demoIcon: {\n description:\n "
},
{
"path": "boilerplate/app/i18n/demo-hi.ts",
"chars": 19072,
"preview": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoHi: DemoTranslations = {\n demoIcon: {\n description:\n "
},
{
"path": "boilerplate/app/i18n/demo-ja.ts",
"chars": 15421,
"preview": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoJa: DemoTranslations = {\n demoIcon: {\n description:\n "
},
{
"path": "boilerplate/app/i18n/demo-ko.ts",
"chars": 13968,
"preview": "import { DemoTranslations } from \"./demo-en\"\n\nexport const demoKo: DemoTranslations = {\n demoIcon: {\n description:\n "
},
{
"path": "boilerplate/app/i18n/en.ts",
"chars": 5863,
"preview": "import demoEn from \"./demo-en\" // @demo remove-current-line\n\nconst en = {\n common: {\n ok: \"OK!\",\n cancel: \"Cancel"
},
{
"path": "boilerplate/app/i18n/es.ts",
"chars": 6530,
"preview": "import demoEs from \"./demo-es\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst es: Translations "
},
{
"path": "boilerplate/app/i18n/fr.ts",
"chars": 6743,
"preview": "import demoFr from \"./demo-fr\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst fr: Translations "
},
{
"path": "boilerplate/app/i18n/hi.ts",
"chars": 6158,
"preview": "import demoHi from \"./demo-hi\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst hi: Translations "
},
{
"path": "boilerplate/app/i18n/index.ts",
"chars": 2524,
"preview": "import { I18nManager } from \"react-native\"\nimport * as Localization from \"expo-localization\"\nimport i18n from \"i18next\"\n"
},
{
"path": "boilerplate/app/i18n/ja.ts",
"chars": 4666,
"preview": "import demoJa from \"./demo-ja\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst ja: Translations "
},
{
"path": "boilerplate/app/i18n/ko.ts",
"chars": 4693,
"preview": "import demoKo from \"./demo-ko\" // @demo remove-current-line\nimport { Translations } from \"./en\"\n\nconst ko: Translations "
},
{
"path": "boilerplate/app/i18n/translate.ts",
"chars": 641,
"preview": "import i18n from \"i18next\"\nimport type { TOptions } from \"i18next\"\n\nimport { TxKeyPath } from \".\"\n\n/**\n * Translates tex"
},
{
"path": "boilerplate/app/navigators/AppNavigator.tsx",
"chars": 2799,
"preview": "/**\n * The app navigator (formerly \"AppNavigator\" and \"MainNavigator\") is used for the primary\n * navigation flows of yo"
},
{
"path": "boilerplate/app/navigators/DemoNavigator.tsx",
"chars": 3810,
"preview": "import { TextStyle, ViewStyle } from \"react-native\"\nimport { createBottomTabNavigator } from \"@react-navigation/bottom-t"
},
{
"path": "boilerplate/app/navigators/navigationTypes.ts",
"chars": 1155,
"preview": "import { ComponentProps } from \"react\"\nimport { BottomTabScreenProps } from \"@react-navigation/bottom-tabs\"\nimport {\n C"
},
{
"path": "boilerplate/app/navigators/navigationUtilities.ts",
"chars": 7378,
"preview": "import { useState, useEffect, useRef } from \"react\"\nimport { BackHandler, Linking, Platform } from \"react-native\"\nimport"
},
{
"path": "boilerplate/app/screens/DemoCommunityScreen.tsx",
"chars": 5234,
"preview": "import { FC } from \"react\"\nimport { Image, ImageStyle, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { ListIt"
},
{
"path": "boilerplate/app/screens/DemoDebugScreen.tsx",
"chars": 5911,
"preview": "import { FC, useCallback, useMemo } from \"react\"\nimport {\n LayoutAnimation,\n Linking,\n Platform,\n TextStyle,\n useCo"
},
{
"path": "boilerplate/app/screens/DemoPodcastListScreen.tsx",
"chars": 11418,
"preview": "import { ComponentType, FC, useCallback, useEffect, useMemo, useState } from \"react\"\nimport {\n AccessibilityProps,\n Ac"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/DemoDivider.tsx",
"chars": 1553,
"preview": "/* eslint-disable react-native/no-inline-styles */\nimport { StyleProp, View, ViewStyle } from \"react-native\"\n\nimport ty"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/DemoShowroomScreen.tsx",
"chars": 10070,
"preview": "import { FC, ReactElement, useCallback, useEffect, useRef, useState } from \"react\"\nimport {\n FlatList,\n Image,\n Image"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/DemoUseCase.tsx",
"chars": 1528,
"preview": "import { ReactNode } from \"react\"\nimport { TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Text } from \"@/com"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/DrawerIconButton.tsx",
"chars": 3513,
"preview": "import { Pressable, PressableProps, ViewStyle, Platform } from \"react-native\"\nimport { useDrawerProgress } from \"react-n"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/SectionListWithKeyboardAwareScrollView.tsx",
"chars": 2056,
"preview": "import { forwardRef, ReactElement, ReactNode, useCallback } from \"react\"\nimport { ScrollViewProps, SectionList, SectionL"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoAutoImage.tsx",
"chars": 51743,
"preview": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { Image, ImageStyle, TextStyle, View, ViewStyle"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoButton.tsx",
"chars": 7275,
"preview": "/* eslint-disable react/jsx-key */\nimport { ImageStyle, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Butto"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoCard.tsx",
"chars": 6003,
"preview": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { AutoImage } from \"@/components/AutoImage\"\nimp"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoEmptyState.tsx",
"chars": 2693,
"preview": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { EmptyState } from \"@/components/EmptyState\"\n\n"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoHeader.tsx",
"chars": 4417,
"preview": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { TextStyle, View, ViewStyle } from \"react-nati"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoIcon.tsx",
"chars": 3336,
"preview": "/* eslint-disable react/jsx-key */\nimport { ImageStyle, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Icon,"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoListItem.tsx",
"chars": 7524,
"preview": "/* eslint-disable react/jsx-key */\nimport { TextStyle, View, ViewStyle } from \"react-native\"\nimport { FlatList } from \"r"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoText.tsx",
"chars": 4113,
"preview": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { Text } from \"@/components/Text\"\nimport { tran"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoTextField.tsx",
"chars": 8803,
"preview": "/* eslint-disable react/jsx-key */\nimport { TextStyle, ViewStyle } from \"react-native\"\n\nimport { Icon } from \"@/componen"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/DemoToggle.tsx",
"chars": 11927,
"preview": "/* eslint-disable react/jsx-key, react-native/no-inline-styles */\nimport { useState } from \"react\"\nimport { TextStyle, V"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/index.ts",
"chars": 318,
"preview": "export * from \"./DemoIcon\"\nexport * from \"./DemoTextField\"\nexport * from \"./DemoToggle\"\nexport * from \"./DemoButton\"\nexp"
},
{
"path": "boilerplate/app/screens/DemoShowroomScreen/demos/types.ts",
"chars": 259,
"preview": "import { ReactElement } from \"react\"\nimport { TxKeyPath } from \"@/i18n\"\nimport type { Theme } from \"@/theme/types\"\n\nexpo"
},
{
"path": "boilerplate/app/screens/ErrorScreen/ErrorBoundary.tsx",
"chars": 2409,
"preview": "import { Component, ErrorInfo, ReactNode } from \"react\"\n\nimport { ErrorDetails } from \"./ErrorDetails\"\n\ninterface Props "
},
{
"path": "boilerplate/app/screens/ErrorScreen/ErrorDetails.tsx",
"chars": 2716,
"preview": "import { ErrorInfo } from \"react\"\nimport { ScrollView, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Button"
},
{
"path": "boilerplate/app/screens/LoginScreen.tsx",
"chars": 4789,
"preview": "import { ComponentType, FC, useEffect, useMemo, useRef, useState } from \"react\"\n// eslint-disable-next-line no-restricte"
},
{
"path": "boilerplate/app/screens/WelcomeScreen.tsx",
"chars": 3530,
"preview": "import { FC } from \"react\"\nimport { Image, ImageStyle, TextStyle, View, ViewStyle } from \"react-native\"\n\nimport { Button"
},
{
"path": "boilerplate/app/services/api/apiProblem.test.ts",
"chars": 1939,
"preview": "import { ApiErrorResponse } from \"apisauce\"\n\nimport { getGeneralApiProblem } from \"./apiProblem\"\n\ntest(\"handles connecti"
},
{
"path": "boilerplate/app/services/api/apiProblem.ts",
"chars": 1932,
"preview": "import { ApiResponse } from \"apisauce\"\n\nexport type GeneralApiProblem =\n /**\n * Times up.\n */\n | { kind: \"timeout\""
},
{
"path": "boilerplate/app/services/api/index.ts",
"chars": 2528,
"preview": "/**\n * This Api class lets you define an API endpoint and methods to request\n * data and process it.\n *\n * See the [Back"
},
{
"path": "boilerplate/app/services/api/types.ts",
"chars": 899,
"preview": "/**\n * These types indicate the shape of the data you expect to receive from your\n * API endpoint, assuming it's a JSON "
},
{
"path": "boilerplate/app/theme/colors.ts",
"chars": 1801,
"preview": "const palette = {\n neutral100: \"#FFFFFF\",\n neutral200: \"#F4F2F1\",\n neutral300: \"#D7CEC9\",\n neutral400: \"#B6ACA6\",\n "
},
{
"path": "boilerplate/app/theme/colorsDark.ts",
"chars": 1152,
"preview": "const palette = {\n neutral900: \"#FFFFFF\",\n neutral800: \"#F4F2F1\",\n neutral700: \"#D7CEC9\",\n neutral600: \"#B6ACA6\",\n "
},
{
"path": "boilerplate/app/theme/context.tsx",
"chars": 4464,
"preview": "import {\n createContext,\n FC,\n PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n} from \"react\""
},
{
"path": "boilerplate/app/theme/context.utils.ts",
"chars": 726,
"preview": "import type { Theme } from \"./types\"\n\nconst systemui = require(\"expo-system-ui\")\n\n/**\n * Set the system UI background co"
},
{
"path": "boilerplate/app/theme/spacing.ts",
"chars": 221,
"preview": "/**\n Use these spacings for margins/paddings and other whitespace throughout your app.\n */\nexport const spacing = {\n x"
},
{
"path": "boilerplate/app/theme/spacingDark.ts",
"chars": 439,
"preview": "const SPACING_MULTIPLIER = 1.0\n\n// This is an example of how you can have different spacing values for different themes."
},
{
"path": "boilerplate/app/theme/styles.ts",
"chars": 668,
"preview": "import { ViewStyle } from \"react-native\"\n\nimport { spacing } from \"./spacing\" // @demo remove-current-line\n\n/* Use this "
},
{
"path": "boilerplate/app/theme/theme.ts",
"chars": 600,
"preview": "import { colors as colorsLight } from \"./colors\"\nimport { colors as colorsDark } from \"./colorsDark\"\nimport { spacing as"
},
{
"path": "boilerplate/app/theme/timing.ts",
"chars": 97,
"preview": "export const timing = {\n /**\n * The duration (ms) for quick animations.\n */\n quick: 300,\n}\n"
},
{
"path": "boilerplate/app/theme/types.ts",
"chars": 2165,
"preview": "import type { StyleProp } from \"react-native\"\n\nimport { colors as colorsLight } from \"./colors\"\nimport { colors as color"
},
{
"path": "boilerplate/app/theme/typography.ts",
"chars": 1850,
"preview": "// TODO: write documentation about fonts and typography along with guides on how to add custom fonts in own\n// markdown "
},
{
"path": "boilerplate/app/utils/crashReporting.ts",
"chars": 1812,
"preview": "/**\n * If you're using Sentry\n * Expo https://docs.expo.dev/guides/using-sentry/\n */\n// import * as Sentry from \"@sent"
},
{
"path": "boilerplate/app/utils/delay.ts",
"chars": 178,
"preview": "/**\n * A \"modern\" sleep statement.\n *\n * @param ms The number of milliseconds to wait.\n */\nexport const delay = (ms: num"
},
{
"path": "boilerplate/app/utils/formatDate.ts",
"chars": 1582,
"preview": "// Note the syntax of these imports from the date-fns library.\n// If you import with the syntax: import { format } from "
},
{
"path": "boilerplate/app/utils/gestureHandler.native.ts",
"chars": 169,
"preview": "// Only import react-native-gesture-handler on native platforms\n// https://reactnavigation.org/docs/drawer-navigator/#in"
},
{
"path": "boilerplate/app/utils/gestureHandler.ts",
"chars": 262,
"preview": "// Don't import react-native-gesture-handler on web\n// https://reactnavigation.org/docs/drawer-navigator/#installation\n\n"
},
{
"path": "boilerplate/app/utils/openLinkInBrowser.ts",
"chars": 233,
"preview": "import { Linking } from \"react-native\"\n\n/**\n * Helper for opening a give URL in an external browser.\n */\nexport function"
},
{
"path": "boilerplate/app/utils/storage/index.ts",
"chars": 1530,
"preview": "import { MMKV } from \"react-native-mmkv\"\n\nexport const storage = new MMKV()\n\n/**\n * Loads a string from storage.\n *\n * @"
},
{
"path": "boilerplate/app/utils/storage/storage.test.ts",
"chars": 1730,
"preview": "import { load, loadString, save, saveString, clear, remove, storage } from \".\"\n\nconst VALUE_OBJECT = { x: 1 }\nconst VALU"
},
{
"path": "boilerplate/app/utils/useHeader.tsx",
"chars": 1649,
"preview": "import { useEffect, useLayoutEffect } from \"react\"\nimport { Platform } from \"react-native\"\nimport { useNavigation } from"
},
{
"path": "boilerplate/app/utils/useIsMounted.ts",
"chars": 456,
"preview": "import { useEffect, useCallback, useRef } from \"react\"\n/**\n * A common react custom hook to check if the component is mo"
},
{
"path": "boilerplate/app/utils/useSafeAreaInsetsStyle.ts",
"chars": 1606,
"preview": "import { Edge, useSafeAreaInsets } from \"react-native-safe-area-context\"\n\nexport type ExtendedEdge = Edge | \"start\" | \"e"
},
{
"path": "boilerplate/app.config.ts",
"chars": 1543,
"preview": "import { ExpoConfig, ConfigContext } from \"@expo/config\"\n\n/**\n * Use tsx/cjs here so we can use TypeScript for our Confi"
},
{
"path": "boilerplate/app.json",
"chars": 1568,
"preview": "{\n \"name\": \"HelloWorld\",\n \"slug\": \"HelloWorld\",\n \"scheme\": \"helloworld\",\n \"version\": \"1.0.0\",\n \"orientation\": \"port"
},
{
"path": "boilerplate/babel.config.js",
"chars": 159,
"preview": "/** @type {import('@babel/core').TransformOptions} */\nmodule.exports = function (api) {\n api.cache(true)\n return {\n "
},
{
"path": "boilerplate/eas.json",
"chars": 830,
"preview": "{\n \"cli\": {\n \"version\": \">= 3.15.1\"\n },\n \"build\": {\n \"development\": {\n \"extends\": \"production\",\n \"dis"
},
{
"path": "boilerplate/ignite/templates/component/NAME.tsx.ejs",
"chars": 993,
"preview": "---\ndestinationDir: app/components/<%= props.subdirectory %>\n---\nimport { StyleProp, TextStyle, View, ViewStyle } from \""
},
{
"path": "boilerplate/ignite/templates/navigator/NAMENavigator.tsx.ejs",
"chars": 615,
"preview": "---\ndestinationDir: app/navigators\n---\nimport { createNativeStackNavigator } from \"@react-navigation/native-stack\"\nimpor"
},
{
"path": "boilerplate/ignite/templates/screen/NAMEScreen.tsx.ejs",
"chars": 989,
"preview": "---\ndestinationDir: app/screens\npatches:\n- path: \"app/navigators/navigationTypes.ts\"\n replace: \"// IGNITE_GENERATOR_ANC"
},
{
"path": "boilerplate/index.tsx",
"chars": 388,
"preview": "import \"@expo/metro-runtime\" // this is for fast refresh on web w/o expo-router\nimport { registerRootComponent } from \"e"
},
{
"path": "boilerplate/jest.config.js",
"chars": 145,
"preview": "/** @type {import('@jest/types').Config.ProjectConfig} */\nmodule.exports = {\n preset: \"jest-expo\",\n setupFiles: [\"<roo"
},
{
"path": "boilerplate/metro.config.js",
"chars": 1308,
"preview": "/* eslint-env node */\n// Learn more https://docs.expo.io/guides/customizing-metro\nconst { getDefaultConfig } = require(\""
},
{
"path": "boilerplate/package.json",
"chars": 4018,
"preview": "{\n \"name\": \"hello-world\",\n \"version\": \"0.0.1\",\n \"private\": true,\n \"main\": \"index.tsx\",\n \"scripts\": {\n \"start\": \""
},
{
"path": "boilerplate/src/app/_layout.tsx",
"chars": 1554,
"preview": "import { useEffect, useState } from \"react\"\nimport { Slot, SplashScreen } from \"expo-router\"\nimport { useFonts } from \"@"
},
{
"path": "boilerplate/src/app/index.tsx",
"chars": 120,
"preview": "import { WelcomeScreen } from '@/screens/WelcomeScreen'\n\nexport default function Index() {\n return <WelcomeScreen />\n}\n"
},
{
"path": "boilerplate/test/i18n.test.ts",
"chars": 2736,
"preview": "import { exec } from \"child_process\"\n\nimport en from \"../app/i18n/en\"\n\n// Use this array for keys that for whatever reas"
},
{
"path": "boilerplate/test/mockFile.ts",
"chars": 102,
"preview": "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",
"chars": 1654,
"preview": "// we always make sure 'react-native' gets included first\n// eslint-disable-next-line no-restricted-imports\nimport * as "
},
{
"path": "boilerplate/test/test-tsconfig.json",
"chars": 167,
"preview": "{\n \"extends\": \"../tsconfig.json\",\n \"compilerOptions\": {\n \"noImplicitAny\": false,\n \"noUnusedLocals\": false\n },\n "
},
{
"path": "boilerplate/tsconfig.json",
"chars": 958,
"preview": "{\n \"extends\": \"expo/tsconfig.base\",\n \"compilerOptions\": {\n \"allowJs\": false,\n \"allowSyntheticDefaultImports\": tr"
},
{
"path": "boilerplate/types/lib.es5.d.ts",
"chars": 1183,
"preview": "/**\n * Fixes https://github.com/microsoft/TypeScript/issues/16655 for `Array.prototype.filter()`\n * For example, using t"
},
{
"path": "docs/Guide.md",
"chars": 5691,
"preview": "---\nsidebar_position: 2\n---\n\n# Getting Started Guide\n\n## What is Ignite?\n\nIgnite is best described as \"[Infinite Red](ht"
},
{
"path": "docs/QuickStart.md",
"chars": 2807,
"preview": "---\nsidebar_position: 3\n---\n\n# Quick Start Guide\n\n## 0. Prerequisites\n\n### For development\n\nThis guide assumes you have "
},
{
"path": "docs/README.md",
"chars": 10810,
"preview": "---\nsidebar_position: 1\n---\n\n<p align=\"center\"><img src=\"https://user-images.githubusercontent.com/1479215/206780298-2b9"
},
{
"path": "docs/_category_.json",
"chars": 264,
"preview": "{\n \"label\": \"ignite-cli\",\n \"link\": null,\n \"customProps\": {\n \"description\": \"\",\n \"projectName\": \"ignite-cli\",\n "
},
{
"path": "docs/boilerplate/Boilerplate.md",
"chars": 4938,
"preview": "---\nsidebar_position: 1\n---\n\n# Ignite's Boilerplate\n\n:::tip\nA \"boilerplate\" project is one that you can use as a startin"
},
{
"path": "docs/boilerplate/_category_.json",
"chars": 106,
"preview": "{\n \"label\": \"Boilerplate\",\n \"position\": 2,\n \"link\": {\n \"type\": \"doc\",\n \"id\": \"Boilerplate\"\n }\n}\n"
},
{
"path": "docs/boilerplate/android.md",
"chars": 829,
"preview": "---\ntitle: android\nsidebar_position: 5\n---\n\n# `android`\n\nIf you choose the `manual` workflow option when spinning up a n"
},
{
"path": "docs/boilerplate/app/_category_.json",
"chars": 113,
"preview": "{\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",
"chars": 1000,
"preview": "---\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 s"
},
{
"path": "docs/boilerplate/app/app.tsx.md",
"chars": 758,
"preview": "# 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 wi"
},
{
"path": "docs/boilerplate/app/components/AutoImage.md",
"chars": 2946,
"preview": "---\nsidebar_position: 30\n---\n\n# AutoImage\n\nIgnite's `AutoImage` Component is an enhanced version of the built-in React N"
},
{
"path": "docs/boilerplate/app/components/Button.md",
"chars": 5983,
"preview": "---\nsidebar_position: 31\n---\n\n# Button\n\nThe `Button` component is a wrapper around the [`Pressable`](https://reactnative"
},
{
"path": "docs/boilerplate/app/components/Card.md",
"chars": 7089,
"preview": "---\nsidebar_position: 32\n---\n\n# Card\n\nThe `Card` component is intended to be used for vertically aligned related content"
},
{
"path": "docs/boilerplate/app/components/Checkbox.md",
"chars": 404,
"preview": "---\nsidebar_position: 32\n---\n\nimport ToggleProps from './\\_toggle_props.mdx';\n\n# Checkbox\n\nThe `Checkbox` component prov"
},
{
"path": "docs/boilerplate/app/components/Components.md",
"chars": 6367,
"preview": "---\nsidebar_position: 3\n---\n\n# Ignite Built-in Components\n\nIgnite comes with a number of customizable built-in React Nat"
},
{
"path": "docs/boilerplate/app/components/EmptyState.md",
"chars": 6562,
"preview": "---\nsidebar_position: 33\n---\n\n# EmptyState\n\nThe `EmptyState` component is to be used when there is no data to display, u"
},
{
"path": "docs/boilerplate/app/components/Header.md",
"chars": 10645,
"preview": "---\nsidebar_position: 34\n---\n\n# Header\n\nThe `Header` component is a component that will appear at the top of your screen"
},
{
"path": "docs/boilerplate/app/components/Icon.md",
"chars": 3133,
"preview": "---\nsidebar_position: 35\n---\n\n# Icon\n\nIgnite's `Icon` and `PressableIcon` Components render an icon using predefined ico"
},
{
"path": "docs/boilerplate/app/components/ListItem.md",
"chars": 3673,
"preview": "---\nsidebar_position: 36\n---\n\n# ListItem\n\nThe `ListItem` component is a component that is used to display a single item "
},
{
"path": "docs/boilerplate/app/components/Radio.md",
"chars": 214,
"preview": "---\nsidebar_position: 37\n---\n\nimport ToggleProps from './\\_toggle_props.mdx';\n\n# Radio\n\nThe `Radio` component provides a"
},
{
"path": "docs/boilerplate/app/components/Screen.md",
"chars": 5665,
"preview": "---\nsidebar_position: 38\n---\n\n# Screen\n\nThis is a component that renders a screen. It is used to wrap your entire screen"
},
{
"path": "docs/boilerplate/app/components/Switch.md",
"chars": 489,
"preview": "---\nsidebar_position: 38\n---\n\nimport ToggleProps from './\\_toggle_props.mdx';\n\n# Switch\n\nThe `Switch` component provides"
},
{
"path": "docs/boilerplate/app/components/Text.md",
"chars": 3691,
"preview": "---\nsidebar_position: 39\n---\n\n# Text\n\nIgnite's `Text` Component is an enhanced version of the built-in React Native [`Te"
},
{
"path": "docs/boilerplate/app/components/TextField.md",
"chars": 8309,
"preview": "---\nsidebar_position: 40\n---\n\n# TextField\n\nIgnite's `TextField` Component is an enhanced version of the built-in React N"
},
{
"path": "docs/boilerplate/app/components/_category_.json",
"chars": 125,
"preview": "{\n \"label\": \"components\",\n \"position\": 5,\n \"link\": {\n \"type\": \"doc\",\n \"id\": \"Components\"\n },\n \"collapsed\": tr"
},
{
"path": "docs/boilerplate/app/components/_toggle_props.mdx",
"chars": 5828,
"preview": "import CodeBlock from \"@theme/CodeBlock\"\n\n## Toggle Props\n\n` hook that can be used to easily set the Header of a react-n"
},
{
"path": "docs/boilerplate/app/utils/useSafeAreaInsetsStyle.ts.md",
"chars": 1221,
"preview": "---\nsidebar_position: 20\n---\n\n# useSafeAreaInsetsStyle\n\nThe `useSafeAreaInsetsStyle()` hook can be used to create a safe"
},
{
"path": "docs/boilerplate/app.json.md",
"chars": 763,
"preview": "---\ntitle: app.json / app.config.js\nsidebar_position: 60\n---\n\n# app.json / app.config.js\n\nThe app.json & app.config.js f"
},
{
"path": "docs/boilerplate/assets.md",
"chars": 528,
"preview": "---\ntitle: assets\nsidebar_position: 20\n---\n\n# Assets Folder\n\nThe `assets` folder is for icons, images, fonts, and other "
},
{
"path": "docs/boilerplate/eas.json.md",
"chars": 1543,
"preview": "---\ntitle: eas.json\nsidebar_position: 70\n---\n\n# eas.json\n\n`eas.json` is the configuration file for [Expo Application Ser"
}
]
// ... and 76 more files (download for full content)
About this extraction
This page contains the full source code of the infinitered/ignite GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 276 files (983.4 KB), approximately 292.4k tokens, and a symbol index with 309 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.