diff --git a/.circleci/comment-artifacts.sh b/.circleci/comment-artifacts.sh deleted file mode 100644 index 7991e99012..0000000000 --- a/.circleci/comment-artifacts.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -URL="https://callstack-github-bot.herokuapp.com/comment" -LINK="https://output.circle-artifacts.com/output/job/$CIRCLE_WORKFLOW_JOB_ID/artifacts/$CIRCLE_NODE_INDEX/docs/index.html" -TEMPLATE="Hey @{{user.login}}, thank you for your pull request 🤗. The documentation from this branch can be viewed [here]($LINK)." - -read -r -d '' DATA << EOM -{ - "pull_request": "$CIRCLE_PULL_REQUEST", - "template": "$TEMPLATE", - "test": { - "type": "string", - "data": "The documentation from this branch can be viewed" - }, - "update": true -} -EOM - -curl \ - -H "Content-Type: application/json" \ - -d "$DATA" \ - -X POST $URL diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index c4053c5f19..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,154 +0,0 @@ -version: 2.1 - -executors: - default: - docker: - - image: cimg/node:20.19.0 - working_directory: ~/react-native-paper - -commands: - attach_project: - steps: - - attach_workspace: - at: ~/react-native-paper - -jobs: - install-dependencies: - executor: default - steps: - - checkout - - attach_project - - restore_cache: - keys: - - dependencies-{{ checksum "package.json" }} - - dependencies- - - restore_cache: - keys: - - dependencies-example-{{ checksum "example/package.json" }} - - dependencies-example- - - run: - name: Install dependencies - command: | - sudo corepack enable - yarn --cwd example --immutable - yarn --cwd docs --immutable - yarn --immutable - - save_cache: - key: dependencies-{{ checksum "package.json" }} - paths: - - node_modules - - save_cache: - key: dependencies-docs-{{ checksum "docs/package.json" }} - paths: - - docs/node_modules - - save_cache: - key: dependencies-example-{{ checksum "example/package.json" }} - paths: - - example/node_modules - - persist_to_workspace: - root: . - paths: - - . - lint: - executor: default - steps: - - attach_project - - run: - name: Lint files - command: | - yarn lint-no-fix - - typescript: - executor: default - steps: - - attach_project - - run: - name: Typecheck files - command: | - yarn typescript - - unit-tests: - executor: default - steps: - - attach_project - - restore_cache: - name: Restore Jest Cache - keys: - - jest-cache-{{ .Branch }} - - jest-cache - - run: - name: Run unit tests - command: | - yarn test --maxWorkers=2 --coverage - - store_artifacts: - path: coverage - destination: coverage - - save_cache: - paths: - - ./cache/jest - key: jest-cache-{{ .Branch }} - - build-package: - executor: default - steps: - - attach_project - - run: - name: Build package - command: | - yarn prepack - node ./scripts/typescript-output-lint - - build-docs: - executor: default - steps: - - attach_project - - run: - name: Build docs - command: | - export DOCUSAURUS_BASE_URL="/output/job/$CIRCLE_WORKFLOW_JOB_ID/artifacts/$CIRCLE_NODE_INDEX/docs/" - cd docs && yarn build && cd .. - bash .circleci/comment-artifacts.sh - - store_artifacts: - path: docs/build - destination: docs - - deploy-docs: - executor: default - steps: - - checkout - - attach_project - - add_ssh_keys: - fingerprints: - - '2f:b4:ed:e1:25:88:5c:4a:03:c2:16:40:56:70:29:29' - - run: - name: Deploy docs to GitHub pages - command: | - bash .circleci/deploy-docs.sh - -workflows: - build-and-test: - jobs: - - install-dependencies - - lint: - requires: - - install-dependencies - - typescript: - requires: - - install-dependencies - - unit-tests: - requires: - - install-dependencies - - build-package: - requires: - - install-dependencies - - build-docs: - requires: - - install-dependencies - filters: - branches: - ignore: main - - deploy-docs: - filters: - branches: - only: - - main diff --git a/.circleci/deploy-docs.sh b/.circleci/deploy-docs.sh deleted file mode 100755 index 5546f8bb2c..0000000000 --- a/.circleci/deploy-docs.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash - -# Based on domenic's tutorial: https://gist.github.com/domenic/ec8b0fc8ab45f39403dd - -set -euxo pipefail # Exit with nonzero exit code if anything fails - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -SOURCE_BRANCH="main" -TARGET_BRANCH="gh-pages" - -git checkout $SOURCE_BRANCH - -cd $DIR/.. - -# Install React Native Paper dependencies for examples -yarn - -cd docs - -# Save some useful information -REPO=`git config remote.origin.url` -SSH_REPO=${REPO/https:\/\/github.com\//git@github.com:} -SHA=`git rev-parse --verify HEAD` - -# Clone the existing gh-pages for this repo into dist/ -# Create a new empty branch if gh-pages doesn't exist yet (should only happen on first deploy) -git clone $REPO dist -cd dist -git checkout $TARGET_BRANCH || git checkout --orphan $TARGET_BRANCH -cd .. - -# Clean existing dist/ contents -rm -rf dist/**/* || : -rm -f dist/*.{html,css,js,json,map,xml} || : -rmdir dist/* || : - -# Run our build script. -yarn -yarn build - -# Move the built docs to cloned `gh-pages` directory -cp -R build/. dist - -rm -rf build - -# Change directory to the one using `gh-pages` branch -cd dist - -# Configure git. -git config user.name "$COMMIT_AUTHOR_NAME" -git config user.email "$COMMIT_AUTHOR_EMAIL" - -git add -A . - -# If there are no changes to the compiled dist (e.g. this is a README update) then just bail. -if git diff --cached --quiet; then - echo "No changes to the output on this push; exiting." - exit 0 -fi - -# Commit the "changes", i.e. the new version. -# The delta will show diffs between new and old versions. -git commit -m "Deploy to GitHub Pages: ${SHA}" - -# Now that we're all set up, we can push. -git push $SSH_REPO $TARGET_BRANCH - -# Change back to original branch -cd .. -git checkout $SOURCE_BRANCH -yarn diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 18c50df521..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,12 +0,0 @@ -node_modules/ -coverage/ -docs/dist/ -docs/build/ -docs/public/ -docs/static/ -web-build/ -testSetup.js -__fixtures__/ - -# generated by bob -lib/ diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 380df91c1e..0000000000 --- a/.eslintrc +++ /dev/null @@ -1,77 +0,0 @@ -{ - "env": { - "browser": true - }, - - "extends": "@callstack", - - "plugins": ["eslint-plugin-local-rules"], - - "rules": { - "local-rules/no-import-react-forwardref": "error", - "local-rules/no-react-forwardref-usage": "error", - - "one-var": "off", - "no-multi-assign": "off", - "no-nested-ternary": "off", - "no-undef": "off", - "global-require": "off", - - "import/no-extraneous-dependencies": "off", - "import/first": "off", - "import/order": [ - "error", - { - "groups": [ - ["external", "builtin"], - "internal", - ["sibling", "parent"], - "index" - ], - "pathGroups": [ - { - "pattern": "@(react|react-native)", - "group": "external", - "position": "before" - }, - { - "pattern": "@src/**", - "group": "internal" - } - ], - "pathGroupsExcludedImportTypes": ["internal", "react"], - "newlines-between": "always", - "alphabetize": { - "order": "asc", - "caseInsensitive": true - } - } - ], - - "react-native/no-unused-styles": "error", - "react-native/split-platform-components": "off", - "react-native/no-raw-text": "off", - "react-native-a11y/has-valid-accessibility-descriptors": "off" - }, - - "overrides": [ - { - "files": ["*.test.js", "*.test.tsx"], - "rules": { - "local-rules/no-react-forwardref-usage": "off" - } - } - ], - - "settings": { - "import/extensions": [".js", ".ts", ".tsx"], - "import/parsers": { - "@typescript-eslint/parser": [".ts", ".tsx"] - }, - "import/resolver": { - "node": { - "extensions": [".js", ".ts", ".tsx"] - } - } - } -} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..6313b56c57 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 0000000000..66e709c9e8 --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,36 @@ +name: Setup +description: Setup Node.js and install dependencies + +runs: + using: composite + steps: + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + with: + node-version-file: .nvmrc + + - name: Restore dependencies + id: yarn-cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae + with: + path: | + **/node_modules + .yarn/install-state.gz + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }} + restore-keys: | + ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + ${{ runner.os }}-yarn- + + - name: Install dependencies + if: steps.yarn-cache.outputs.cache-hit != 'true' + run: yarn install --immutable + shell: bash + + - name: Cache dependencies + if: steps.yarn-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae + with: + path: | + **/node_modules + .yarn/install-state.gz + key: ${{ steps.yarn-cache.outputs.cache-primary-key }} diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..1bc6cef610 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,22 @@ +# General Code Review Standards + +## Review Priorities + +- Prioritize correctness, regressions, missing tests, and API design issues. +- Check that the behavior scope matches the stated intent. +- Verify claims about external standards, platform conventions, accessibility, and design systems against the implementation. +- Avoid asking for unrelated refactors, stylistic churn, or backwards compatibility unless required by the change. + +## Public APIs + +- Review new or modified public APIs for visibility, naming, defaults, customizability, and extensibility. +- Ensure public APIs and exported types do not leak implementation details. + +## Dependencies + +- For dependencies added for individual components, evaluate bundle size and platform impact. Prefer existing capabilities when they meet the requirements. + +## Validation + +- Call out missing tests when the changed behavior is risky. +- For visual changes, check before and after screenshots or videos for each affected platform and a nearby unaffected case. diff --git a/.github/instructions/code.instructions.md b/.github/instructions/code.instructions.md new file mode 100644 index 0000000000..9f0d264fef --- /dev/null +++ b/.github/instructions/code.instructions.md @@ -0,0 +1,66 @@ +--- +applyTo: '**/*.{ts,tsx,js,jsx}' +--- + +# TypeScript and React Review Instructions + +## Code Quality + +- Flag unnecessary abstractions, one-off helpers, excessive indirection, and defensive code without a real use case. +- Flag comments that restate obvious code instead of explaining non-obvious intent. +- Check that changes follow existing project style, structure, naming, and local patterns. + +## TypeScript + +- Flag uses of `any`, unsafe `as` assertions, and non-null assertions (`!`) unless clearly justified. +- Avoid broad types such as `object`, `Function`, `{}`, and `Record` unless the broadness is intentional. +- Do not accept `@ts-ignore`. +- Only accept `@ts-expect-error` when it has a comment explaining why the error is expected and cannot be resolved. +- Prefer simple types over complex conditional types, overloads, or type logic when simpler approaches work. +- Prefer `type` aliases over `interface` unless there is a specific reason to use `interface`. +- Reuse React Native prop and ref types where they describe the component's contract. + +## React + +- Check that hooks follow the Rules of Hooks. +- Check dependency arrays for unstable values. +- Ensure components do not perform side effects during rendering. +- Avoid storing values in state when they can be derived from props or existing state. +- Avoid multiple sources of truth for the same state. +- Avoid `React.Children`, `cloneElement`, and reading React elements directly unless there is a strong justification. +- Treat memoization as something that needs a reason: expensive calculation, stable identity requirement, or measured performance need. +- Check async effects for cancellation or race conditions when relevant. + +## Component APIs and Styles + +- Prefer existing native props or `style` over new props when they express the required behavior. +- Verify that components forward relevant native props and events without changing their meaning. +- Ensure controlled values have callbacks to update them. Support uncontrolled input when controlled rendering causes a demonstrated performance or reliability problem. +- Merge consumer styles after defaults for supported overrides. Check that computed styles preserve supplied padding, colors, dimensions, and other supported values. +- Expose test IDs for public interaction or content. Avoid IDs on internal wrappers solely for tests and default IDs that can collide across component instances. + +## Design and Accessibility + +- Compare component states, dimensions, icons, shapes, ripple effects, and disabled appearance with the Material Design specification. +- Use theme defaults and named tokens for colors, spacing, sizing, and motion where available. Check spacing against the design scale. +- Check labels, roles, focus order, and loading states. Verify keyboard, mouse, and touch interaction where applicable. +- Ensure disabled controls expose their state to assistive technology and prevent activation. Preserve appropriate focus when a control becomes unavailable after activation. +- Check that dismissing temporary UI restores focus to an appropriate element. +- Ensure visual changes do not make noninteractive content pressable. + +## Platforms + +- Check affected behavior on iOS, Android, and web, including positioning and interaction within scrolling containers and safe areas. +- Use direct `Platform.OS` checks or `Platform.select` so build tools can remove unused branches. Use `.native` files for native/web splits. +- Use CSS for web behavior it already supports instead of recreating it with JavaScript or layout measurements. +- Keep browser APIs and layout measurements out of server rendering. Verify that server output and the initial client render match. + +## Animations and Layout + +- Check for unnecessary layout shifts. +- Prefer animating only `transform` and `opacity` unless another property is required. +- Verify that animations handle rapid state changes, gestures, interruption, and cancellation. Stale completion callbacks must not leave rendered state, focus, or component callbacks inconsistent. +- Check that duration and easing feel appropriate for the platform and interaction. +- Keep continuous gesture work off the JavaScript thread when responsiveness requires it, using Gesture Handler and Reanimated where appropriate. +- Measure dimensions near animation start when this provides current values and avoids extra state. +- Verify text and UI elements do not overlap or overflow at supported screen sizes. diff --git a/.github/instructions/docs.instructions.md b/.github/instructions/docs.instructions.md new file mode 100644 index 0000000000..e0c0fd307b --- /dev/null +++ b/.github/instructions/docs.instructions.md @@ -0,0 +1,11 @@ +--- +applyTo: '**/*.{md,mdx}' +--- + +# Documentation Review Instructions + +- Verify that examples match the actual API, are self-contained, and focus on the relevant behavior. +- Check that changed headings follow the document hierarchy. +- Verify that changed internal links and anchors lead to the intended content. +- Ensure documentation explains public behavior. +- Use diffs or before and after code snippets in migration guides when they help explain the change. diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md new file mode 100644 index 0000000000..caa914bd24 --- /dev/null +++ b/.github/instructions/tests.instructions.md @@ -0,0 +1,12 @@ +--- +applyTo: '**/*.{test,spec}.{ts,tsx,js,jsx}' +--- + +# Test Review Instructions + +- Check that tests verify public behavior, including rendered output, interaction, callbacks, and warnings. Flag assertions about private properties, context internals, or exact internal style values. +- Test internal helpers directly only when their logic is complex enough to justify separate coverage. +- Prefer accessibility queries, then test IDs for public interaction or content. +- Check that test titles describe user-facing behavior. +- Check coverage of happy paths, edge cases, and error states. +- Flag unnecessary mocking. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index f14817e3d1..4d825afb12 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,14 +3,16 @@ ### Motivation - + ### Related issue -### Test plan +### Screenshots / Videos + + - +### Test plan - + diff --git a/.github/workflows/build-example-apps.yml b/.github/workflows/build-example-apps.yml new file mode 100644 index 0000000000..64bc9fc819 --- /dev/null +++ b/.github/workflows/build-example-apps.yml @@ -0,0 +1,149 @@ +name: Build example apps + +on: + push: + branches: + - main + paths: + - '**' + - '!docs/**' + - '!**/*.md' + - '!**/__tests__/**' + - '!jest/**' + - '!.github/workflows/**' + - '.github/workflows/build-example-apps.yml' + pull_request: + paths: + - '**' + - '!docs/**' + - '!**/*.md' + - '!**/__tests__/**' + - '!jest/**' + - '!.github/workflows/**' + - '.github/workflows/build-example-apps.yml' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + build-android: + name: Build Android + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Calculate fingerprint + id: fingerprint + working-directory: example + run: | + fingerprint=$(yarn expo-updates fingerprint:generate --platform android | jq -er .hash) + echo "fingerprint=$fingerprint" >> "$GITHUB_OUTPUT" + + - name: Cache release build + id: cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: react-native-paper-example.apk + key: android-${{ steps.fingerprint.outputs.fingerprint }}-${{ hashFiles('.github/workflows/build-example-apps.yml') }} + + - name: Setup JDK + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 + with: + distribution: temurin + java-version: 17 + + - name: Build Android app + if: steps.cache.outputs.cache-hit != 'true' + working-directory: example + run: | + yarn expo prebuild --platform android --no-install + cd android + ./gradlew :app:assembleRelease -Dorg.gradle.jvmargs=-XX:MaxMetaspaceSize=1g -PreactNativeArchitectures=arm64-v8a,x86_64 + mv app/build/outputs/apk/release/app-release.apk ../../react-native-paper-example.apk + + - name: Repack cached build + if: steps.cache.outputs.cache-hit == 'true' + working-directory: example + run: | + yarn dlx @expo/repack-app@0.10.3 \ + --platform android \ + --source-app ../react-native-paper-example.apk \ + --output ../react-native-paper-example.apk + + - name: Upload APK + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + path: react-native-paper-example.apk + archive: false + retention-days: 30 + + build-ios: + name: Build iOS + runs-on: macos-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Calculate fingerprint + id: fingerprint + working-directory: example + run: | + fingerprint=$(yarn expo-updates fingerprint:generate --platform ios | jq -er .hash) + echo "fingerprint=$fingerprint" >> "$GITHUB_OUTPUT" + + - name: Cache release build + id: cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: react-native-paper-example.app + key: ios-${{ steps.fingerprint.outputs.fingerprint }}-${{ hashFiles('.github/workflows/build-example-apps.yml') }} + + - name: Build iOS app + if: steps.cache.outputs.cache-hit != 'true' + working-directory: example + run: | + yarn expo prebuild --platform ios --no-install + cd ios + pod install + xcodebuild build \ + -workspace ReactNativePaperExample.xcworkspace \ + -scheme ReactNativePaperExample \ + -configuration Release \ + -destination 'generic/platform=iOS Simulator' \ + -derivedDataPath build \ + ARCHS=arm64 + mv build/Build/Products/Release-iphonesimulator/ReactNativePaperExample.app ../../react-native-paper-example.app + + - name: Repack cached build + if: steps.cache.outputs.cache-hit == 'true' + working-directory: example + run: | + mv ../react-native-paper-example.app "$RUNNER_TEMP/cached.app" + yarn dlx @expo/repack-app@0.10.3 \ + --platform ios \ + --source-app "$RUNNER_TEMP/cached.app" \ + --output ../react-native-paper-example.app + + - name: Archive app + run: ditto -c -k --keepParent react-native-paper-example.app react-native-paper-example.app.zip + + - name: Upload app + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + path: react-native-paper-example.app.zip + archive: false + retention-days: 30 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..e3dccbb3d5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,113 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Lint + run: yarn lint + + typecheck: + name: Typecheck + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Check types + run: yarn typecheck + + unit-tests: + name: Unit tests + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Restore Jest cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ./cache/jest + key: jest-cache-${{ runner.os }}-${{ github.ref_name }} + restore-keys: jest-cache-${{ runner.os }}- + + - name: Run unit tests + run: yarn test --maxWorkers=2 --coverage + + - name: Upload coverage + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: always() + with: + name: coverage-${{ runner.os }} + path: coverage + + build-package: + name: Build package + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Build package + run: yarn prepack + + build-example: + name: Build example + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Build example + run: yarn example expo export --platform all + + build-docs: + name: Build docs + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup + uses: ./.github/actions/setup + + - name: Build docs + run: yarn --cwd docs build + + - name: Upload docs + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: docs + path: docs/build diff --git a/.github/workflows/contributor-label.yml b/.github/workflows/contributor-label.yml new file mode 100644 index 0000000000..f5fcb36767 --- /dev/null +++ b/.github/workflows/contributor-label.yml @@ -0,0 +1,80 @@ +name: Contributor Label + +on: + pull_request_target: + types: [opened, reopened] + +permissions: + issues: write + pull-requests: write + +jobs: + label: + name: Add contributor label + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 + id: app-token + with: + app-id: ${{ secrets.ORG_MEMBERSHIP_APP_ID }} + private-key: ${{ secrets.ORG_MEMBERSHIP_APP_PRIVATE_KEY }} + owner: callstack-internal + permission-members: read + + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + ORG_MEMBERSHIP_TOKEN: ${{ steps.app-token.outputs.token }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const owner = context.repo.owner; + const repo = context.repo.repo; + + const label = 'Contributor: Internal'; + + const pullRequest = context.payload.pull_request; + + if (pullRequest.labels.some(({ name }) => name === label)) { + return; + } + + const username = pullRequest.user.login; + + const { data } = + await github.rest.repos.getCollaboratorPermissionLevel({ + owner, + repo, + username, + }); + + let isInternal = + ['MEMBER', 'OWNER'].includes(pullRequest.author_association) || + ['admin', 'maintain', 'write'].includes(data.permission); + + if (!isInternal) { + try { + const internalGithub = getOctokit(process.env.ORG_MEMBERSHIP_TOKEN); + + await internalGithub.rest.orgs.checkMembershipForUser({ + org: 'callstack-internal', + username, + }); + + isInternal = true; + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + } + + if (!isInternal) { + return; + } + + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: pullRequest.number, + labels: [label], + }); diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000000..a8e11ea034 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,49 @@ +name: Deploy docs + +on: + push: + branches: + - main + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + deploy: + name: Deploy docs + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Setup + uses: ./.github/actions/setup + + - name: Generate 5.x docs + run: yarn --cwd docs generate:5x + + - name: Build docs + run: yarn --cwd docs build + + - name: Configure Pages + uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 + with: + path: docs/build + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 diff --git a/.github/workflows/publish-each-pr.yml b/.github/workflows/publish-each-pr.yml deleted file mode 100644 index 0fa519e55f..0000000000 --- a/.github/workflows/publish-each-pr.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: Expo Preview -on: [pull_request] - -permissions: - contents: read - pull-requests: write - -jobs: - publish: - name: Install and publish - runs-on: ubuntu-latest - if: github.event.pull_request.head.repo.full_name == 'callstack/react-native-paper' - steps: - - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version-file: .nvmrc - - - name: Setup Expo - uses: expo/expo-github-action@d300b960e9f91a8c59b2aaca92e89ad70b0785ac # v7 - with: - eas-version: latest - token: ${{ secrets.EXPO_TOKEN }} - - - name: Restore dependencies - id: yarn-cache - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }} - restore-keys: | - ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} - ${{ runner.os }}-yarn- - - - name: Install dependencies - if: steps.yarn-cache.outputs.cache-hit != 'true' - run: | - sudo corepack enable - yarn --cwd example --immutable - yarn --immutable - - - name: Cache dependencies - if: steps.yarn-cache.outputs.cache-hit != 'true' - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4 - with: - path: '**/node_modules' - key: ${{ steps.yarn-cache.outputs.cache-primary-key }} - - - name: Publish Expo app - working-directory: ./example - run: CI=1 rm -rf android && eas update --branch=pr-${{ github.event.number }} --message="$(git log -1 --pretty=%B)" - - - name: Get expo config - working-directory: ./example - id: expo - run: echo "EXPO_CONFIG=$(npx expo config --json)" >> $GITHUB_OUTPUT - - - name: Comment on PR - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const config = JSON.parse('${{ steps.expo.outputs.EXPO_CONFIG }}'); - - const { sdkVersion } = config; - const { projectId } = config.extra.eas; - - const channel = 'pr-${{ github.event.number }}'; - - const url = `https://expo.dev/@react-native-paper/react-native-paper-example?serviceType=eas&distribution=expo-go&scheme=exp+react-native-paper-example&channel=${channel}&sdkVersion=${sdkVersion}`; - const body = `The mobile version of example app from this branch is ready! You can [see it here.](${url}) - - `; - - const comments = await github.rest.issues.listComments({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - }); - - if (comments.data.some(comment => comment.body === body)) { - return; - } - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body - }) diff --git a/.github/workflows/publish-example-app.yml b/.github/workflows/publish-example-app.yml new file mode 100644 index 0000000000..7c9a86531f --- /dev/null +++ b/.github/workflows/publish-example-app.yml @@ -0,0 +1,206 @@ +name: Publish example app + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: Release tag to publish the example app for, e.g. v6.0.0 + required: true + platform: + description: Platform to build. Defaults to all. + required: false + default: all + type: choice + options: + - all + - ios + - android + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + check: + name: Check release is on main + runs-on: ubuntu-latest + env: + TAG: ${{ github.event.release.tag_name || inputs.tag }} + outputs: + on-main: ${{ steps.check.outputs.on-main }} + sha: ${{ steps.check.outputs.sha }} + tag: ${{ env.TAG }} + profile: ${{ steps.check.outputs.profile }} + steps: + - id: check + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + run: | + status=$(gh api "repos/$GH_REPO/compare/main...$TAG" --jq .status) + + if [ "$status" = "identical" ] || [ "$status" = "behind" ]; then + sha=$(gh api "repos/$GH_REPO/commits/$TAG" --jq .sha) + echo "on-main=true" >> "$GITHUB_OUTPUT" + echo "sha=$sha" >> "$GITHUB_OUTPUT" + else + echo "on-main=false" >> "$GITHUB_OUTPUT" + echo "::warning::$TAG is not on main ($status), nothing to deploy." + echo "$TAG is not on main ($status), nothing to deploy." >> "$GITHUB_STEP_SUMMARY" + fi + + if [ "${TAG#*-}" != "$TAG" ]; then + echo "profile=preview" >> "$GITHUB_OUTPUT" + else + echo "profile=production" >> "$GITHUB_OUTPUT" + fi + + publish: + name: Build and submit example app + needs: check + if: needs.check.outputs.on-main == 'true' + runs-on: ubuntu-latest + concurrency: + # One queue per channel, so a pending preview run doesn't cancel a production one + group: publish-example-app-${{ needs.check.outputs.profile }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ needs.check.outputs.sha }} + + - name: Setup + uses: ./.github/actions/setup + + - name: Setup Expo + uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8.2.1 + with: + eas-version: 24.12.1 + token: ${{ secrets.EXPO_TOKEN }} + + - name: Check Expo authentication + working-directory: ./example + run: | + if ! eas whoami; then + echo "::error::Could not authenticate with Expo, check EXPO_TOKEN." + exit 1 + fi + + - name: Set example app version from the release tag + working-directory: ./example + env: + TAG: ${{ needs.check.outputs.tag }} + run: | + version="${TAG#v}" + version="${version%%-*}" + + if ! echo "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "::error::Could not read a major.minor.patch version from $TAG." + exit 1 + fi + + jq --arg version "$version" '.expo.version = $version' app.json > app.json.tmp + mv app.json.tmp app.json + + - name: Decide between an update and a build + id: decide + working-directory: ./example + env: + PROFILE: ${{ needs.check.outputs.profile }} + PLATFORM: ${{ inputs.platform || 'all' }} + run: | + platforms="$PLATFORM" + if [ "$PLATFORM" = "all" ]; then + platforms="ios android" + fi + + build=() + update=() + for platform in $platforms; do + hash=$(npx expo-updates fingerprint:generate --platform "$platform" | jq -er .hash) + + existing=$(eas build:list \ + --platform "$platform" \ + --channel "$PROFILE" \ + --fingerprint-hash "$hash" \ + --status finished \ + --limit 1 \ + --json \ + --non-interactive | jq 'if type == "array" then length else "notanarray" end') + + case "$existing" in + ''|*[!0-9]*) + echo "::error::Could not read builds for $platform." + exit 1 + ;; + esac + + echo "$platform: fingerprint $hash, $existing matching build(s)" + if [ "$existing" -eq 0 ]; then + build+=("$platform") + else + update+=("$platform") + fi + done + + # EAS takes a single platform or "all" + to_platform() { + case $# in + 0) echo "" ;; + 1) echo "$1" ;; + *) echo all ;; + esac + } + + echo "build=$(to_platform "${build[@]}")" >> "$GITHUB_OUTPUT" + echo "update=$(to_platform "${update[@]}")" >> "$GITHUB_OUTPUT" + echo "Building: ${build[*]:-none}. Updating: ${update[*]:-none}." + + - name: Publish an update + # Before the build, so a slow or timed-out build can't hold up the update + if: steps.decide.outputs.update != '' + working-directory: ./example + env: + PROFILE: ${{ needs.check.outputs.profile }} + PLATFORM: ${{ steps.decide.outputs.update }} + TAG: ${{ needs.check.outputs.tag }} + run: | + eas update \ + --channel "$PROFILE" \ + --platform "$PLATFORM" \ + --message "$TAG" \ + --non-interactive + + echo "Published an OTA update for $TAG for $PLATFORM on the $PROFILE channel." >> "$GITHUB_STEP_SUMMARY" + + - name: Build and submit to the app stores + # Don't let a failed update for one platform skip the other's build + if: ${{ !cancelled() && steps.decide.outputs.build != '' }} + working-directory: ./example + env: + PROFILE: ${{ needs.check.outputs.profile }} + PLATFORM: ${{ steps.decide.outputs.build }} + TAG: ${{ needs.check.outputs.tag }} + run: | + eas build \ + --platform "$PLATFORM" \ + --profile "$PROFILE" \ + --auto-submit-with-profile "$PROFILE" \ + --non-interactive + + echo "Built and submitted $TAG for $PLATFORM using the $PROFILE profile." >> "$GITHUB_STEP_SUMMARY" + + - name: Report failure + if: failure() || cancelled() + env: + TAG: ${{ needs.check.outputs.tag }} + run: | + echo "::error::Example app deployment failed for $TAG. The release itself is fine, only the demo app did not update." + echo "Check EAS and the stores before re-running, a build may still be running or already submitted. Re-running before it finishes starts a second build." >> "$GITHUB_STEP_SUMMARY" + echo "If the build finished but the store upload failed, resubmit that build with \`eas submit --id \`. Re-running this workflow will publish an OTA update instead." >> "$GITHUB_STEP_SUMMARY" + echo "If only the update failed, re-running is safe." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/pull-request-checks.yml b/.github/workflows/pull-request-checks.yml new file mode 100644 index 0000000000..350cd9a621 --- /dev/null +++ b/.github/workflows/pull-request-checks.yml @@ -0,0 +1,97 @@ +name: Pull Request Checks + +on: + pull_request_target: + types: [opened, edited, reopened] + +permissions: + issues: write + pull-requests: write + +concurrency: + group: pull-request-checks-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + check: + name: Check description and visual evidence + runs-on: ubuntu-latest + steps: + - name: Check pull request details + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const MAX_PR_BODY_LENGTH = 1000; + const MARKER = ''; + + const { owner, repo } = context.repo; + + const issueNumber = context.payload.pull_request.number; + const body = context.payload.pull_request.body || ''; + + const problems = []; + + if (Array.from(body).length > MAX_PR_BODY_LENGTH) { + problems.push( + `- The description is too long. Please keep it under ${MAX_PR_BODY_LENGTH} characters.` + ); + } + + if ( + !/(?:https?:\/\/[^\s)"']+\.(?:png|jpe?g|gif|webp|avif|mp4|mov|webm|m4v)(?:[?#][^\s)"']*)?|https?:\/\/github\.com\/user-attachments\/assets\/[^\s)"']+)/i.test( + body + ) + ) { + problems.push( + '- Screenshot or video evidence is missing. Make sure to include one if it affects the UI.' + ); + } + + const existingComment = ( + await github.paginate(github.rest.issues.listComments, { + owner, + repo, + issue_number: issueNumber, + per_page: 100, + }) + ).find( + (comment) => + comment.user?.login === 'github-actions[bot]' && + comment.body?.includes(MARKER) + ); + + if (problems.length === 0) { + if (existingComment) { + await github.rest.issues.deleteComment({ + owner, + repo, + comment_id: existingComment.id, + }); + } + + return; + } + + const commentBody = [ + MARKER, + 'Found potential problems with the pull request:', + '', + problems.join('\n'), + ].join('\n'); + + if (existingComment) { + await github.rest.issues.updateComment({ + owner, + repo, + comment_id: existingComment.id, + body: commentBody, + }); + } else { + await github.rest.issues.createComment({ + owner, + repo, + issue_number: issueNumber, + body: commentBody, + }); + } diff --git a/.github/workflows/semantic-pr.yml b/.github/workflows/semantic-pr.yml index 632323c6a5..371b3c504a 100644 --- a/.github/workflows/semantic-pr.yml +++ b/.github/workflows/semantic-pr.yml @@ -6,7 +6,7 @@ jobs: name: Validate PR title runs-on: ubuntu-latest steps: - - uses: amannn/action-semantic-pull-request@91682d013dea3ff257520b9b68c9cb93ced4fe9b # v4.5.0 + - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 7b6949f2fb..1b4619fbde 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -5,7 +5,6 @@ on: - cron: '30 1 * * *' permissions: - contents: read issues: write pull-requests: write @@ -13,7 +12,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9 + - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 30 diff --git a/.github/workflows/triage.yaml b/.github/workflows/triage.yaml index c9c69eb8bb..26e574ef24 100644 --- a/.github/workflows/triage.yaml +++ b/.github/workflows/triage.yaml @@ -12,8 +12,8 @@ jobs: runs-on: ubuntu-latest if: github.event.label.name == 'needs more info' steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -40,8 +40,8 @@ jobs: runs-on: ubuntu-latest if: github.event.label.name == 'needs repro' steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/updates.yml b/.github/workflows/updates.yml deleted file mode 100644 index fa753fd5bf..0000000000 --- a/.github/workflows/updates.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Expo EAS Updates -on: - push: - branches: - - main -jobs: - publish: - if: ${{ (contains(github.event.pull_request.labels.*.name, 'example app')) }} - name: Install and publish - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version-file: .nvmrc - - - name: Setup Expo - uses: expo/expo-github-action@d300b960e9f91a8c59b2aaca92e89ad70b0785ac # v7 - with: - expo-version: latest - eas-version: latest - token: ${{ secrets.EXPO_TOKEN }} - - - name: Restore dependencies - id: yarn-cache - uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4 - with: - path: '**/node_modules' - key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }} - restore-keys: | - ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} - ${{ runner.os }}-yarn- - - - name: Install dependencies - if: steps.yarn-cache.outputs.cache-hit != 'true' - run: | - sudo corepack enable - yarn --cwd example --immutable - yarn --immutable - - - name: Cache dependencies - if: steps.yarn-cache.outputs.cache-hit != 'true' - uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4 - with: - path: '**/node_modules' - key: ${{ steps.yarn-cache.outputs.cache-primary-key }} - - - name: Publish Expo app update - working-directory: ./example - run: eas update --auto diff --git a/.github/workflows/versions.yml b/.github/workflows/versions.yml index a84b362955..bcadaaeab1 100644 --- a/.github/workflows/versions.yml +++ b/.github/workflows/versions.yml @@ -12,7 +12,7 @@ jobs: if: ${{ github.event.label.name == 'bug' }} runs-on: ubuntu-latest steps: - - uses: react-navigation/check-versions-action@deac0a153b834fdda425028be69b2cf786dacc31 # v1.1.0 + - uses: react-navigation/check-versions-action@1f7edac18020a4dc38c4d174bb04311b196f9b75 # v1.2.2 with: github-token: ${{ secrets.GITHUB_TOKEN }} required-packages: | diff --git a/.gitignore b/.gitignore index 6ece42c049..4c86dcc669 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,15 @@ yarn-error.log # coverage/ cache/jest/ +__ts-tests__/ + +# ESLint +# +.eslintcache + +# TypeScript +# +*.tsbuildinfo # BUCK # @@ -55,6 +64,9 @@ android/keystores/debug.keystore # dist/ web-build/ +docs/5.x/component-docs.json +docs/5.x/docs/components/ +docs/6.x/docs/components/ # Release # @@ -63,12 +75,10 @@ CHANGELOG.md # generated by bob lib/ -.expo - # Yarn .yarn/* !.yarn/patches !.yarn/plugins !.yarn/releases !.yarn/sdks -!.yarn/versions \ No newline at end of file +!.yarn/versions diff --git a/.nvmrc b/.nvmrc index d2c5c8a013..e8416a151f 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v22.15.0 \ No newline at end of file +v24.13.0 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..5c8875a9e1 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,33 @@ +# Build +build/ +dist/ +web-build/ +docs/.cache-loader/ +docs/build/ +docs/dist/ +docs/docs/components/ +docs/public/react-native-paper/ +example/coverage/ +example/dist/ + +# generated docs +docs/5.x/_nav.json +docs/5.x/index.mdx +docs/5.x/docs/_meta.json +docs/5.x/docs/guides/_meta.json +docs/5.x/docs/components/_meta.json +docs/5.x/docs/components/**/_meta.json +docs/6.x/_nav.json +docs/6.x/index.mdx +docs/6.x/docs/_meta.json +docs/6.x/docs/guides/_meta.json +docs/6.x/docs/components/ +docs/src/data/componentDocs6x.json + +# generated by bob +lib/ + +# Expo +example/.expo/ +example/android/ +example/ios/ diff --git a/.prettierrc b/.prettierrc index cbfec3ea55..e687413c41 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,4 @@ - singleQuote: true tabWidth: 2 -trailingComma: "es5" +trailingComma: 'es5' useTabs: false diff --git a/.release-it.json b/.release-it.json index 869746da21..bd70df14cf 100644 --- a/.release-it.json +++ b/.release-it.json @@ -7,7 +7,8 @@ "publish": true }, "github": { - "release": true + "release": true, + "draft": true }, "plugins": { "@release-it/conventional-changelog": { diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..4d94259957 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,16 @@ +# Project instructions + +Read and follow the relevant project guidance before making or reviewing changes: + +- [README.md](README.md) for the project overview and documentation links. +- [CONTRIBUTING.md](CONTRIBUTING.md) for setup, development workflow, validation, component guidelines, and commit conventions. +- [package.json](package.json) for the package manager version and available scripts. +- [CI workflow](.github/workflows/ci.yml) for build and validation checks. +- [Pull request template](.github/pull_request_template.md) when preparing a pull request. + +For implementation and review, apply the shared review instructions: + +- [General review standards](.github/copilot-instructions.md). +- [TypeScript and React rules](.github/instructions/code.instructions.md) for JavaScript and TypeScript files. +- [Documentation rules](.github/instructions/docs.instructions.md) for Markdown and MDX files. +- [Test rules](.github/instructions/tests.instructions.md) for test files. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..43c994c2d3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a4cf09cd56..99aef2a692 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,8 +13,9 @@ The core team works directly on GitHub and all work is public. > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github). 1. Fork the repo and create your branch from `main` (a guide on [how to fork a repository](https://help.github.com/articles/fork-a-repo/)). -2. Run `yarn` on the root level, to setup the development environment. -3. Do the changes you want and test them out in the example app before sending a pull request. +2. Use the Node.js version specified in `.nvmrc`. +3. Run `yarn` on the root level, to setup the development environment. +4. Do the changes you want and test them out in the example app before sending a pull request. ### Commit message convention @@ -25,16 +26,16 @@ We follow the [conventional commits specification](https://www.conventionalcommi - `refactor`: code refactor, e.g. new folder structure for components. - `docs`: changes into documentation, e.g. add usage example for Button. - `test`: adding or updating tests, eg unit, snapshot testing. -- `chore`: tooling changes, e.g. change circleci config. +- `chore`: tooling changes, e.g. change the CI configuration. - `BREAKING CHANGE`: for changes that break existing usage, e.g. change API of a component. Our pre-commit hooks verify that your commit message matches this format when committing. ### Linting and tests -We use `typescript` for type checking, `eslint` with `prettier` for linting and formatting the code, and `jest` for testing. Our pre-commit hooks verify that the linter and tests pass when commiting. You can also run the following commands manually: +We use `typescript` for type checking, `eslint` with `prettier` for linting and formatting the code, and `jest` for testing. Our pre-commit hooks verify that type checking, linting, and tests pass when committing. You can also run the following commands manually: -- `yarn typescript`: type-check files with `tsc`. +- `yarn typecheck`: type-check files with `tsc`. - `yarn lint`: lint files with `eslint` and `prettier`. - `yarn test`: run unit tests with `jest`. @@ -43,27 +44,40 @@ We use `typescript` for type checking, `eslint` with `prettier` for linting and When you're sending a pull request: - Prefer small pull requests focused on one change. -- Verify that `typescript`, `eslint` and all tests are passing. +- Verify that `typecheck`, `lint` and all tests are passing. - Preview the documentation to make sure it looks good. - Follow the pull request template when opening a pull request. When you're working on a component: -- Follow the guidelines described in the [official material design docs](https://material.io/guidelines/). -- Write a brief description of every prop when defining `type Props` to aid with documentation. -- Provide an example usage for the component (check other components to get a idea). -- Update the type definitions for Flow and TypeScript if you changed an API or added a component. +- Follow the guidelines described in the [official material design docs](https://m3.material.io/). +- Write a brief description of every prop when defining the props type to aid with documentation. +- Provide an example usage for the component (check other components to get an idea). +- Update the type definitions for TypeScript if you changed an API or added a component. ### Running the example -The example app uses [Expo](https://expo.dev/) for the React Native example. You will need to install the Expo app for [Android](https://play.google.com/store/apps/details?id=host.exp.exponent) and [iOS](https://itunes.apple.com/app/apple-store/id982107779) to start developing. +The example app uses [Expo](https://expo.dev/) development build for the React Native example. -> [!IMPORTANT] -> The example app is built with `react-native@0.77.x` and Expo SDK 52, which isn’t compatible with Expo Go. To run the app, you have to create a [development build](https://docs.expo.dev/develop/development-builds/create-a-build/). +First, generate the native projects: -After you're done, you can run `yarn example start` in the project root (or `npx expo start` in the `example/` folder) and scan the QR code to launch it on your device. +```sh +yarn example expo prebuild +``` + +Then build and install the development build: + +```sh +yarn example ios +``` + +or: + +```sh +yarn example android +``` -To run the example on web, run `yarn example web` in the project root. +After you're done, you can run `yarn example start` in the project root and scan the QR code to launch it on your device, or press `i`, `a` or `w` to launch it on the iOS simulator, Android emulator, or web browser, respectively. ### Testing a specific pull request/commit @@ -72,7 +86,7 @@ If you want to test the changes brought by a pull request, you can do so by poin ```json { "dependencies": { - "react-native-paper": "git+https://github.com/callstack/react-native-paper.git#", + "react-native-paper": "git+https://github.com/callstack/react-native-paper.git#" } } ``` @@ -83,18 +97,39 @@ Alternatively, you may clone the `react-native-paper` repo and use the [yalc](ht ### Working on documentation -The documentation is automatically generated from the [TypeScript](https://www.typescriptlang.org/) annotations in the components. You can add comments above the type annotations to add descriptions. To preview the generated documentation, run `yarn docs start` in the project root. +To preview the documentation, run `yarn docs dev` in the project root. ### Publishing a release -We use [release-it](https://github.com/webpro/release-it) to automate our release. If you have publish access to the NPM package, run the following from the main branch to publish a new release: +We use [release-it](https://github.com/webpro/release-it) to automate our release. If you have publish access to the NPM package, run the following from the main branch, always with the exact version you want to publish: ```sh -yarn release +yarn release 6.0.0-beta.0 --dry-run +yarn release 6.0.0-beta.0 +``` + +- Always pass the version. A bare `yarn release` applies the bump the commit history recommends, which can be a stable or major version you did not intend. npm never accepts the same version twice, so a wrong version cannot be taken back. +- Start with the dry run. Before running it for real, check the compare range in the changelog heading it prints (for example `v6.0.0-alpha.0...v6.0.0-alpha.1`) and the `--tag` on the `npm publish` line. +- The npm tag and the GitHub prerelease flag follow the version: `6.0.0-beta.0` is published under the `beta` tag as a GitHub prerelease, `6.0.0` under `latest` as a regular release. +- The GitHub release is created as a draft. Once `npm publish` has succeeded, add the release notes and publish the draft: + +```sh +gh release edit v6.0.0-beta.0 --notes-file notes.md --draft=false ``` NOTE: You must have a `GITHUB_TOKEN` environment variable available. You can create a GitHub access token with the "repo" access [here](https://github.com/settings/tokens). +You also need to be logged in to npm with an account that can publish the package: check with `npm whoami`, and run `npm login` if it fails. + +Publishing the GitHub release also deploys the example app via EAS, so keep the release a draft until npm has the package. If there were no native code changes, it'll ship an OTA update. + +Otherwise, for stable releases: + +- The Android build is automatically submitted to Google Play for review +- The iOS build is automatically uploaded to App Store Connect and needs to be manually submitted for review + +Pre-release builds are shipped to the internal track on Google Play for Android and TestFlight for iOS. They shouldn't be submitted to the App Store for review, or the stable build with the same version would be rejected. + ## Reporting issues You can report issues on our [bug tracker](https://github.com/callstack/react-native-paper/issues). Please follow the issue template when opening an issue. diff --git a/babel.config.js b/babel.config.js index f7b3da3b33..8ba8eb658c 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,4 @@ module.exports = { presets: ['module:@react-native/babel-preset'], + plugins: ['react-native-worklets/plugin'], }; diff --git a/docs/.eslintrc b/docs/.eslintrc deleted file mode 100644 index bba456d680..0000000000 --- a/docs/.eslintrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../.eslintrc", - - "settings": { - "import/core-modules": ["react-native-paper"] - }, - - "rules": { - "react/prop-types": "off", - "import/no-unresolved": [ - 2, - {"ignore": ["^@docusaurus", "^@theme", "@^site"]} - ] - } -} diff --git a/docs/.gitignore b/docs/.gitignore index 9d90495ad6..e59dab5e8d 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -3,10 +3,12 @@ # Production /build +/test-results +/visual/output # Generated files -.docusaurus .cache-loader +/public/llms.txt # Misc .DS_Store @@ -19,9 +21,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -# component-docs-plugin -docs/components - # legacy dist/ .linaria-cache/ diff --git a/docs/5.x/_nav.json b/docs/5.x/_nav.json new file mode 100644 index 0000000000..0099eb059f --- /dev/null +++ b/docs/5.x/_nav.json @@ -0,0 +1,12 @@ +[ + { + "text": "Guides", + "link": "/docs/guides/getting-started", + "activeMatch": "^/docs/guides/" + }, + { + "text": "Showcase", + "link": "/docs/showcase", + "activeMatch": "^/docs/showcase$" + } +] diff --git a/docs/5.x/docs/_meta.json b/docs/5.x/docs/_meta.json new file mode 100644 index 0000000000..1a81e3fb19 --- /dev/null +++ b/docs/5.x/docs/_meta.json @@ -0,0 +1,12 @@ +[ + { + "type": "dir-section-header", + "name": "guides", + "label": "Guides" + }, + { + "type": "file", + "name": "showcase", + "label": "Showcase" + } +] diff --git a/docs/5.x/docs/guides/_meta.json b/docs/5.x/docs/guides/_meta.json new file mode 100644 index 0000000000..3fbe040166 --- /dev/null +++ b/docs/5.x/docs/guides/_meta.json @@ -0,0 +1,14 @@ +[ + "getting-started", + "theming", + "icons", + "fonts", + "react-native-web", + "recommended-libraries", + "contributing", + "theming-with-react-navigation", + "bottom-navigation", + "react-navigation", + "migration-guide-to-5.0", + "ripple-effect" +] diff --git a/docs/docs/guides/09-bottom-navigation.md b/docs/5.x/docs/guides/bottom-navigation.md similarity index 100% rename from docs/docs/guides/09-bottom-navigation.md rename to docs/5.x/docs/guides/bottom-navigation.md diff --git a/docs/5.x/docs/guides/contributing.mdx b/docs/5.x/docs/guides/contributing.mdx new file mode 100644 index 0000000000..2861043dc3 --- /dev/null +++ b/docs/5.x/docs/guides/contributing.mdx @@ -0,0 +1,7 @@ +--- +title: Contributing +--- + +import Contributing from '../../../../CONTRIBUTING.md'; + + diff --git a/docs/docs/guides/04-fonts.md b/docs/5.x/docs/guides/fonts.md similarity index 59% rename from docs/docs/guides/04-fonts.md rename to docs/5.x/docs/guides/fonts.md index 60d3c6e457..661d6bbf6b 100644 --- a/docs/docs/guides/04-fonts.md +++ b/docs/5.x/docs/guides/fonts.md @@ -8,40 +8,39 @@ title: Fonts The easiest way to install custom fonts to your RN project is do as follows: - 1. Define path to assets directory with fonts in project: +1. Define path to assets directory with fonts in project: - Example: +Example: - ```js - module.exports = { - ... - assets: [ - './assets/fonts' - ], - ``` +```js + module.exports = { + ... + assets: [ + './assets/fonts' + ], +``` :::note `fonts` is a folder with `.ttf` files ::: - 2. Place your font files in your assets directory. - - 3. Link font files, using the following command in the terminal: +2. Place your font files in your assets directory. - * React Native `>= 0.69`: +3. Link font files, using the following command in the terminal: - ```sh - npx react-native-asset - ``` +- React Native `>= 0.69`: - * React Native `< 0.69`: +```sh +npx react-native-asset +``` - ```sh - npx react-native link - ``` +- React Native `< 0.69`: +```sh +npx react-native link +``` - 4. Restart your project to refresh changes. +4. Restart your project to refresh changes. Now, you are able to use `fontFamily` from font files. @@ -53,8 +52,8 @@ Now, you are able to use `fontFamily` from font files. To create a custom font, prepare a `fontConfig` object where fonts are divided by platforms. After that, you have to: -* pass the `fontConfig` into `configureFonts` params object property called `config` -* set the params object property `isV3` to `false`. +- pass the `fontConfig` into `configureFonts` params object property called `config` +- set the params object property `isV3` to `false`. The `fontConfig` object accepts `ios`, `android`, `macos`, `windows`, `web`, and `native`. Use these to override fonts on particular platforms. @@ -64,8 +63,12 @@ At a minimum, you need to explicitly pass fonts for `android`, `ios`, and `web`. ```js import * as React from 'react'; -import { configureFonts, MD2LightTheme, PaperProvider } from 'react-native-paper'; -import App from './src/App'; +import { + configureFonts, + MD2LightTheme, + PaperProvider, +} from 'react-native-paper'; +import App from './App'; const fontConfig = { web: { @@ -121,12 +124,12 @@ const fontConfig = { fontFamily: 'sans-serif-thin', fontWeight: 'normal', }, - } + }, }; const theme = { ...MD2LightTheme, - fonts: configureFonts({config: fontConfig, isV3: false}), + fonts: configureFonts({ config: fontConfig, isV3: false }), }; export default function Main() { @@ -149,7 +152,7 @@ If you're using TypeScript use `as const` when defining `fontConfig`. In the latest version fonts in theme are structured based on the `variant` keys e.g. `displayLarge` or `bodyMedium` which are then used in `Text`'s component throughout the whole library. :::info -The default `fontFamily` is different per particular platfrom: +The default `fontFamily` is different per particular platform: ```js Platform.select({ @@ -158,234 +161,235 @@ Platform.select({ default: 'sans-serif', // and 'sans-serif-medium' for `fontWeight:"500"` }), ``` + ::: -* #### Display +- #### Display
- ```json - "displaySmall": { - "fontFamily": "Font", - "fontSize": 36, - "fontWeight": "400", - "letterSpacing": 0, - "lineHeight": 44, - } - ``` +```json +"displaySmall": { + "fontFamily": "Font", + "fontSize": 36, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 44, +} +```
- ```json - "displayMedium": { - "fontFamily": "Font", - "fontSize": 45, - "fontWeight": "400", - "letterSpacing": 0, - "lineHeight": 52, - } - ``` +```json +"displayMedium": { + "fontFamily": "Font", + "fontSize": 45, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 52, +} +```
- ```json - "displayLarge": { - "fontFamily": "Font", - "fontSize": 57, - "fontWeight": "400", - "letterSpacing": 0, - "lineHeight": 64, - } - ``` +```json +"displayLarge": { + "fontFamily": "Font", + "fontSize": 57, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 64, +} +```
-* #### Headline +- #### Headline
- ```json - "headlineSmall": { - "fontFamily": "Font", - "fontSize": 24, - "fontWeight": "400", - "letterSpacing": 0, - "lineHeight": 32, - } - ``` +```json +"headlineSmall": { + "fontFamily": "Font", + "fontSize": 24, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 32, +} +```
- ```json - "headlineMedium": { - "fontFamily": "Font", - "fontSize": 28, - "fontWeight": "400", - "letterSpacing": 0, - "lineHeight": 36, - } - ``` +```json +"headlineMedium": { + "fontFamily": "Font", + "fontSize": 28, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 36, +} +```
- ```json - "headlineLarge": { - "fontFamily": "Font", - "fontSize": 32, - "fontWeight": "400", - "letterSpacing": 0, - "lineHeight": 40, - } - ``` +```json +"headlineLarge": { + "fontFamily": "Font", + "fontSize": 32, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 40, +} +```
-* #### Title +- #### Title
- ```json - "titleSmall": { - "fontFamily": "Font", - "fontSize": 14, - "fontWeight": "500", - "letterSpacing": 0.1, - "lineHeight": 20, - } - ``` +```json +"titleSmall": { + "fontFamily": "Font", + "fontSize": 14, + "fontWeight": "500", + "letterSpacing": 0.1, + "lineHeight": 20, +} +```
- ```json - "titleMedium": { - "fontFamily": "Font", - "fontSize": 16, - "fontWeight": "500", - "letterSpacing": 0.15, - "lineHeight": 24, - } - ``` +```json +"titleMedium": { + "fontFamily": "Font", + "fontSize": 16, + "fontWeight": "500", + "letterSpacing": 0.15, + "lineHeight": 24, +} +```
- ```json - "titleLarge": { - "fontFamily": "Font", - "fontSize": 22, - "fontWeight": "400", - "letterSpacing": 0, - "lineHeight": 28, - } - ``` +```json +"titleLarge": { + "fontFamily": "Font", + "fontSize": 22, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 28, +} +```
-* #### Label +- #### Label
- ```json - "labelSmall": { - "fontFamily": "Font", - "fontSize": 11, - "fontWeight": "500", - "letterSpacing": 0.5, - "lineHeight": 16, - } - ``` +```json +"labelSmall": { + "fontFamily": "Font", + "fontSize": 11, + "fontWeight": "500", + "letterSpacing": 0.5, + "lineHeight": 16, +} +```
- ```json - "labelMedium": { - "fontFamily": "Font", - "fontSize": 12, - "fontWeight": "500", - "letterSpacing": 0.5, - "lineHeight": 16, - } - ``` +```json +"labelMedium": { + "fontFamily": "Font", + "fontSize": 12, + "fontWeight": "500", + "letterSpacing": 0.5, + "lineHeight": 16, +} +```
- ```json - "labelLarge": { - "fontFamily": "Font", - "fontSize": 14, - "fontWeight": "500", - "letterSpacing": 0.1, - "lineHeight": 20, - } - ``` +```json +"labelLarge": { + "fontFamily": "Font", + "fontSize": 14, + "fontWeight": "500", + "letterSpacing": 0.1, + "lineHeight": 20, +} +```
-* #### Body +- #### Body
- ```json - "bodySmall": { - "fontFamily": "Font", - "fontSize": 12, - "fontWeight": "400", - "letterSpacing": 0.4, - "lineHeight": 16, - } - ``` +```json +"bodySmall": { + "fontFamily": "Font", + "fontSize": 12, + "fontWeight": "400", + "letterSpacing": 0.4, + "lineHeight": 16, +} +```
- ```json - "bodyMedium": { - "fontFamily": "Font", - "fontSize": 14, - "fontWeight": "400", - "letterSpacing": 0.25, - "lineHeight": 20, - } - ``` +```json +"bodyMedium": { + "fontFamily": "Font", + "fontSize": 14, + "fontWeight": "400", + "letterSpacing": 0.25, + "lineHeight": 20, +} +```
- ```json - "bodyLarge": { - "fontFamily": "Font", - "fontSize": 16, - "fontWeight": "400", - "letterSpacing": 0.15, - "lineHeight": 24, - } - ``` +```json +"bodyLarge": { + "fontFamily": "Font", + "fontSize": 16, + "fontWeight": "400", + "letterSpacing": 0.15, + "lineHeight": 24, +} +```
@@ -400,16 +404,22 @@ If any component uses Paper's `Text` component, without specified variant "letterSpacing": 0, }, ``` + ::: #### Using `configureFonts` helper -* If there is a need to create a custom font variant, prepare its config object including required all fonts properties. After that, defined `fontConfig` has to be passed under the `variant` name as `config` into the params object: +- If there is a need to create a custom font variant, prepare its config object including required all fonts properties. After that, defined `fontConfig` has to be passed under the `variant` name as `config` into the params object: ```js import * as React from 'react'; -import { configureFonts, MD3LightTheme, PaperProvider } from 'react-native-paper'; -import App from './src/App'; +import { + configureFonts, + MD3LightTheme, + PaperProvider, +} from 'react-native-paper'; +import { Platform } from 'react-native'; +import App from './App'; const fontConfig = { customVariant: { @@ -422,12 +432,12 @@ const fontConfig = { letterSpacing: 0.5, lineHeight: 22, fontSize: 20, - } + }, }; const theme = { ...MD3LightTheme, - fonts: configureFonts({config: fontConfig}), + fonts: configureFonts({ config: fontConfig }), }; export default function Main() { @@ -442,31 +452,34 @@ export default function Main() { If you're using TypeScript you will need to create a custom `Text` component which accepts your custom variants: ```typescript -import { customText } from 'react-native-paper' +import { customText } from 'react-native-paper'; // Use this instead of importing `Text` from `react-native-paper` -export const Text = customText<'customVariant'>() +export const Text = customText<'customVariant'>(); ``` - -* In order to override one of the available `variant`'s font properties, pass the modified `fontConfig` under specific `variant` name as `config` into the params object: +- In order to override one of the available `variant`'s font properties, pass the modified `fontConfig` under specific `variant` name as `config` into the params object: ```js import * as React from 'react'; -import { configureFonts, MD3LightTheme, PaperProvider } from 'react-native-paper'; -import App from './src/App'; +import { + configureFonts, + MD3LightTheme, + PaperProvider, +} from 'react-native-paper'; +import App from './App'; const fontConfig = { bodyLarge: { letterSpacing: 0.5, lineHeight: 22, fontSize: 20, - } + }, }; const theme = { ...MD3LightTheme, - fonts: configureFonts({config: fontConfig}), + fonts: configureFonts({ config: fontConfig }), }; export default function Main() { @@ -478,20 +491,24 @@ export default function Main() { } ``` -* However, if you just want to override any font property e.g. `fontFamily` or `letterSpacing` for all variants, you can pass the `fontConfig` as a `config` into the params object without specifying variant name: +- However, if you just want to override any font property e.g. `fontFamily` or `letterSpacing` for all variants, you can pass the `fontConfig` as a `config` into the params object without specifying variant name: ```js import * as React from 'react'; -import { configureFonts, MD3LightTheme, PaperProvider } from 'react-native-paper'; -import App from './src/App'; +import { + configureFonts, + MD3LightTheme, + PaperProvider, +} from 'react-native-paper'; +import App from './App'; const fontConfig = { - fontFamily: 'NotoSans' + fontFamily: 'NotoSans', }; const theme = { ...MD3LightTheme, - fonts: configureFonts({config: fontConfig}), + fonts: configureFonts({ config: fontConfig }), }; export default function Main() { @@ -503,17 +520,16 @@ export default function Main() { } ``` - ## Variable fonts -Although React Native Paper supports `fontWeight` and `fontStyle` properties, there are multiple limitations to custom -fonts in React Native. Using custom [variable fonts](https://fonts.google.com/knowledge/introducing_type/introducing_variable_fonts) +Although React Native Paper supports `fontWeight` and `fontStyle` properties, there are multiple limitations to custom +fonts in React Native. Using custom [variable fonts](https://fonts.google.com/knowledge/introducing_type/introducing_variable_fonts) is especially problematic, with some platforms failing to render variants entirely. To ensure correct typography in your -app, we suggest installing each font variant as a separate file. Below you'll find example on how to set up React Native Paper -theme with custom fonts. +app, we suggest installing each font variant as a separate file. Below you'll find example on how to set up React Native Paper +theme with custom fonts. Should you decide to use a variable font anyway, second example will show you how to test if the font is rendered correctly in React Native on all platforms. - +
Variable fonts examples
    diff --git a/docs/docs/guides/01-getting-started.md b/docs/5.x/docs/guides/getting-started.md similarity index 98% rename from docs/docs/guides/01-getting-started.md rename to docs/5.x/docs/guides/getting-started.md index 68071c4d25..554f3ceb60 100644 --- a/docs/docs/guides/01-getting-started.md +++ b/docs/5.x/docs/guides/getting-started.md @@ -18,7 +18,7 @@ npm install react-native-paper npm install react-native-safe-area-context ``` -Additionaly for `iOS` platform there is a requirement to link the native parts of the library: +Additionally for `iOS` platform there is a requirement to link the native parts of the library: ```bash npx pod-install diff --git a/docs/docs/guides/03-icons.mdx b/docs/5.x/docs/guides/icons.mdx similarity index 96% rename from docs/docs/guides/03-icons.mdx rename to docs/5.x/docs/guides/icons.mdx index 015a1bee1b..e982c82730 100644 --- a/docs/docs/guides/03-icons.mdx +++ b/docs/5.x/docs/guides/icons.mdx @@ -2,15 +2,15 @@ title: Icons --- -import TabItem from '@theme/TabItem'; -import Tabs from '@theme/Tabs'; -import IconsList from '@site/src/components/IconsList.tsx'; +import TabItem from '@docs/components/TabItem'; +import Tabs from '@docs/components/Tabs'; +import IconsList from '@docs/components/IconsList.tsx'; # Icons ## Configuring icons -Many of the components require the [react-native-vector-icons](https://github.com/oblador/react-native-vector-icons) library to render correctly. If you're using Expo, you don't need to do anything extra, but if it's a vanilla React Native project, you need to link the library as described in the [getting started guide](./01-getting-started.md). +Many of the components require the [react-native-vector-icons](https://github.com/oblador/react-native-vector-icons) library to render correctly. If you're using Expo, you don't need to do anything extra, but if it's a vanilla React Native project, you need to link the library as described in the [getting started guide](./getting-started). :::note If you opted out of vector icons support using [babel-plugin-optional-require](https://github.com/satya164/babel-plugin-optional-require), you won't be able to use icon names for the icon prop. Some components may not look correct without vector icons and might need extra configuration. diff --git a/docs/docs/guides/10-migration-guide-to-5.0.md b/docs/5.x/docs/guides/migration-guide-to-5.0.md similarity index 78% rename from docs/docs/guides/10-migration-guide-to-5.0.md rename to docs/5.x/docs/guides/migration-guide-to-5.0.md index 6c358d387f..21bb96c2f7 100644 --- a/docs/docs/guides/10-migration-guide-to-5.0.md +++ b/docs/5.x/docs/guides/migration-guide-to-5.0.md @@ -4,29 +4,29 @@ title: Introducing v5 with Material You React Native Paper v5 is all about adopting the new Material Design 3 aka Material You. It was released in October 2021 after intense work and effort to make Material You follow a more expressive approach to design. -Paper now supports both Material Design 2 and 3 through the configuration described in [Versioning](#versioning) and is compatible with a handful of API changes. +Paper now supports both Material Design 2 and 3 through the configuration described in [Versioning](#versioning) and is compatible with a handful of API changes. # Migration guide to Material You 5.0 -Version 5.0 brings support for the next Material Design iteration branded as Material You (in fact being Material Design v3 or in short MD3) into the `react-native-paper` library. All the components were refined according to the official [design kit on figma](https://www.figma.com/community/file/1035203688168086460) and adjusted in terms of visuals by changes in colors, typography and animations. +Version 5.0 brings support for the next Material Design iteration branded as Material You (in fact being Material Design v3 or in short MD3) into the `react-native-paper` library. All the components were refined according to the official [design kit on figma](https://www.figma.com/community/file/1035203688168086460) and adjusted in terms of visuals by changes in colors, typography and animations. -Below you can find the most important information about the components whose API may have changed API has been changed due to supporting new props, renaming existing ones or some deprecations. Hopefully, based on the presented required changes, migration to the latest version should be smooth. Enjoy! +Below you can find the most important information about the components whose API may have changed API has been changed due to supporting new props, renaming existing ones or some deprecations. Hopefully, based on the presented required changes, migration to the latest version should be smooth. Enjoy! ### Installation -* The `v5` has been oficially released as a stable version, which means it will be installed by default from the `npm`. In order to do that, open a Terminal in your project's folder and run: +- The `v5` has been officially released as a stable version, which means it will be installed by default from the `npm`. In order to do that, open a Terminal in your project's folder and run: ```bash npm2yarn npm install react-native-paper ``` -* From `v5` there is a need to install [react-native-safe-area-context](https://github.com/th3rdwave/react-native-safe-area-context) for handling safe area. +- From `v5` there is a need to install [react-native-safe-area-context](https://github.com/th3rdwave/react-native-safe-area-context) for handling safe area. ```bash npm2yarn npm install react-native-safe-area-context ``` -Additionaly for `iOS` platform there is a requirement to link the native parts of the library: +Additionally for `iOS` platform there is a requirement to link the native parts of the library: ```bash npx pod-install @@ -38,13 +38,13 @@ npx pod-install Introducing Material You (MD3) into `react-native-paper` doesn't mean dropping previous Material Design (MD2)! On the contrary, both of them will be supported, however, not simultaneously. To specify which design system components should follow in the app, there is a newly created property in [the theme](https://callstack.github.io/react-native-paper/docs/guides/theming#theme-properties) named `version` that accepts one of two values: -* 3 – (default) new Material You (MD3), -* 2 - previous Material Design (MD2). +- 3 – (default) new Material You (MD3), +- 2 - previous Material Design (MD2). ```js theme: { /* ... */ - version: 3 | 2 + version: 3 | 2; } ``` @@ -60,7 +60,7 @@ Each accent and error colors has a group of related tones. The tones are mapped Dynamic colors are not supported yet ::: -![color-palette](../../static/migration/color-palette.png) +![color-palette](../../../public/migration/color-palette.png) Colors theme structure should follow the default palette and contain the following properties: @@ -114,7 +114,7 @@ theme: { ## Typography -A new way of approaching typography introduces one component `` which accepts prop `variant`. Variant defines appropriate text styles for type role and its size. The updated type scale organizes styles into five roles that are named to describe their purposes: Display, Headline, Title, Label and Body along with three display styles large, medium, and small. In total, there are fifteen variants that are MD3 compliant and are reflecting design typography tokens written in camel case. +A new way of approaching typography introduces one component `` which accepts prop `variant`. Variant defines appropriate text styles for type role and its size. The updated type scale organizes styles into five roles that are named to describe their purposes: Display, Headline, Title, Label and Body along with three display styles large, medium, and small. In total, there are fifteen variants that are MD3 compliant and are reflecting design typography tokens written in camel case. :::info If any component uses Paper's `Text` component, without specified variant, then `default` variant is applied. @@ -140,11 +140,11 @@ If any component uses Paper's `Text` component, without specified variant Label Large Label Medium Label Small - ``` +``` Take a look at the suggested replacement diff: - ```diff +```diff - Headline + Headline @@ -159,33 +159,33 @@ Take a look at the suggested replacement diff: - Caption + Caption - ``` - +``` 👉 You can find more about typography on the [Material You website](https://m3.material.io/styles/typography/overview) ### Configure fonts -The existing utility called `configureFonts` was adjusted to help users configure their theme fonts in both version, that's why that function, as of today, is going to accept the object with the follwing properties as an argument: +The existing utility called `configureFonts` was adjusted to help users configure their theme fonts in both version, that's why that function, as of today, is going to accept the object with the following properties as an argument: ```ts -configureFonts(params) +configureFonts(params); ``` Parameters: -| NAME | TYPE | REQUIRED | -| ----------- | ----------- | ----------- | -| params | object | No | +| NAME | TYPE | REQUIRED | +| ------ | ------ | -------- | +| params | object | No | Valid `params` keys are: - * `config` ([MD2FontsConfig](https://github.com/callstack/react-native-paper/blob/main/src/styles/fonts.tsx#L63) | [MD3FontsConfig](https://github.com/callstack/react-native-paper/blob/main/src/styles/fonts.tsx#L67)) - fonts config object appropriate to the MD version - * `isV3` (boolean) - whether adjusting theme fonts for v3. Default it true. +- `config` ([MD2FontsConfig](https://github.com/callstack/react-native-paper/blob/main/styles/fonts.tsx#L63) | [MD3FontsConfig](https://github.com/callstack/react-native-paper/blob/main/styles/fonts.tsx#L67)) - fonts config object appropriate to the MD version +- `isV3` (boolean) - whether adjusting theme fonts for v3. Default it true. To use your current font config from v2 and migrate to v3 there are two requirements: -* the font config previously passed directly into function has to be passed into the params object property called `config` -* the params object property `isV3` has to be set to `false` + +- the font config previously passed directly into function has to be passed into the params object property called `config` +- the params object property `isV3` has to be set to `false` ```diff - configureFonts(fontConfig) @@ -202,31 +202,25 @@ If you want to check how to use `configureFonts` on MD3, check the [Fonts](https `Appbar` and `Appbar.Header` in the latest version can be used in four various modes due to new prop `mode`: -* `small` - Appbar with default height (64) (default), -* `medium` - Appbar with medium height (112), -* `large` - Appbar with large height (152), -* `center-aligned` - Appbar with default height (64) and center-aligned title. +- `small` - Appbar with default height (64) (default), +- `medium` - Appbar with medium height (112), +- `large` - Appbar with large height (152), +- `center-aligned` - Appbar with default height (64) and center-aligned title. ```js - - /* ... */ - +/* ... */ ``` To make it easier for users to build the `BottomBar`, formed on the `Appbar` components, we have added a property `safeAreaInsets`: ```js - - /* ... */ - +/* ... */ ``` - + It's worth noting that by default the theme version 3 `Appbar` and `Appbar.Header` don't have a shadow. However, it can be added by passing prop `elevated` into the component: ```js - - /* ... */ - +/* ... */ ``` #### Appbar.Action @@ -259,9 +253,9 @@ According to the updates in `Surface` on the top of which `Banner`, `Searchbar` For the sake of new animation of pill shape, indicating active destination, and assisting icon change from outlined to filled, there are three changes within `navigationState.routes` property items: -* `color` is deprecated since color is constant and the same for all routes, -* `icon` is renamed to `focusedIcon`, as the name implies, with theme version 3 it's the outline icon used as focused tab icon and with theme version 2 it's a default icon, -* `unfocusedIcon` (optional) is the filled icon used as the unfocused tab icon, compatible with theme version 3. +- `color` is deprecated since color is constant and the same for all routes, +- `icon` is renamed to `focusedIcon`, as the name implies, with theme version 3 it's the outline icon used as focused tab icon and with theme version 2 it's a default icon, +- `unfocusedIcon` (optional) is the filled icon used as the unfocused tab icon, compatible with theme version 3. :::info `unfocusedIcon` is optional, if you can't find outline icon equivalent, omit that prop, so `focusedIcon` will be displayed in both active and inactive state. @@ -280,29 +274,32 @@ routes: [ ] ``` -The `compact` prop was also introduced, working with both themes. It indicates whether tabs should be spread across the entire width, especially in a horizontal mode. +The `compact` prop was also introduced, working with both themes. It indicates whether tabs should be spread across the entire width, especially in a horizontal mode. ```js ``` It's worth to mention that default value of prop `shifting` depends on the theme version: -* 3 - it's `false`, -* 2 - it's `true` when there are more than 3 tabs. + +- 3 - it's `false`, +- 2 - it's `true` when there are more than 3 tabs. Two additional props that control the scene animation were introduced that control the animation of the tabs when `sceneAnimationEnabled` is `true`: -* `sceneAnimationType: "opacity" | "shifting" | undefined` - defines the animation type for the scene. `shifting` enables a new animation where navigating to a scene will shift it horizontally into view. Both `opacity` and `undefined` have the same effect, fading the scene into view. -* `sceneAnimationEasing` allows specifying a custom easing function for the scene animation. -![shiftingAnimation](../../static/screenshots/bottom-navigation-shifting.gif) +- `sceneAnimationType: "opacity" | "shifting" | undefined` - defines the animation type for the scene. `shifting` enables a new animation where navigating to a scene will shift it horizontally into view. Both `opacity` and `undefined` have the same effect, fading the scene into view. +- `sceneAnimationEasing` allows specifying a custom easing function for the scene animation. + +![shiftingAnimation](../../../public/screenshots/bottom-navigation-shifting.gif) On a final note, please be aware that `BottomNavigation` with theme version 3 doesn't have a shadow. ### Button `Button`'s property `mode` has been expanded with two additional options: -* `elevated` - button with a background color and elevation, used when absolutely necessary e.g. button requires visual separation from a patterned background, -* `container-tonal` - button with a secondary background color, an alternative middle ground between contained and outlined buttons. + +- `elevated` - button with a background color and elevation, used when absolutely necessary e.g. button requires visual separation from a patterned background, +- `container-tonal` - button with a secondary background color, an alternative middle ground between contained and outlined buttons. ```js <> @@ -316,8 +313,9 @@ On a final note, please be aware that `BottomNavigation` with theme version 3 do ``` The property `color` is deprecated, but in its place two new props called `buttonColor` and `textColor` are introduced: -* `buttonColor` - custom button's background color, -* `textColor` - custom button's text color. + +- `buttonColor` - custom button's background color, +- `textColor` - custom button's text color. ```diff - @@ -343,23 +341,21 @@ The `Card` component's property `mode` has been expanded with one additional opt Since there is no one right way to make a card, there is also no one right way for specifying a title and subtitle variant. Therefore two new props come in handy: -* `titleVariant` - title text variant defines appropriate text styles for type role and its size. -* `subtitleVariant` - subtitle text variant defines appropriate text styles for type role and its size. +- `titleVariant` - title text variant defines appropriate text styles for type role and its size. +- `subtitleVariant` - subtitle text variant defines appropriate text styles for type role and its size. ```js - + ``` ### Checkbox + #### Checkbox.Item `Checkbox.Item` similarly to `RadioButton.Item` has been expanded with the prop called `labelVariant`, which defines appropriate text styles for type role and its size. ```js - ``` @@ -368,18 +364,24 @@ Since there is no one right way to make a card, there is also no one right way f To properly compose `Chip` component and adjust into required type, there are three new props that will come in handy: -* `compact` - sets smaller horizontal paddings around the label, useful for `Chip` containing only the label, -* `elevated` - indicating whether `Chip` should be elevated, -* `showSelectedOverlay` - defining whether to display an overlay on a selected button. +- `compact` - sets smaller horizontal paddings around the label, useful for `Chip` containing only the label, +- `elevated` - indicating whether `Chip` should be elevated, +- `showSelectedOverlay` - defining whether to display an overlay on a selected button. ```js <> Compact Chip - Elevated Chip - Chip with selected overlay + + Elevated Chip + + + Chip with selected overlay + ``` + ### Dialog + #### Dialog.Icon `Dialog.Icon` is another freshly added component presenting an icon within a `Dialog`, placed at the top of the content. @@ -395,12 +397,13 @@ It's working only with theme version 3. ``` + ### Divider `Divider` component received two new props: -* `bold` - divider is bolded, -* `horizontalInset` - divider has horizontal insets on both sides. +- `bold` - divider is bolded, +- `horizontalInset` - divider has horizontal insets on both sides. Additionally prop `inset` was renamed to `leftInset`. @@ -410,6 +413,7 @@ Additionally prop `inset` was renamed to `leftInset`. ``` ### Drawer + #### Drawer.CollapsedItem (Navigation rail) `Drawer.CollapsedItem` is a newly created side navigation component that can be used within `Drawer`, representing a destination in the form of an action item with an icon and optionally label. @@ -456,15 +460,15 @@ With the latest version, there is a possibility to specify whether `Drawer.Secti `FAB`, `AnimatedFAB` and `FAB.Group` in the latest version can be used with four variants and two modes, thanks to two new props: -* `variant` defines color mappings variant for combinations of container and icon colors. Can be one of: primary (default), secondary, tertiary or surface. +- `variant` defines color mappings variant for combinations of container and icon colors. Can be one of: primary (default), secondary, tertiary or surface. ```js ``` -* `mode` specifies whether a button should be flat or elevated: - - `flat` - button without a shadow, - - `elevated` - button with a shadow. +- `mode` specifies whether a button should be flat or elevated: + - `flat` - button without a shadow, + - `elevated` - button with a shadow. ```js @@ -472,11 +476,11 @@ With the latest version, there is a possibility to specify whether `Drawer.Secti #### FAB -Additionaly `FAB` may be applied in one of three available sizes, thanks to new prop `size`: +Additionally `FAB` may be applied in one of three available sizes, thanks to new prop `size`: -* `small` - FAB with small height (40), -* `medium` - Appbar with default medium height (56), -* `large` - Appbar with large height (96). +- `small` - FAB with small height (40), +- `medium` - Appbar with default medium height (56), +- `large` - Appbar with large height (96). ```js @@ -504,7 +508,7 @@ There is also deprecation in one of the `actions` properties, namely `small` pro + ``` -Additionally, the action item property previously known as `labelStyle` was renamed to `containerStyle` since it's tied mostly with the container styles. At the same time, `labelStyle` is still available with the new role related to styling item label. +Additionally, the action item property previously known as `labelStyle` was renamed to `containerStyle` since it's tied mostly with the container styles. At the same time, `labelStyle` is still available with the new role related to styling item label. ```diff - @@ -515,13 +519,13 @@ Additionally, the action item property previously known as `labelStyle` was rena `IconButton` received two new props: -* `selected` sets alternative combination of icon and container color, +- `selected` sets alternative combination of icon and container color, ```js ``` -* `containerColor` custom background color of the icon container. +- `containerColor` custom background color of the icon container. ```js @@ -535,12 +539,13 @@ At the same time, the `color` prop was renamed to `iconColor`. ``` ### Menu + #### Menu.Item `Menu.Item` received two new props: -* `dense` sets smaller item height for more condensed layout, -* `trailingIcon` which handles displaying an icon at the end of the item row. +- `dense` sets smaller item height for more condensed layout, +- `trailingIcon` which handles displaying an icon at the end of the item row. ```js @@ -554,46 +559,47 @@ At the same time, by analogy to the second new prop, the `icon` prop was renamed ``` ### RadioButton + ##### RadioButton.Item `RadioButton.Item` has been expanded with the prop called `labelVariant`, which defines appropriate text styles for type role and its size. ```js - ``` ### SegmentedButtons -`SegmentedButtons` is a completely new component introduced in the latest version. It allows people to select options, switch views, or sort elements. It supports single and multiselect select variant and provide a lot +`SegmentedButtons` is a completely new component introduced in the latest version. It allows people to select options, switch views, or sort elements. It supports single and multiselect select variant and provide a lot of customization options. -![segmentedButtons](../../static/screenshots/segmentedbuttons.gif) +![segmentedButtons](../../../public/screenshots/segmentedbuttons.gif) ```js const MyComponent = () => { const [value, setValue] = React.useState(''); return ( - + ); }; ``` @@ -602,14 +608,15 @@ const MyComponent = () => { `Snackbar` due to the optional close affordance, in form of `IconButton` (located on the right side of action button), received three new props: -* `icon` - icon to display when `onIconPress` is defined. Default will be `close` icon. -* `onIconPress` - function to execute on icon button press. The icon button appears only when this prop is specified. -* `iconAccessibilityLabel` - accessibility label for the icon button. +- `icon` - icon to display when `onIconPress` is defined. Default will be `close` icon. +- `onIconPress` - function to execute on icon button press. The icon button appears only when this prop is specified. +- `iconAccessibilityLabel` - accessibility label for the icon button. ### Surface `Surface` component received one new prop: -* `elevation` - accepts values from `0` to `5` and applies background color and shadows to the `Surface` component. Supports both iOS and Android. + +- `elevation` - accepts values from `0` to `5` and applies background color and shadows to the `Surface` component. Supports both iOS and Android. Previously `elevation` was passed inside the `style` prop. Since it supported not only Android, but also iOS, we decided to extract it from `style` and create a separate `elevation` prop for that. @@ -619,9 +626,10 @@ Previously `elevation` was passed inside the `style` prop. Since it supported no ``` ### TextInput + #### TextInput.Icon -The property `name` was renamed to `icon`, since the scope and type of that prop is much wider than just the icon name – it accepts also the function which receives an object with color and size properties and +The property `name` was renamed to `icon`, since the scope and type of that prop is much wider than just the icon name – it accepts also the function which receives an object with color and size properties and ```diff - @@ -632,8 +640,7 @@ The property `name` was renamed to `icon`, since the scope and type of that prop Component displayed upon tapping and holding a screen element or component used to present an informative text label identifying an element, such as a description of its function. -![tooltip](../../static/screenshots/tooltips.gif) - +![tooltip](../../../public/screenshots/tooltips.gif) ```js @@ -645,8 +652,9 @@ Component displayed upon tapping and holding a screen element or component used With this, that’s a wrap. -The update wouldn't happen without a group of great React Native experts I'm happy to work with. +The update wouldn't happen without a group of great React Native experts I'm happy to work with. From this place I would like to thank: + - [Daniel Szczepanik](https://github.com/Drakeoon) for his commitment, effort and collaborative work on adjusting components, - [Olimpia Zurek](https://github.com/OlimpiaZurek) for her contribution and help, - [Aleksandra Desmurs-Linczewska](https://github.com/p-syche), [Jan Jaworski](https://github.com/jaworek) and [Kewin Wereszczyński](https://github.com/kwereszczynski) for checking and testing changes as well as providing valuable feedback, diff --git a/docs/docs/guides/05-react-native-web.md b/docs/5.x/docs/guides/react-native-web.md similarity index 100% rename from docs/docs/guides/05-react-native-web.md rename to docs/5.x/docs/guides/react-native-web.md diff --git a/docs/docs/guides/09-react-navigation.md b/docs/5.x/docs/guides/react-navigation.md similarity index 92% rename from docs/docs/guides/09-react-navigation.md rename to docs/5.x/docs/guides/react-navigation.md index d9227cfb2b..04a3a9ae1e 100644 --- a/docs/docs/guides/09-react-navigation.md +++ b/docs/5.x/docs/guides/react-navigation.md @@ -6,14 +6,14 @@ title: Integrate AppBar with React Navigation ## Prerequisites - - `react-native-paper` - - `react-navigation` - +- `react-native-paper` +- `react-navigation` + We assume that you have already installed the mentioned libraries above, otherwise please check out the guides below. - - [React Native Paper - Getting Started](https://callstack.github.io/react-native-paper/docs/guides/getting-started) - - [React Navigation - Getting Started](https://reactnavigation.org/docs/getting-started/) + +[React Native Paper - Getting Started](https://callstack.github.io/react-native-paper/docs/guides/getting-started) + +[React Navigation - Getting Started](https://reactnavigation.org/docs/getting-started/) ## Stack Navigator @@ -46,7 +46,7 @@ At the moment our navigation stack contains two screens and will render `HomeScr ```js import React from 'react'; -import {View, Text, Button, StyleSheet} from 'react-native'; +import { View, Text, Button, StyleSheet } from 'react-native'; function HomeScreen() { return ( @@ -120,7 +120,8 @@ export default function App() { initialRouteName="Home" screenOptions={{ header: (props) => , - }}> + }} + > @@ -169,7 +170,12 @@ In order to achieve it, we firstly check, if the navigation bar receives a `back import { Appbar } from 'react-native-paper'; import { getHeaderTitle } from '@react-navigation/elements'; -export default function CustomNavigationBar({ navigation, route, options, back }) { +export default function CustomNavigationBar({ + navigation, + route, + options, + back, +}) { const title = getHeaderTitle(options, route.name); return ( @@ -186,6 +192,7 @@ export default function CustomNavigationBar({ navigation, route, options, back } ### Adding more items to `Appbar` Another interesting pattern that can be implemented with `react-native-paper` and `react-navigation` is a "menu" button. Thanks to the `Menu` component we can add a nice looking pop-up to our `Appbar`. To implement this feature we need to make a couple of changes in `CustomNavigationBar`: + - Render a `Menu` component - Pass `Appbar.Action` to the anchor prop - Add a state to control `Menu` visibility @@ -204,13 +211,15 @@ import { PaperProvider } from 'react-native-paper'; initialRouteName="Home" screenOptions={{ header: (props) => , - }}> + }} + > - +; ``` + ::: We also want the menu to appear only on `HomeScreen`, which means we will render it conditionally based on the `back` prop. @@ -240,12 +249,8 @@ export default function CustomNavigationBar({ - }> + anchor={} + > { console.log('Option 1 was pressed'); @@ -276,6 +281,6 @@ Final result: -That's all we need! We have app bar that contains everything we need to navigate through screens and access an additional menu on the main screen. As you can see, with Material design `Appbar` provided by `react-native-paper` used together with `react-navigation` we can easily create an app that looks and works great. +That's all we need! We have app bar that contains everything we need to navigate through screens and access an additional menu on the main screen. As you can see, with Material design `Appbar` provided by `react-native-paper` used together with `react-navigation` we can easily create an app that looks and works great. Grab the Expo [snack](https://snack.expo.dev/@react-native-paper/integrate-appbar-with-react-navigation) if you want to check the whole code! diff --git a/docs/docs/guides/06-recommended-libraries.md b/docs/5.x/docs/guides/recommended-libraries.md similarity index 96% rename from docs/docs/guides/06-recommended-libraries.md rename to docs/5.x/docs/guides/recommended-libraries.md index e70157ca1d..076dfb6871 100644 --- a/docs/docs/guides/06-recommended-libraries.md +++ b/docs/5.x/docs/guides/recommended-libraries.md @@ -24,17 +24,19 @@ An implementation of the [bottom sheet behaviour](https://material.io/design/com [gorhom/react-native-bottom-sheet](https://github.com/gorhom/react-native-bottom-sheet) An implementation of the [bottom sheet behaviour](https://material.io/design/components/sheets-bottom.html), maintained by [@Gorhom](https://twitter.com/Gorhom). - ## Date Picker + [web-ridge/react-native-paper-dates](https://github.com/web-ridge/react-native-paper-dates) Material Design themed [date picker](https://material.io/components/date-pickers), maintained by [@RichardLindhout](https://twitter.com/RichardLindhout) - + [react-native-community/react-native-datetimepicker](https://github.com/react-native-community/react-native-datetimepicker) ## Time Picker + [web-ridge/react-native-paper-dates](https://github.com/web-ridge/react-native-paper-dates) -Material Design themed [time picker](https://material.io/components/time-pickers), maintained by [@RichardLindhout](https://twitter.com/RichardLindhout) +Material Design themed [time picker](https://material.io/components/time-pickers), maintained by [@RichardLindhout](https://twitter.com/RichardLindhout) ## System Colors + [pchmn/expo-material3-theme](https://github.com/pchmn/expo-material3-theme) -Retrieve Material 3 system colors from Android 12+ devices \ No newline at end of file +Retrieve Material 3 system colors from Android 12+ devices diff --git a/docs/docs/guides/11-ripple-effect.md b/docs/5.x/docs/guides/ripple-effect.md similarity index 99% rename from docs/docs/guides/11-ripple-effect.md rename to docs/5.x/docs/guides/ripple-effect.md index 31845060d7..e6f6e503ef 100644 --- a/docs/docs/guides/11-ripple-effect.md +++ b/docs/5.x/docs/guides/ripple-effect.md @@ -40,6 +40,3 @@ import { Provider as PaperProvider } from 'react-native-paper'; // ... ``` - - - diff --git a/docs/docs/guides/08-theming-with-react-navigation.md b/docs/5.x/docs/guides/theming-with-react-navigation.md similarity index 95% rename from docs/docs/guides/08-theming-with-react-navigation.md rename to docs/5.x/docs/guides/theming-with-react-navigation.md index 6003953c02..5ccd78701d 100644 --- a/docs/docs/guides/08-theming-with-react-navigation.md +++ b/docs/5.x/docs/guides/theming-with-react-navigation.md @@ -21,15 +21,12 @@ import { DefaultTheme as NavigationDefaultTheme, } from '@react-navigation/native'; -import { - MD2LightTheme, - MD2DarkTheme, -} from 'react-native-paper'; +import { MD2LightTheme, MD2DarkTheme } from 'react-native-paper'; ``` ### Material Design 3 -From v5, React Native Paper theme colors structure follows the Material Design 3 (known as Material You) colors system, which differs significantly from both the previous Paper's theme and React Navigation theme. +From v5, React Native Paper theme colors structure follows the Material Design 3 (known as Material You) colors system, which differs significantly from both the previous Paper's theme and React Navigation theme. However, to simplify adapting React Navigation theme colors, to use the ones from React Native Paper, it's worth using a utility called `adaptNavigationTheme` – it accepts navigation-compliant themes in both modes and returns their equivalents adjusted to Material Design 3. @@ -49,10 +46,7 @@ const { LightTheme, DarkTheme } = adaptNavigationTheme({ Library exports also Material Design 3 themes in both modes: ```js -import { - MD3LightTheme, - MD3DarkTheme, -} from 'react-native-paper'; +import { MD3LightTheme, MD3DarkTheme } from 'react-native-paper'; ``` ## Combining theme objects @@ -117,8 +111,6 @@ export default function App() { } ``` - - Our goal here is to combine those two themes, so that we could control the theme for the entire application from a single place. To make things easier we can use [deepmerge](https://www.npmjs.com/package/deepmerge) package. We can install it with: @@ -135,10 +127,7 @@ import { DarkTheme as NavigationDarkTheme, DefaultTheme as NavigationDefaultTheme, } from '@react-navigation/native'; -import { - MD2DarkTheme, - MD2LightTheme, -} from 'react-native-paper'; +import { MD2DarkTheme, MD2LightTheme } from 'react-native-paper'; import merge from 'deepmerge'; const CombinedDefaultTheme = merge(MD2LightTheme, NavigationDefaultTheme); @@ -331,11 +320,7 @@ const Header = ({ scene }) => { }} > - + ); }; @@ -343,7 +328,7 @@ const Header = ({ scene }) => { And now you can switch between light and dark theme! -![paperGuide1](../../static/screenshots/themingWithReactNavigationDarkLightSwitch.gif) +![paperGuide1](../../../public/screenshots/themingWithReactNavigationDarkLightSwitch.gif) Thanks to the linking of themes that we did earlier, switching themes can be controlled with only one piece of state. diff --git a/docs/docs/guides/02-theming.mdx b/docs/5.x/docs/guides/theming.mdx similarity index 98% rename from docs/docs/guides/02-theming.mdx rename to docs/5.x/docs/guides/theming.mdx index 76869182ae..d2d4392fe8 100644 --- a/docs/docs/guides/02-theming.mdx +++ b/docs/5.x/docs/guides/theming.mdx @@ -2,7 +2,7 @@ title: Theming --- -import DynamicColorTheme from '@site/src/components/DynamicColorTheme.tsx'; +import DynamicColorTheme from '@docs/components/DynamicColorTheme.tsx'; # Theming @@ -73,14 +73,12 @@ A theme usually contains the following properties: - `colors` (`object`): various colors used throughout different elements. > The primary key color is used to derive roles for key components across the UI, such as the FAB, prominent buttons, active states, as well as the tint of elevated surfaces. - - `primary` - `onPrimary` - `primaryContainer` - `onPrimaryContainer` > The secondary key color is used for less prominent components in the UI such as filter chips, while expanding the opportunity for color expression. - - `secondary` - `onSecondary` - `secondaryContainer` @@ -89,34 +87,29 @@ A theme usually contains the following properties: > The tertiary key color is used to derive the roles of contrasting accents that can be used to balance primary and secondary colors or bring heightened attention to an element. > The tertiary color role is left for teams to use at their discretion and is intended to support broader color expression in products. - - `tertiary` - `onTertiary` - `tertiaryContainer` - `onTertiaryContainer` > The neutral key color is used to derive the roles of surface and background, as well as high emphasis text and icons. - - `background` - `onBackground` - `surface` - `onSurface` > The neutral variant key color is used to derive medium emphasis text and icons, surface variants, and component outlines. - - `surfaceVariant` - `onSurfaceVariant` - `outline` > In addition to the accent and neutral key color, the color system includes a semantic color role for error - - `error` - `onError` - `errorContainer` - `onErrorContainer` > Surfaces at elevation levels 0-5 are tinted via color overlays based on the primary color, such as app bars or menus. The addition of a grade from 0-5 introduces tonal variation to the surface baseline. - - `elevation` (`object`) - `level0` - transparent - `level1` - 5% opacity @@ -126,12 +119,10 @@ A theme usually contains the following properties: - `level5` - 14% opacity > Colors for disabled state - - `surfaceDisabled` - `onSurfaceDisabled` > These additional role mappings exist in a scheme and are mapped to components where needed. - - `shadow` - `inverseOnSurface` - `inverseSurface` @@ -197,7 +188,7 @@ Created schemes are following the Material Design 3 color system and covering co Passed source color into the util is translated into tones to automatically provide the range of tones that map to color roles. -![customColors](../../static/screenshots/custom-colors.png) +![customColors](../../../public/screenshots/custom-colors.png) _Source: [Material You Color System](https://m3.material.io/styles/color/the-color-system/custom-colors)_ @@ -344,11 +335,10 @@ By default, TypeScript works well whenever you change the value of the built-in There are two supported ways of overriding the theme: -1. Simple built-in theme overrides - when you only customize the values and - the whole theme schema remains the same -2. Advanced theme overrides - when you add new properties or - change the built-in schema shape - +1. Simple built-in theme overrides - when you only customize the values + and the whole theme schema remains the same +2. Advanced theme overrides - when you add new properties or + change the built-in schema shape :::caution TypeScript support for `withTheme` is currently limited to Material You (MD3) theme only. diff --git a/docs/docs/showcase.mdx b/docs/5.x/docs/showcase.mdx similarity index 79% rename from docs/docs/showcase.mdx rename to docs/5.x/docs/showcase.mdx index db58df8f24..19bce467e3 100644 --- a/docs/docs/showcase.mdx +++ b/docs/5.x/docs/showcase.mdx @@ -2,7 +2,7 @@ title: Showcase --- -import Showcase from '../src/components/Showcase.tsx'; +import Showcase from '@docs/components/Showcase.tsx'; # Who's using Paper? diff --git a/docs/5.x/index.mdx b/docs/5.x/index.mdx new file mode 100644 index 0000000000..91f12670f9 --- /dev/null +++ b/docs/5.x/index.mdx @@ -0,0 +1,38 @@ +--- +pageType: home +hero: + name: Cross-platform Material Design for React Native + tagline: Paper is a collection of customizable and production-ready components for React Native, following Google’s Material Design guidelines. + actions: + - text: Get started + link: /docs/guides/getting-started + theme: brand + - text: Try on Snack + link: https://snack.expo.dev/@react-native-paper/react-native-paper-example_v5 + theme: alt +features: + - title: Production-ready components + details: Buttons, inputs, lists, cards, menus, dialogs, navigation, and feedback patterns built for day-to-day product work. + icon: /react-native-paper/images/home/components.svg + span: 4 + - title: Material You theming + details: Adopt Material 3 color, typography, elevation, and state layers with a theme system designed for real apps. + icon: /react-native-paper/images/home/palette.svg + span: 4 + - title: Accessible by default + details: Common interactions, contrast, and state handling are already baked into the component surface instead of left to each screen. + icon: /react-native-paper/images/home/accessibility.svg + span: 4 + - title: Cross-platform parity + details: Keep design language and component behavior aligned across iOS, Android, and React Native Web from one codebase. + icon: /react-native-paper/images/home/devices.svg + span: 4 + - title: Dark mode support + details: Ship light and dark experiences with the same primitives, including adaptive surfaces and semantic color roles. + icon: /react-native-paper/images/home/moon.svg + span: 4 + - title: Migration-friendly docs + details: Browse stable 5.x, preview 6.x, and legacy static docs from one version selector without splitting the docs experience. + icon: /react-native-paper/images/home/versions.svg + span: 4 +--- diff --git a/docs/6.x/_nav.json b/docs/6.x/_nav.json new file mode 100644 index 0000000000..f7de8846cb --- /dev/null +++ b/docs/6.x/_nav.json @@ -0,0 +1,17 @@ +[ + { + "text": "Guides", + "link": "/docs/guides/getting-started", + "activeMatch": "^/(?:6\\.x/)?docs/guides/" + }, + { + "text": "Components", + "link": "/docs/components/ActivityIndicator", + "activeMatch": "^/(?:6\\.x/)?docs/components/" + }, + { + "text": "Showcase", + "link": "/docs/showcase", + "activeMatch": "^/(?:6\\.x/)?docs/showcase$" + } +] diff --git a/docs/6.x/docs/_meta.json b/docs/6.x/docs/_meta.json new file mode 100644 index 0000000000..9e4ac38659 --- /dev/null +++ b/docs/6.x/docs/_meta.json @@ -0,0 +1,17 @@ +[ + { + "type": "dir-section-header", + "name": "guides", + "label": "Guides" + }, + { + "type": "dir-section-header", + "name": "components", + "label": "Components" + }, + { + "type": "file", + "name": "showcase", + "label": "Showcase" + } +] diff --git a/docs/6.x/docs/guides/_meta.json b/docs/6.x/docs/guides/_meta.json new file mode 100644 index 0000000000..11525283c4 --- /dev/null +++ b/docs/6.x/docs/guides/_meta.json @@ -0,0 +1,15 @@ +[ + "getting-started", + "theming", + "icons", + "fonts", + "react-native-web", + "recommended-libraries", + "contributing", + "theming-with-react-navigation", + "bottom-navigation", + "react-navigation", + "rtl", + "ripple-effect", + "migration" +] diff --git a/docs/6.x/docs/guides/bottom-navigation.md b/docs/6.x/docs/guides/bottom-navigation.md new file mode 100644 index 0000000000..380a8b8be3 --- /dev/null +++ b/docs/6.x/docs/guides/bottom-navigation.md @@ -0,0 +1,93 @@ +--- +title: Using BottomNavigation with React Navigation +--- + +Build a Material Design bottom tab bar by combining two pieces: + +- `@react-navigation/bottom-tabs` handles routing, state, and screen options. +- `BottomNavigation.Bar` renders the Material 3 tab bar (ripple, badges, shifting/labeled modes). + + + +:::info +Install [`@react-navigation/native`](https://reactnavigation.org/docs/getting-started) and [`@react-navigation/bottom-tabs`](https://reactnavigation.org/docs/bottom-tab-navigator) first. +::: + +## Quick example + +Pass a `BottomNavigation.Bar` to the navigator's `tabBar` prop. The bar reads navigation state and dispatches `tabPress` events back: + +```jsx +import MaterialCommunityIcons from '@react-native-vector-icons/material-design-icons'; +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; +import { CommonActions } from '@react-navigation/native'; +import { BottomNavigation } from 'react-native-paper'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +const Tab = createBottomTabNavigator(); + +function MyTabs() { + const insets = useSafeAreaInsets(); + + return ( + ( + { + const event = navigation.emit({ + type: 'tabPress', + target: route.key, + canPreventDefault: true, + }); + if (event.defaultPrevented) { + preventDefault(); + } else { + navigation.dispatch({ + ...CommonActions.navigate(route.name, route.params), + target: state.key, + }); + } + }} + renderIcon={({ route, focused, color }) => + descriptors[route.key].options.tabBarIcon?.({ + focused, + color, + size: 24, + }) ?? null + } + getLabelText={({ route }) => { + const { options } = descriptors[route.key]; + return typeof options.tabBarLabel === 'string' + ? options.tabBarLabel + : typeof options.title === 'string' + ? options.title + : route.name; + }} + /> + )} + > + ( + + ), + }} + /> + ( + + ), + }} + /> + + ); +} +``` diff --git a/docs/6.x/docs/guides/contributing.mdx b/docs/6.x/docs/guides/contributing.mdx new file mode 100644 index 0000000000..2861043dc3 --- /dev/null +++ b/docs/6.x/docs/guides/contributing.mdx @@ -0,0 +1,7 @@ +--- +title: Contributing +--- + +import Contributing from '../../../../CONTRIBUTING.md'; + + diff --git a/docs/6.x/docs/guides/fonts.md b/docs/6.x/docs/guides/fonts.md new file mode 100644 index 0000000000..139c5a9635 --- /dev/null +++ b/docs/6.x/docs/guides/fonts.md @@ -0,0 +1,469 @@ +--- +title: Fonts +--- + +# Fonts + +## Installing custom fonts + +The easiest way to install custom fonts to your RN project is do as follows: + +1. Define path to assets directory with fonts in project: + +Example: + +```js + module.exports = { + ... + assets: [ + './assets/fonts' + ], +``` + +:::note +`fonts` is a folder with `.ttf` files +::: + +2. Place your font files in your assets directory. + +3. Link font files, using the following command in the terminal: + +- React Native `>= 0.69`: + +```sh +npx react-native-asset +``` + +- React Native `< 0.69`: + +```sh +npx react-native link +``` + +4. Restart your project to refresh changes. + +Now, you are able to use `fontFamily` from font files. + +## Configuring fonts in ThemeProvider + +:::note +Older Material Design 2 platform-split font configuration (`configureFonts` with per-platform `ios` / `android` / `web` keys) is no longer supported. Use the Material Design 3 typescale and `configureFonts` as documented below. +::: + +### Material Design 3 + +#### Variants + +In the latest version fonts in theme are structured based on the `variant` keys e.g. `displayLarge` or `bodyMedium` which are then used in `Text`'s component throughout the whole library. + +:::info +The default `fontFamily` is different per particular platform: + +```js +Platform.select({ + web: 'Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif', + ios: 'System', + default: 'sans-serif', // and 'sans-serif-medium' for `fontWeight:"500"` +}), +``` + +Material Design 3 typescale uses two font families: + +- **Brand**: Display, Headline and Title Large +- **Plain**: Title Medium/Small, Label and Body + +The default theme uses the platform default for both font families. + +::: + +- #### Display + +
    +
    + +```json +"displaySmall": { + "fontFamily": "Font", + "fontSize": 36, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 44, +} +``` + +
    + +
    + +```json +"displayMedium": { + "fontFamily": "Font", + "fontSize": 45, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 52, +} +``` + +
    + +
    + +```json +"displayLarge": { + "fontFamily": "Font", + "fontSize": 57, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 64, +} +``` + +
    +
    + +- #### Headline + +
    +
    + +```json +"headlineSmall": { + "fontFamily": "Font", + "fontSize": 24, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 32, +} +``` + +
    + +
    + +```json +"headlineMedium": { + "fontFamily": "Font", + "fontSize": 28, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 36, +} +``` + +
    + +
    + +```json +"headlineLarge": { + "fontFamily": "Font", + "fontSize": 32, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 40, +} +``` + +
    +
    + +- #### Title + +
    +
    + +```json +"titleSmall": { + "fontFamily": "Font", + "fontSize": 14, + "fontWeight": "500", + "letterSpacing": 0.1, + "lineHeight": 20, +} +``` + +
    + +
    + +```json +"titleMedium": { + "fontFamily": "Font", + "fontSize": 16, + "fontWeight": "500", + "letterSpacing": 0.15, + "lineHeight": 24, +} +``` + +
    + +
    + +```json +"titleLarge": { + "fontFamily": "Font", + "fontSize": 22, + "fontWeight": "400", + "letterSpacing": 0, + "lineHeight": 28, +} +``` + +
    +
    + +- #### Label + +
    +
    + +```json +"labelSmall": { + "fontFamily": "Font", + "fontSize": 11, + "fontWeight": "500", + "letterSpacing": 0.5, + "lineHeight": 16, +} +``` + +
    + +
    + +```json +"labelMedium": { + "fontFamily": "Font", + "fontSize": 12, + "fontWeight": "500", + "letterSpacing": 0.5, + "lineHeight": 16, +} +``` + +
    + +
    + +```json +"labelLarge": { + "fontFamily": "Font", + "fontSize": 14, + "fontWeight": "500", + "letterSpacing": 0.1, + "lineHeight": 20, +} +``` + +
    +
    + +- #### Body + +
    +
    + +```json +"bodySmall": { + "fontFamily": "Font", + "fontSize": 12, + "fontWeight": "400", + "letterSpacing": 0.4, + "lineHeight": 16, +} +``` + +
    + +
    + +```json +"bodyMedium": { + "fontFamily": "Font", + "fontSize": 14, + "fontWeight": "400", + "letterSpacing": 0.25, + "lineHeight": 20, +} +``` + +
    + +
    + +```json +"bodyLarge": { + "fontFamily": "Font", + "fontSize": 16, + "fontWeight": "400", + "letterSpacing": 0.5, + "lineHeight": 24, +} +``` + +
    +
    + +- #### Emphasized + +Each variant also has an `Emphasized` counterpart with a heavier font weight: `displayLargeEmphasized`, +`bodyMediumEmphasized` etc. + +```json +"bodyMediumEmphasized": { + "fontFamily": "Font", + "fontSize": 14, + "fontWeight": "500", + "letterSpacing": 0.25, + "lineHeight": 20, +} +``` + +:::info +If any component uses Paper's `Text` component, without specified variant, then `default` variant is applied: + +```json +"default": { + "fontFamily": "FontFamily", + "fontWeight": "400", + "letterSpacing": 0, +}, +``` + +::: + +#### Using `configureFonts` helper + +- If there is a need to create a custom font variant, prepare its config object including required all fonts properties. After that, defined `fontConfig` has to be passed under the `variant` name as `config` into the params object: + +```js +import * as React from 'react'; +import { + configureFonts, + MD3LightTheme, + PaperProvider, +} from 'react-native-paper'; +import App from './App'; + +const fontConfig = { + customVariant: { + fontFamily: Platform.select({ + web: 'Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif', + ios: 'System', + default: 'sans-serif', + }), + fontWeight: '400', + letterSpacing: 0.5, + lineHeight: 22, + fontSize: 20, + }, +}; + +const theme = { + ...MD3LightTheme, + fonts: configureFonts({ config: fontConfig }), +}; + +export default function Main() { + return ( + + + + ); +} +``` + +If you're using TypeScript you will need to create a custom `Text` component which accepts your custom variants: + +```typescript +import { customText } from 'react-native-paper'; + +// Use this instead of importing `Text` from `react-native-paper` +export const Text = customText<'customVariant'>(); +``` + +- In order to override one of the available `variant`'s font properties, pass the modified `fontConfig` under specific `variant` name as `config` into the params object: + +```js +import * as React from 'react'; +import { + configureFonts, + MD3LightTheme, + PaperProvider, +} from 'react-native-paper'; +import App from './App'; + +const fontConfig = { + bodyLarge: { + letterSpacing: 0.5, + lineHeight: 22, + fontSize: 20, + }, +}; + +const theme = { + ...MD3LightTheme, + fonts: configureFonts({ config: fontConfig }), +}; + +export default function Main() { + return ( + + + + ); +} +``` + +- However, if you just want to override any font property e.g. `fontFamily` or `letterSpacing` for all variants, you can pass the `fontConfig` as a `config` into the params object without specifying variant name: + +```js +import * as React from 'react'; +import { + configureFonts, + MD3LightTheme, + PaperProvider, +} from 'react-native-paper'; +import App from './App'; + +const fontConfig = { + fontFamily: 'NotoSans', +}; + +const theme = { + ...MD3LightTheme, + fonts: configureFonts({ config: fontConfig }), +}; + +export default function Main() { + return ( + + + + ); +} +``` + +## Variable fonts + +Although React Native Paper supports `fontWeight` and `fontStyle` properties, there are multiple limitations to custom +fonts in React Native. Using custom [variable fonts](https://fonts.google.com/knowledge/introducing_type/introducing_variable_fonts) +is especially problematic, with some platforms failing to render variants entirely. To ensure correct typography in your +app, we suggest installing each font variant as a separate file. Below you'll find example on how to set up React Native Paper +theme with custom fonts. + +Should you decide to use a variable font anyway, second example will show you how to test if the font is rendered correctly in React Native on all platforms. + +
    + Variable fonts examples + +
    diff --git a/docs/6.x/docs/guides/getting-started.mdx b/docs/6.x/docs/guides/getting-started.mdx new file mode 100644 index 0000000000..45ccaf2c01 --- /dev/null +++ b/docs/6.x/docs/guides/getting-started.mdx @@ -0,0 +1,200 @@ +--- +title: Getting Started +--- + +import TabItem from '@docs/components/TabItem'; +import Tabs from '@docs/components/Tabs'; + +# Getting Started + +## Installation + +React Native Paper 6.x requires React 19 and React Native's New Architecture. + +Open a Terminal in your project's folder and install the library: + +```bash npm2yarn +npm install react-native-paper@alpha +``` + +React Native Paper 6.x is a prerelease published under the npm `alpha` tag, and `npm install react-native-paper` without the tag still installs 5.x. + +React Native Paper uses [react-native-safe-area-context](https://github.com/th3rdwave/react-native-safe-area-context) for handling safe area, [react-native-reanimated](https://docs.swmansion.com/react-native-reanimated/) and [react-native-worklets](https://docs.swmansion.com/react-native-worklets/) for animations. + + + + +```bash +npx expo install react-native-safe-area-context react-native-reanimated react-native-worklets +``` + +Minimum requirements: + +- `react-native-reanimated` **≥ 4.3.0** +- `react-native-worklets` **≥ 0.8.1** +- `react-native-safe-area-context` — version compatible with your React Native toolchain. + +Follow the [React Native Reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/). + +You also need to install [@react-native-vector-icons/material-design-icons](https://github.com/oblador/react-native-vector-icons/tree/master/packages/material-design-icons), because some components use these icons internally: + +```bash +npx expo install @react-native-vector-icons/material-design-icons +``` + +If you have a `babel.config.js` or `.babelrc` file, make sure that it includes `babel-preset-expo`. + + + + +```bash npm2yarn +npm install react-native-safe-area-context react-native-reanimated react-native-worklets +``` + +Minimum requirements: + +- `react-native-reanimated` **≥ 4.3.0** +- `react-native-worklets` **≥ 0.8.1** +- `react-native-safe-area-context` — version compatible with your React Native toolchain. + +Follow the [React Native Reanimated installation guide](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/). + +If you're on a vanilla React Native project, you also need to install [@react-native-vector-icons/material-design-icons](https://github.com/oblador/react-native-vector-icons/tree/master/packages/material-design-icons). + +Specifically, the `MaterialDesignIcons` icon pack needs to be included in the project because some components use those icons internally, such as `AppBar.BackAction` on Android. + +```bash npm2yarn +npm install @react-native-vector-icons/material-design-icons +``` + +:::note +`@react-native-vector-icons/material-design-icons` may require some additional setup steps depending on platform. To ensure the icon font is configured correctly, please follow the upstream [installation guide](https://github.com/oblador/react-native-vector-icons?tab=readme-ov-file#setup). +::: + +:::info +If you don't want to install vector icons, you can use [babel-plugin-optional-require](https://github.com/satya164/babel-plugin-optional-require) to opt-out. +::: + + + + +### Bundle size optimization + +To get smaller bundle size by excluding modules you don't use, you can use our optional babel plugin. The plugin automatically rewrites the import statements so that only the modules you use are imported instead of the whole library. Add `react-native-paper/babel` to the `plugins` section in your `babel.config.js` for production environment. It should look like this: + +```js +module.exports = { + presets: ['module:metro-react-native-babel-preset'], + env: { + production: { + plugins: ['react-native-paper/babel'], + }, + }, +}; +``` + +If you created your project using Expo, it'll look something like this: + +```js +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + env: { + production: { + plugins: ['react-native-paper/babel'], + }, + }, + }; +}; +``` + +The plugin only works if you are importing the library using ES2015 import statements and not with `require`. + +:::note +The above examples are for the latest `react-native` using Babel 7. If you have `react-native <= 0.55`, you'll have a `.babelrc` file instead of a `babel.config.js` file and the content of the file will be different. +::: + +If you're using Flow for typechecking your code, you need to add the following under the `[options]` section in your `.flowconfig`: + +```ini +module.file_ext=.js +module.file_ext=.native.js +module.file_ext=.android.js +module.file_ext=.ios.js +``` + +## Usage + +Wrap your root component in `PaperProvider` from `react-native-paper`(if you are using versions prior to 5.8.0 you need to use `Provider`). If you have a vanilla React Native project, it's a good idea to add it in the component which is passed to `AppRegistry.registerComponent`. This will usually be in the `index.js` file. If you have an Expo project, you can do this inside the exported component in the `App.js` file. + +Example: + +```js +import * as React from 'react'; +import { AppRegistry } from 'react-native'; +import { PaperProvider } from 'react-native-paper'; +import { name as appName } from './app.json'; +import App from './src/App'; + +export default function Main() { + return ( + + + + ); +} + +AppRegistry.registerComponent(appName, () => Main); +``` + +The `PaperProvider` component provides the theme to all the components in the framework. It also acts as a portal to components which need to be rendered at the top level. + +If you have another provider (such as `Redux`), wrap it outside `PaperProvider` so that the context is available to components rendered inside a `Modal` from the library: + +```js +import * as React from 'react'; +import { PaperProvider } from 'react-native-paper'; +import { Provider as StoreProvider } from 'react-redux'; +import App from './src/App'; +import store from './store'; + +export default function Main() { + return ( + + + + + + ); +} +``` + +## Customization + +You can provide a custom theme to customize the colors, typescales etc. with the `Provider` component. Check the [Material Design 3 default theme](https://github.com/callstack/react-native-paper/blob/main/src/theme/schemes/LightTheme.tsx) to see what customization options are supported. + +Example: + +```js +import * as React from 'react'; +import { LightTheme, PaperProvider } from 'react-native-paper'; +import App from './src/App'; + +const theme = { + ...LightTheme, + colors: { + ...LightTheme.colors, + primary: 'tomato', + secondary: 'yellow', + }, +}; + +export default function Main() { + return ( + + + + ); +} +``` diff --git a/docs/6.x/docs/guides/icons.mdx b/docs/6.x/docs/guides/icons.mdx new file mode 100644 index 0000000000..813678733d --- /dev/null +++ b/docs/6.x/docs/guides/icons.mdx @@ -0,0 +1,169 @@ +--- +title: Icons +--- + +import TabItem from '@docs/components/TabItem'; +import Tabs from '@docs/components/Tabs'; +import IconsList from '@docs/components/IconsList.tsx'; + +# Icons + +## Configuring icons + +Many of the components render icon names using [`@react-native-vector-icons/material-design-icons`](https://github.com/oblador/react-native-vector-icons/tree/master/packages/material-design-icons). In Expo projects this usually works without extra setup. In vanilla React Native projects, install the package and complete any required platform setup as described in the [getting started guide](./getting-started). + +:::note +If you opted out of vector icons support using [babel-plugin-optional-require](https://github.com/satya164/babel-plugin-optional-require), you won't be able to use icon names for the icon prop. Some components may not look correct without vector icons and might need extra configuration. +::: + +## Using the `icon` prop + +Many components such as `Button` accept an `icon` prop which is used to display an icon. The `icon` prop supports the following types of values: + +### 1. An icon name + +You can pass the name of an icon from [`MaterialDesignIcons`](https://pictogrammers.com/library/mdi/). This will use the [`@react-native-vector-icons/material-design-icons`](https://github.com/oblador/react-native-vector-icons/tree/master/packages/material-design-icons) library to display the icon. + +Example: + +```js + +``` + +:::note +Choose the correct tab below based on your `react-native-vector-icons` version: + +- Use **latest** tab if you're using the latest version of `@react-native-vector-icons/material-design-icons` +- Use **10.x** tab if you're still using the old single package `react-native-vector-icons`, version `10.x` + +If you are using `@expo/vector-icons`, please check their [searchable list of icons](https://icons.expo.fyi/). +::: + + + +
    + See the list of supported icons + +
    +
    + +
    + See the list of supported icons + +
    +
    +
    + +### 2. An image source + +You can pass an image source, such as an object of shape `{ uri: 'https://path.to' }` or a local image: `require('../path/to/image.png')` to use as an icon. The image might be rendered with a different color than the one provided depending on the component. If don't want this behavior, see the next example to pass an `Image` element. + +Remote image: + +```js + +``` + +Local image: + +```js + +``` + +### 3. A render function + +You can pass a function which returns a react element to be used an icon. The function receives an object with `size` and `color` properties as its arguments. The element is used as is without any modification. However, it might get clipped if the provided element's size is bigger than what the component renders. It's up to you to make sure that the size of the element is correct. + +Example: + +```js + +``` + +### 4. Use custom icons + +If you want to use icons other than `MaterialDesignIcons` you need to import the icons and pass it to the `settings` prop within `PaperProvider`. + +Example: + +```js +import AwesomeIcon from '@react-native-vector-icons/fontawesome'; +// ... + + , + }} + theme={this.state.theme} +> + // ... +; +``` + +## RTL support + +If you want your icon to behave properly in a RTL environment, you can pass an object to the `icon` prop with shape: `{ source: { uri: 'https://path.to' }, direction : 'rtl' }`. `source` can be any of the values that the `icon` prop accepts in [option 1](#1.-an-icon-name) and [option 2](#2.-an-image-source). For `direction` you have a few options: + +1. `auto` - uses the device language to determine if icon should be displayed from rtl. Uses the `I18nManager` module to get this info. +2. `rtl` - flips the icon so that it is rtl, this is regardless of the device language. +3. `ltr` - displays from ltr, even if in an rtl environment. + +Example for using an image source: + +```js + +``` + +Example for using an icon name: + +```js + +``` + +You can also use a render function. Along with `size` and `color`, you have access to `direction` which will either be `'rtl'` or `'ltr'`. You can then decide how to render your icon component accordingly. + +Example of using a render function: + +```js + +``` diff --git a/docs/6.x/docs/guides/migration.md b/docs/6.x/docs/guides/migration.md new file mode 100644 index 0000000000..72aee6d00f --- /dev/null +++ b/docs/6.x/docs/guides/migration.md @@ -0,0 +1,575 @@ +--- +title: Migration from Paper 5.x to 6.x +--- + +## General changes + +### Animations + +React Native Paper 6 uses [Reanimated](https://docs.swmansion.com/react-native-reanimated/) for most animations instead of the built-in React Native `Animated` API. So make sure to install `react-native-reanimated` 4.3.0 or later and `react-native-worklets` 0.8.1 or later, then complete the Reanimated setup. See the [getting started guide](./getting-started) for Expo and Community CLI instructions. + +The following props now accept animated styles returned from `useAnimatedStyle`. They no longer accept `Animated.Value` or `Animated.AnimatedInterpolation` where these were previously supported: + +- `Appbar.Action` and `Appbar.BackAction`: `style` +- `Badge`: `style` +- `Banner`: `style` +- `Button`: `style` +- `Card`: `style` +- `Chip`: `style` +- `Dialog`: `style` +- `Drawer.CollapsedItem`: `style` +- `FAB` and `FAB.Extended`: `style` +- `IconButton`: `style` +- `Menu`: `contentStyle` +- `Modal`: `contentContainerStyle` +- `Searchbar`: `style` +- `Snackbar`: `style` +- `Surface`: `style` + +So you can use Reanimated's `useSharedValue` and `useAnimatedStyle` to animate these components instead of the React Native `Animated` API. + +```tsx +import { useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; + +const MyComponent = () => { + const opacity = useSharedValue(1); + const animatedStyle = useAnimatedStyle(() => ({ + opacity: opacity.value, + })); + + return Button; +}; +``` + +### Elevation + +The `elevation` prop no longer accepts a React Native `Animated.Value` in the following components: + +- `Banner` +- `Card` +- `Searchbar` +- `Snackbar` +- `Surface` + +You can use an elevation level from `0` to `5` instead. Changes to the elevation level are animated automatically. + +### Styles + +The following component style props no longer support overriding their background color or border radius: + +- `Banner` +- `Button` +- `Card` +- `Chip` +- `Dialog` +- `Menu`: `contentStyle` +- `Searchbar` +- `Snackbar` + +You can use the component's color prop where available, or override the corresponding theme colors. + +### Test IDs + +Hardcoded default test IDs have been removed for the components listed below: + +- `Appbar.Content`: `appbar-content` +- `Appbar.Header`: `appbar-header` +- `BottomNavigation`: `bottom-navigation` +- `BottomNavigation.Bar`: `bottom-navigation-bar` +- `Button`: `button` +- `Card`: `card` +- `Chip`: `chip` +- `Drawer.CollapsedItem`: `drawer-collapsed-item` +- `FAB`: `floating-action-button` +- `FAB.Extended`: `extended-floating-action-button` +- `FAB.Menu`: `floating-action-button-menu` +- `IconButton`: `icon-button` +- `Menu`: `menu` +- `Menu.Item`: `menu-item` +- `Modal`: `modal` +- `ProgressBar`: `progress-bar` +- `Searchbar`: `search-bar` +- `Surface`: `surface` + +You can specify a `testID` explicitly to restore each component's own test ID. + +These components used to also derive test IDs for internal, implementation-only elements by appending a suffix to the `testID` prop (e.g. `${testID}-container`, `${testID}-icon`, `${testID}-outline`). They have been removed entirely. + +If you were relying on internal test IDs, update your tests not to rely on internal implementation details and only interact with elements or assert content your users can reach, e.g.: query by role, label, text etc., or `testID` props accepted by the component. + +Some components now accept explicit `testID` props for their interactable elements: + +- `BottomNavigation`: `barTestID` for the internal `BottomNavigation.Bar`, replacing the previous `${testID}-bar` derivation. +- `Chip`: `closeIconTestID` for the close icon button. +- `Dialog` and `Modal`: `overlayTestID` for the overlay displayed behind the content. +- `Menu`: `overlayTestID` for the overlay displayed behind the menu. +- `Searchbar`: `searchTestID`, `clearTestID`, and `trailingTestID` for the search, clear, and trailing icon buttons. +- `Snackbar`: `iconTestID` for the icon button. + +## Components + +### Appbar + +The `style` props for `Appbar` and `Appbar.Header` no longer accept `Animated.Value` or `Animated.AnimatedInterpolation`. They only accept static styles. + +The `style.elevation` property is no longer supported. Use the `elevated` prop to control Appbar elevation. + +### Surface + +- The `elevation` prop no longer accepts a React Native `Animated.Value`. Any `elevation` changes are animated automatically. +- The `style` prop no longer configures elevation, background color, or border radius. Use these props instead: + - `elevation` + - `backgroundColor` + - `borderRadius` + - `borderBottomEndRadius` + - `borderBottomLeftRadius` + - `borderBottomRightRadius` + - `borderBottomStartRadius` + - `borderEndEndRadius` + - `borderEndStartRadius` + - `borderStartEndRadius` + - `borderStartStartRadius` + - `borderTopEndRadius` + - `borderTopLeftRadius` + - `borderTopRightRadius` + - `borderTopStartRadius` + - `borderCurve` +- The `pointerEvents` prop is no longer supported as it's deprecated in React Native Web. You can specify `pointerEvents` in the `style` prop instead. +- The `overflow: 'hidden'` style is no longer supported in `style` as it can clip shadows. You can nest a `View` inside the `Surface` and apply `overflow: 'hidden'` to that instead. +- The default `testID` for `Surface` was removed. You can specify a `testID` explicitly if you need it. + +e.g.: + +```diff + ++ + Content ++ + +``` + +### Modal + +`Modal` now uses a `Portal` internally and doesn't require an explicit `Portal` wrapper. So you need to remove any existing `Portal` wrappers around `Modal`: + +```diff +- +- +- Content +- +- ++ ++ Content ++ +``` + +- The `contentContainerStyle` prop no longer configures the background color or any border radius property. We have added new props for these: + - `contentBackgroundColor` + - `contentBorderRadius` +- We have added the `contentElevation` prop to configure the elevation of the modal content. + +e.g.: + +```diff + + Content + +``` + +The modal content now has the `dialog` role, so it needs an accessible name. You can provide one with the new `aria-label` prop: + +```diff +- ++ + Content + +``` + +The overlay behind the content is now hidden from assistive technology. Instead, when the modal is `dismissable`, screen reader users can dismiss it with a visually hidden button inside the dialog. + +The `overlayAccessibilityLabel` prop was renamed to `dismissAccessibilityLabel`, which is used for the button's accessibility label: + +```diff + + Content + +``` + +Previously, the Android back button dismissed the modal when `dismissable` was `true`, even if `dismissableBackButton` was `false`. The `dismissableBackButton` prop can now prevent the modal from being dismissed via the back button independently of the `dismissable` prop. + +### Dialog + +`Dialog` now uses a `Portal` internally and doesn't require an explicit `Portal` wrapper. So you need to remove any existing `Portal` wrappers around `Dialog`: + +```diff +- +- +- Alert +- +- ++ ++ Alert ++ +``` + +The dialog now has the `dialog` role. On web, the dialog's accessible name is set automatically by `Dialog.Title`. You can specify a different name with the new `aria-label` prop, e.g. when the dialog has no title: + +```jsx + + + Are you sure? + + +``` + +When the dialog is `dismissable`, screen reader users can dismiss it with a visually hidden button inside the dialog. You can change the button's accessibility label with the new `dismissAccessibilityLabel` prop. + +- The default elevation changed from level `1` to level `3`. +- The `style` prop no longer configures the background color or border radius. You can override `theme.colors.surfaceContainerHigh` and `theme.shapes.corner.extraLarge` using the `theme` prop instead. + +#### `Dialog.Actions` + +`Dialog.Actions` no longer injects `compact` on its buttons. To keep the previous behavior, you need to add the `compact` prop to each button: + +```tsx +// Before (v5) + + + + +// After (v6) + + + +``` + +### Menu + +The `overlayAccessibilityLabel` prop was renamed to `dismissAccessibilityLabel`: + +```diff + + + +``` + +### Searchbar + +The misspelled `traileringIcon` props have been renamed: + +- **`traileringIcon`** → **`trailingIcon`** +- **`traileringIconColor`** → **`trailingIconColor`** +- **`traileringIconAccessibilityLabel`** → **`trailingIconAccessibilityLabel`** +- **`onTraileringIconPress`** → **`onTrailingIconPress`** + +```diff + +``` + +### TextInput + +The Paper 6.x `TextInput` is a complete rewrite with a new API. Import the component the same way, but note that the props and behavior have changed significantly. + +#### Types + +```tsx +import { TextInput, type TextInputProps } from 'react-native-paper'; +``` + +#### Variant + +- **`mode="flat"`** → **`variant="filled"`** +- **`mode="outlined"`** → **`variant="outlined"`** + +```tsx +// Before (v5) + + + +// After (v6) + + +``` + +#### Adornments + +- **`left` / `right`** → **`startAccessory` / `endAccessory`** +- **`TextInput.Affix`** → **`prefix` / `suffix`**, or **`TextInput.Icon`**, or **`startAccessory` / `endAccessory`** + +```tsx +// Before (v5) +} + right={} +/> + +// After (v6) + } + endAccessory={(p) => } + maxLength={100} + prefix="$" + suffix="/100" + counter +/> +``` + +#### Label and supporting text + +- **`label: React.Element | string`** → **`string`** +- **`HelperText`** was removed; use **`supportingText`**. + +```tsx +// Before (v5) +<> + + + Enter a valid email + + + +// After (v6) + +``` + +#### Removed props + +No direct `TextInput` equivalents for: + +- **`dense`**, **`contentStyle`**, **`underlineStyle`** +- **`underlineColor`**, **`activeUnderlineColor`**, **`outlineColor`**, **`activeOutlineColor`**, **`textColor`** + +Use **`style`** on the inner input and the **`theme`** for colors. + +```tsx +import { MD3LightTheme, TextInput } from 'react-native-paper'; + +const theme = { + ...MD3LightTheme, + colors: { + ...MD3LightTheme.colors, + outline: '#79747E', + primary: '#6750A4', + }, +}; + +// Before (v5) + + +// After (v6) + +``` + +### Card + +#### `Card` layout + +`Card` and its related components have been reworked. Instead of injecting padding to its children, the `Card` component now adds a padding (`16dp`) around its content as well as a gap (`16dp`) between its children. + +For the related components: + +- `Card.Cover` and `Card.Actions` now apply negative margins to extend into the Card's edges: + - `Card.Cover` applies top, left and right margins for vertical cards and left, top, and bottom margins for horizontal cards. + - `Card.Actions` applies bottom, left, and right margins for vertical cards and left, bottom, and right margins for horizontal cards. +- `Card.Title` and `Card.Content` no longer apply padding around them. + +This means, in a typical card layout, the Card will automatically apply the necessary spacing between these sections: + +```tsx + + + + + Card content + + + + + + +``` + +So your existing layouts with the following structure will continue to work as before: + +- `Card` without `Card.Cover` or `Card.Actions`. +- `Card` with `Card.Cover` at the top and/or `Card.Actions` at the bottom. + +If you have `Card.Cover` or `Card.Actions` in positions other than the top and bottom respectively, you will need to adjust their margins. For example, for a `Card.Cover` in the middle of the Card, you would need to set its top and bottom margins to zero: + +```tsx + + + + + Card content + + +``` + +We have also added a `direction` prop to the `Card` component, which lets you arrange its items horizontally instead of the default vertical layout. So if you have custom `flexDirection` styles on the Card, you should replace them with the `direction` prop. + +```tsx +// Before (v5) + + + + + Card content + + + + + + + +// After (v6) + + + + + Card content + + + + + + +``` + +#### `Card.Actions` + +`Card.Actions` no longer injects `mode` on the buttons. To keep the previous behavior, you need to set `mode="outlined"` on the first button and `mode="contained"` on the rest: + +```tsx +// Before (v5) + + + + + +// After (v6) + + + + +``` + +### List + +#### `List.Accordion` + +An accordion with a `left` element now indents only its `List.Item` children. Anything else you put inside keeps its own padding, so indent it yourself to line it up with the items. + +```tsx +// Before (v5) + }> + + Custom row + + + +// After (v6) + }> + + Custom row + + +``` + +`theme` set on `List.Accordion` no longer reaches its children either. Pass it to the child that needs the override. + +### ToggleButton + +`ToggleButton`, `ToggleButton.Group` and `ToggleButton.Row` were removed. For an +icon-only toggle, use `IconButton` with the `selected` prop. For a set of +mutually exclusive options, use `SegmentedButtons`. + +```tsx +// Before (v5) + + + + + +// After (v6) +<> + setValue('bold')} + /> + setValue('italic')} + /> + +``` diff --git a/docs/6.x/docs/guides/react-native-web.md b/docs/6.x/docs/guides/react-native-web.md new file mode 100644 index 0000000000..9b9d4e075f --- /dev/null +++ b/docs/6.x/docs/guides/react-native-web.md @@ -0,0 +1,43 @@ +--- +title: Using on the Web +--- + +# Using on the Web + +React Native Paper supports web via [React Native for Web](https://necolas.github.io/react-native-web/), which lets you run React Native components in a browser using React DOM. + +Before continuing, make sure you have React Native Paper installed and configured by following the [Getting Started guide](getting-started). + +## Setting up web support with Expo + +The recommended way to run React Native Paper on the web is with Expo, which has built-in web support via React Native for Web. Install the required dependencies: + +```bash +npx expo install react-dom react-native-web @expo/metro-runtime +``` + +Then start the web server: + +```bash +npx expo start --web +``` + +No additional bundler configuration is required. See the [Expo Web docs](https://docs.expo.dev/workflow/web/) for details on how Expo configures React Native for Web under the hood. + +## Without Expo + +If you're not using Expo, follow the [React Native for Web setup guide](https://necolas.github.io/react-native-web/docs/setup/) to configure your bundler. The setup covers aliasing `react-native` to `react-native-web` in webpack, Babel, and Jest. + +You will also need to manually load the Material Design icon font used by Paper. Add the following to your HTML shell or inject it at the root of your app: + +```css +@font-face { + font-family: 'MaterialDesignIcons'; + src: url('~@react-native-vector-icons/material-design-icons/fonts/MaterialDesignIcons.ttf') + format('truetype'); +} +``` + +## Load the Roboto font (optional) + +The default Paper theme uses the Roboto typeface. With Expo, use the [`@expo-google-fonts/roboto`](https://github.com/expo/google-fonts/tree/master/font-packages/roboto) package. For other setups, follow the instructions on the [Roboto specimen page](https://fonts.google.com/specimen/Roboto). diff --git a/docs/6.x/docs/guides/react-navigation.md b/docs/6.x/docs/guides/react-navigation.md new file mode 100644 index 0000000000..6c01200f11 --- /dev/null +++ b/docs/6.x/docs/guides/react-navigation.md @@ -0,0 +1,286 @@ +--- +title: Integrate AppBar with React Navigation +--- + +# Integrate AppBar with react-navigation + +## Prerequisites + +- `react-native-paper` +- `react-navigation` + +We assume that you have already installed the mentioned libraries above, otherwise please check out the guides below. + +[React Native Paper - Getting Started](https://callstack.github.io/react-native-paper/docs/guides/getting-started) + +[React Navigation - Getting Started](https://reactnavigation.org/docs/getting-started/) + +## Stack Navigator + +We will start with `react-navigation` by creating a basic navigation stack. Stack navigator gives us a possibility of transition between screens in our app and manage navigation's history. In a simple scenario where there is only one stack navigator present in the app, it resembles a navigation state in a browser. +Screens are pushed and popped from the stack while the user navigates to a new screen or go back to the previous one. + +Let's create two screens. A main screen named `Home` and details screen named `Details`. + +```js +import 'react-native-gesture-handler'; +import React from 'react'; +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +const Stack = createNativeStackNavigator(); + +export default function App() { + return ( + + + + + + + ); +} +``` + +At the moment our navigation stack contains two screens and will render `HomeScreen` or `DetailsScreen` components according to the current navigation state. We have not implemented those components yet, so let's do this now: + +```js +import React from 'react'; +import { View, Text, Button, StyleSheet } from 'react-native'; + +function HomeScreen() { + return ( + + Home Screen + + ); +} + +function DetailsScreen() { + return ( + + Details Screen + + ); +} + +const style = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, +}); +``` + +Once we have finished implementing the components, we can run the app and check how Stack looks like. + + + +To navigate from `HomeScreen` to `DetailsScreen` we can use the navigation object provided by `Stack.Screen` component. Every component rendered by `Stack.Screen` has an access to the navigation object via props. Let's modify our `HomeScreen` component: + +```js +import { Button } from 'react-native-paper'; + +function HomeScreen({ navigation }) { + return ( + + Home Screen + + + ); +} + +const style = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, +}); +``` + +Our result: + + + +As you can see, we can already navigate between two screens. In the next steps, we will show you how to use Paper's `AppBar` instead of the default header. + +### Adding `AppBar` + +We can customize Stack's header by passing custom component: + +```js +export default function App() { + return ( + + , + }} + > + + + + + ); +} +``` + +Now we will implement `CustomNavigationBar` using `AppBar` component: + +```js +import { Appbar } from 'react-native-paper'; + +export default function CustomNavigationBar() { + return ( + + + + ); +} +``` + +Current implementation of the `CustomNavigationBar` is simple - we just render a title inside of it, however it's hardcoded "My awesome app" text, so let's adjust it. +To automatically get the correct `title` for the screen, we're going to take the advantage of `getHeaderTitle` helper available in `@react-navigation/elements` in the following way: + +```js +import { Appbar } from 'react-native-paper'; +import { getHeaderTitle } from '@react-navigation/elements'; + +export default function CustomNavigationBar({ route, options }) { + const title = getHeaderTitle(options, route.name); + + return ( + + + + ); +} +``` + +You may notice there is no way to go back to the previous screen, because the back button is not visible in the header. Let's add it now and let's make sure it's visible on all Stack's screens except `Home` screen. + +In order to achieve it, we firstly check, if the navigation bar receives a `back` prop. If it has, it means there is another screen on the stack beneath the current screen and we should render the back arrow button in such a case. + +```js +import { Appbar } from 'react-native-paper'; +import { getHeaderTitle } from '@react-navigation/elements'; + +export default function CustomNavigationBar({ + navigation, + route, + options, + back, +}) { + const title = getHeaderTitle(options, route.name); + + return ( + + {back ? : null} + + + ); +} +``` + + + +### Adding more items to `Appbar` + +Another interesting pattern that can be implemented with `react-native-paper` and `react-navigation` is a "menu" button. Thanks to the `Menu` component we can add a nice looking pop-up to our `Appbar`. To implement this feature we need to make a couple of changes in `CustomNavigationBar`: + +- Render a `Menu` component +- Pass `Appbar.Action` to the anchor prop +- Add a state to control `Menu` visibility + +:::note +To have properly working `Menu` component, remember to wrap your root component with the `PaperProvider`: + +```js +import { PaperProvider } from 'react-native-paper'; + +// ... + + + + , + }} + > + + + + +; +``` + +::: + +We also want the menu to appear only on `HomeScreen`, which means we will render it conditionally based on the `back` prop. + +```js +import React from 'react'; +import { Appbar, Menu } from 'react-native-paper'; +import { getHeaderTitle } from '@react-navigation/elements'; + +export default function CustomNavigationBar({ + navigation, + route, + options, + back, +}) { + const [visible, setVisible] = React.useState(false); + const openMenu = () => setVisible(true); + const closeMenu = () => setVisible(false); + + const title = getHeaderTitle(options, route.name); + + return ( + + {back ? : null} + + {!back ? ( + } + > + { + console.log('Option 1 was pressed'); + }} + title="Option 1" + /> + { + console.log('Option 2 was pressed'); + }} + title="Option 2" + /> + { + console.log('Option 3 was pressed'); + }} + title="Option 3" + disabled + /> + + ) : null} + + ); +} +``` + +Final result: + + + +That's all we need! We have app bar that contains everything we need to navigate through screens and access an additional menu on the main screen. As you can see, with Material design `Appbar` provided by `react-native-paper` used together with `react-navigation` we can easily create an app that looks and works great. + +Grab the Expo [snack](https://snack.expo.dev/@react-native-paper/integrate-appbar-with-react-navigation) if you want to check the whole code! diff --git a/docs/6.x/docs/guides/recommended-libraries.md b/docs/6.x/docs/guides/recommended-libraries.md new file mode 100644 index 0000000000..568effba4e --- /dev/null +++ b/docs/6.x/docs/guides/recommended-libraries.md @@ -0,0 +1,37 @@ +--- +title: Recommended Libraries +--- + +# Recommended Libraries + +Our mission is to provide a full suite of well-integrated components built with Material Design in mind. However, many components already have better well maintained implementations. + +Here are some of the libraries we recommend: + +## Tabs + +[react-native-community/react-native-tab-view](https://github.com/react-native-community/react-native-tab-view) +Material Design themed [swipeable tabs](https://material.io/design/components/tabs.html), maintained by [@satya164](https://twitter.com/satya164) and [@mosdnk](https://twitter.com/mosdnk). + +[react-native-paper-tabs](https://github.com/web-ridge/react-native-paper-tabs) +Material Design themed [swipeable tabs](https://material.io/design/components/tabs.html) for React Native Paper, maintained by [@RichardLindhout](https://twitter.com/RichardLindhout) + +## Bottom sheet + +[osdnk/reanimated-bottom-sheet](https://github.com/osdnk/react-native-reanimated-bottom-sheet) +An implementation of the [bottom sheet behaviour](https://material.io/design/components/sheets-bottom.html), maintained by [@mosdnk](https://twitter.com/mosdnk). + +[gorhom/react-native-bottom-sheet](https://github.com/gorhom/react-native-bottom-sheet) +An implementation of the [bottom sheet behaviour](https://material.io/design/components/sheets-bottom.html), maintained by [@Gorhom](https://twitter.com/Gorhom). + +## Date Picker + +[web-ridge/react-native-paper-dates](https://github.com/web-ridge/react-native-paper-dates) +Material Design themed [date picker](https://material.io/components/date-pickers), maintained by [@RichardLindhout](https://twitter.com/RichardLindhout) + +[react-native-community/react-native-datetimepicker](https://github.com/react-native-community/react-native-datetimepicker) + +## Time Picker + +[web-ridge/react-native-paper-dates](https://github.com/web-ridge/react-native-paper-dates) +Material Design themed [time picker](https://material.io/components/time-pickers), maintained by [@RichardLindhout](https://twitter.com/RichardLindhout) diff --git a/docs/6.x/docs/guides/ripple-effect.md b/docs/6.x/docs/guides/ripple-effect.md new file mode 100644 index 0000000000..e6f6e503ef --- /dev/null +++ b/docs/6.x/docs/guides/ripple-effect.md @@ -0,0 +1,42 @@ +--- +title: Ripple effect +--- + +The ripple effect is a visual feedback that occurs when a user interacts with a pressable UI element, such as a button. This response takes the form of a circular ripple expanding from the point of contact, much like a drop falling into water and creating ripples. + +The ripple effect is an essential aspect of Material Design, and Paper's pressable components have it enabled by default. Nonetheless, it can be tailored to suit specific needs. + +:::note +The ripple effect on the iOS platform is replaced by a highlight effect. +::: + +## Customize ripple effect color in component + +The `rippleColor` prop is available for every pressable component which allows you to set the color of the ripple effect. For the instance, to make the `Button` component's ripple effect transparent, simply pass the desired color value to the prop: + +```typescript + +``` + +## Disable ripple effect in all components + +To disable the ripple effect in **all** of Paper's components set `rippleEffectEnabled: false` on the `settings` prop of `PaperProvider`. + +```typescript +import { Provider as PaperProvider } from 'react-native-paper'; +// ... + + + // ... + +``` diff --git a/docs/6.x/docs/guides/rtl.md b/docs/6.x/docs/guides/rtl.md new file mode 100644 index 0000000000..7a3bb1bb51 --- /dev/null +++ b/docs/6.x/docs/guides/rtl.md @@ -0,0 +1,82 @@ +--- +title: RTL Support +--- + +# RTL Support + +React Native Paper supports right-to-left (RTL) layouts for languages such as Arabic and Hebrew. + +## How it works + +By default, React Native Paper reads the writing direction from `I18nManager.getConstants().isRTL` on native platforms. So it will use your existing RTL setup on initial render. + +See [I18nManager](http://reactnative.dev/docs/i18nmanager) docs and [Enabling RTL support in Expo](https://docs.expo.dev/guides/localization/#enabling-rtl-support) to configure your app properly. + +On the Web, the RTL value is not set globally, unlike native platforms. `I18nManager.getConstants().isRTL` is a no-op on [React Native Web](https://necolas.github.io/react-native-web/). To enable RTL globally, you can specify `dir` attribute on the `html` element: + +```html + + + +``` + +Then, let `react-native-paper` know about it by using the `direction` prop on `PaperProvider` or the `LocaleProvider` component to match the writing direction in your app. + +:::note +The `direction` prop informs React Native Paper about the text direction in the app i.e. it doesn't change the text direction by itself. If you intend to support RTL languages, it's important to set this prop to the correct value that's configured in the app. If it doesn't match the actual text direction, the layout might be incorrect. +::: + +## Setting direction for the whole app + +Pass the `direction` prop to `PaperProvider`. Defaults to `'rtl'` when `I18nManager.getConstants().isRTL` returns `true`, otherwise `'ltr'`. + +Supported values: + +- `'ltr'`: Left-to-right text direction for languages like English, French etc. +- `'rtl'`: Right-to-left text direction for languages like Arabic, Hebrew etc. + +```js +import * as React from 'react'; +import { PaperProvider } from 'react-native-paper'; +import App from './App'; + +export default function Main() { + return ( + + + + ); +} +``` + +## Overriding direction for a subtree + +Use `LocaleProvider` to override the direction for a specific part of the tree without affecting the rest of the app: + +```js +import * as React from 'react'; +import { LocaleProvider } from 'react-native-paper'; + +export default function ArabicSection() { + return ( + + {/* Components here will use RTL layout */} + + ); +} +``` + +## Reading the current direction + +The direction is available in your own components via the `useLocale` hook: + +```js +import * as React from 'react'; +import { useLocale } from 'react-native-paper'; + +function MyComponent() { + const { direction } = useLocale(); + + // Use the direction +} +``` diff --git a/docs/6.x/docs/guides/theming-with-react-navigation.md b/docs/6.x/docs/guides/theming-with-react-navigation.md new file mode 100644 index 0000000000..ada86a611c --- /dev/null +++ b/docs/6.x/docs/guides/theming-with-react-navigation.md @@ -0,0 +1,286 @@ +--- +title: Theming with React Navigation +--- + +# Theming with React Navigation + +In this guide, we will look into how to apply theming for an application using React Native Paper and React Navigation at the same time. + +Offering different theme options, especially dark/light ones, has become increasingly a standard requirement of the modern mobile application. Fortunately, both React Navigation and React Native Paper support configurable theming out-of-the-box. +But how to make them work together? + +## Themes adaptation + +### Material Design 3 + +React Native Paper follows the Material Design 3 (Material You) color system, which differs from React Navigation’s default theme shape. + +However, to simplify adapting React Navigation theme colors, to use the ones from React Native Paper, it's worth using a utility called `adaptNavigationTheme` – it accepts navigation-compliant themes in both modes and returns their equivalents adjusted to Material Design 3. + +```ts +import { + DarkTheme as NavigationDarkTheme, + DefaultTheme as NavigationDefaultTheme, +} from '@react-navigation/native'; +import { adaptNavigationTheme } from 'react-native-paper'; + +const { LightTheme, DarkTheme } = adaptNavigationTheme({ + reactNavigationLight: NavigationDefaultTheme, + reactNavigationDark: NavigationDarkTheme, +}); +``` + +Library exports also Material Design 3 themes in both modes: + +```js +import { MD3LightTheme, MD3DarkTheme } from 'react-native-paper'; +``` + +## Combining theme objects + +Both libraries require a wrapper to be used at the entry point of the application. +React Navigation exposes `NavigationContainer`, which ensures that navigation works correctly and accepts `theme` as an optional property. Read more about setting up navigation [here](https://reactnavigation.org/docs/getting-started/). +For React Native Paper theme to work, we need to use `PaperProvider` also at the application's entry point. + +```js +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { TouchableOpacity } from 'react-native'; +import { Card, Text, List, PaperProvider } from 'react-native-paper'; + +const Stack = createNativeStackNavigator(); + +const HomeScreen = ({ navigation }) => ( + + navigation?.push('Details', { + title, + content, + }) + } + > + + + {title} + {content} + + + +); + +const DetailsScreen = (props) => { + const { title, content } = props?.route?.params; + return ( + + {title} + + + ); +}; + +export default function App() { + return ( + + + + + + + + + ); +} +``` + +Our goal here is to combine those two themes, so that we could control the theme for the entire application from a single place. + +To make things easier we can use [deepmerge](https://www.npmjs.com/package/deepmerge) package. We can install it with: + +```bash npm2yarn +npm install deepmerge +``` + +### Material Design 3 + +```js +import { + NavigationContainer, + DarkTheme as NavigationDarkTheme, + DefaultTheme as NavigationDefaultTheme, +} from '@react-navigation/native'; +import { + MD3DarkTheme, + MD3LightTheme, + adaptNavigationTheme, +} from 'react-native-paper'; +import merge from 'deepmerge'; + +const { LightTheme, DarkTheme } = adaptNavigationTheme({ + reactNavigationLight: NavigationDefaultTheme, + reactNavigationDark: NavigationDarkTheme, +}); + +const CombinedDefaultTheme = merge(MD3LightTheme, LightTheme); +const CombinedDarkTheme = merge(MD3DarkTheme, DarkTheme); +``` + +Alternatively, we could merge those themes using vanilla JavaScript: + +### Material Design 3 + +```js +const { LightTheme, DarkTheme } = adaptNavigationTheme({ + reactNavigationLight: NavigationDefaultTheme, + reactNavigationDark: NavigationDarkTheme, +}); + +const CombinedDefaultTheme = { + ...MD3LightTheme, + ...LightTheme, + colors: { + ...MD3LightTheme.colors, + ...LightTheme.colors, + }, +}; +const CombinedDarkTheme = { + ...MD3DarkTheme, + ...DarkTheme, + colors: { + ...MD3DarkTheme.colors, + ...DarkTheme.colors, + }, +}; +``` + +## Passing theme with Providers + +After combining the themes, we will be able to control theming in both libraries from a single source, which will come in handy later. + +Next, we need to pass merged themes into the Providers. For this part, we use the dark one - `CombinedDarkTheme`. + +```js +const Stack = createStackNavigator(); + +export default function App() { + return ( + + + + + + + + + ); +} +``` + +## Customizing theme + +We don't need to limit ourselves to the themes offered by the libraries' default. Both packages allow for custom themes to be applied. +You can learn all about it in their documentation: + +- [Theming in React Navigation](https://reactnavigation.org/docs/themes/) +- [Theming in React Native Paper](https://callstack.github.io/react-native-paper/docs/guides/theming) + +## React Context for theme customization + +Now, we wouldn't want to stay on dark theme forever, which is why we need to gain the ability to control theme dynamically. A bit of state management is needed for this purpose. + +React Context proves itself very useful in handling cross-cutting concerns like global theme handling, so we will use just that. + +## Creating Context + +First, we define our Context. + +```js +import React from 'react'; + +export const PreferencesContext = React.createContext({ + toggleTheme: () => {}, + isThemeDark: false, +}); +``` + +## Using Context + +Context Provider should be imported also at the entry point, as we want it to wrap the whole app, for the theme values to be accessible at every component that we have. + +```js +import React from 'react'; +import { PreferencesContext } from './PreferencesContext'; + +const Stack = createStackNavigator(); + +export default function App() { + const [isThemeDark, setIsThemeDark] = React.useState(false); + + let theme = isThemeDark ? CombinedDarkTheme : CombinedDefaultTheme; + + const toggleTheme = React.useCallback(() => { + return setIsThemeDark(!isThemeDark); + }, [isThemeDark]); + + const preferences = React.useMemo( + () => ({ + toggleTheme, + isThemeDark, + }), + [toggleTheme, isThemeDark] + ); + + return ( + // Context is wired into the local state of our main component, so that its values could be propagated throughout the entire application + + + + + + + + + + + ); +} +``` + +Now that the Context is available at every component, all we need to do is import it. Next, provide the user with some UI element to control changing the theme. We will use `Paper`'s [Switch](https://callstack.github.io/react-native-paper/docs/components/Switch) for this purpose. + +```js +import React from 'react'; +import { useTheme, Appbar, TouchableRipple, Switch } from 'react-native-paper'; +import { PreferencesContext } from './PreferencesContext'; + +const Header = ({ scene }) => { + const theme = useTheme(); + const { toggleTheme, isThemeDark } = React.useContext(PreferencesContext); + + return ( + + + + + ); +}; +``` + +And now you can switch between light and dark theme! + +![paperGuide1](../../../public/screenshots/themingWithReactNavigationDarkLightSwitch.gif) + +Thanks to the linking of themes that we did earlier, switching themes can be controlled with only one piece of state. + +React Native Paper components will automatically use the provided theme thanks to the `PaperProvider` that is wrapped around the entry point of our application, but we can also access theme values manually with `useTheme` hook, +exposed by the library. You can see how it's done in the `Header` component code above. + +If light/dark themes are not enough for your use case, you can learn more about creating Material Design themes [here](https://material.io/design/material-theming/implementing-your-theme.html#color). +On `main` branch of the example app, you will find implemented [Menu](https://callstack.github.io/react-native-paper/docs/components/Menu) component, which allows you to choose a few custom themes. Inspecting code in `utils` and `Header` may give you some idea of how to use your own themes with `Paper`, in addition to dedicated [docs](https://callstack.github.io/react-native-paper/docs/components/Menu). + +Read more about integrating `Paper` with `React Navigation` in a brilliant [article](https://reactnavigation.org/blog/2020/01/29/using-react-navigation-5-with-react-native-paper/) by [@trensik](https://twitter.com/trensik) diff --git a/docs/6.x/docs/guides/theming.mdx b/docs/6.x/docs/guides/theming.mdx new file mode 100644 index 0000000000..b58bf0702d --- /dev/null +++ b/docs/6.x/docs/guides/theming.mdx @@ -0,0 +1,516 @@ +--- +title: Theming +--- + +import DynamicColorTheme from '@docs/components/DynamicColorTheme.tsx'; + +# Theming + +:::note +To observe changes related to switching between light and dark mode in the app, ensure that the "Override force-dark" feature in the "developer options" settings on your Android device is not overridden. +::: + +## Applying a theme to the whole app + +To support custom themes, paper exports a `PaperProvider` component. You need to wrap your root component with the provider to be able to support themes: + +```js +import * as React from 'react'; +import { PaperProvider } from 'react-native-paper'; +import App from './src/App'; + +export default function Main() { + return ( + + + + ); +} +``` + +By default React Native Paper will apply the [Material You theme (MD3)](https://github.com/callstack/react-native-paper/blob/main/src/theme/schemes/LightTheme.tsx) if no `theme` or `version` prop is passed to the `PaperProvider`. + +## Accessing theme properties + +Use the built-in `useTheme()` hook to get access to the theme's variables: + +```js +import * as React from 'react'; +import { useTheme } from 'react-native-paper'; + +export default function PaymentScreen() { + const theme = useTheme(); + + return ; +} +``` + +You can also use the `withTheme()` HOC exported from the library. If you wrap your component with the HOC, you'll receive the theme as a prop: + +```js +import * as React from 'react'; +import { withTheme } from 'react-native-paper'; + +function PaymentScreen({ theme }) { + return ; +} + +export default withTheme(PaymentScreen); +``` + +## Theme properties + +You can change the theme prop dynamically and all the components will automatically update to reflect the new theme. + +A theme usually contains the following properties: + +- `dark` (`boolean`): whether this is a dark theme or light theme. +- `version`: Material You (MD3); kept for compatibility and normalized to `3` by `PaperProvider` +- `roundness` (`number`): roundness of common elements, such as buttons. +- `colors` (`object`): various colors used throughout different elements. + + > The primary key color is used to derive roles for key components across the UI, such as the FAB, prominent buttons, active states, as well as the tint of elevated surfaces. + - `primary` + - `onPrimary` + - `primaryContainer` + - `onPrimaryContainer` + + > The secondary key color is used for less prominent components in the UI such as filter chips, while expanding the opportunity for color expression. + - `secondary` + - `onSecondary` + - `secondaryContainer` + - `onSecondaryContainer` + + > The tertiary key color is used to derive the roles of contrasting accents that can be used to balance primary and secondary colors or bring heightened attention to an element. + + > The tertiary color role is left for teams to use at their discretion and is intended to support broader color expression in products. + - `tertiary` + - `onTertiary` + - `tertiaryContainer` + - `onTertiaryContainer` + + > The neutral key color is used to derive the roles of surface and background, as well as high emphasis text and icons. + - `background` + - `onBackground` + - `surface` + - `onSurface` + + > The neutral variant key color is used to derive medium emphasis text and icons, surface variants, and component outlines. + - `surfaceVariant` + - `onSurfaceVariant` + - `outline` + + > In addition to the accent and neutral key color, the color system includes a semantic color role for error + - `error` + - `onError` + - `errorContainer` + - `onErrorContainer` + + > Surfaces at elevation levels 0-5 are tinted via color overlays based on the primary color, such as app bars or menus. The addition of a grade from 0-5 introduces tonal variation to the surface baseline. + - `elevation` (`object`) + - `level0` - transparent + - `level1` - 5% opacity + - `level2` - 8% opacity + - `level3` - 11% opacity + - `level4` - 12% opacity + - `level5` - 14% opacity + + > Colors for disabled state + - `surfaceDisabled` + - `onSurfaceDisabled` + + > These additional role mappings exist in a scheme and are mapped to components where needed. + - `shadow` + - `inverseOnSurface` + - `inverseSurface` + - `inversePrimary` + - `backdrop` + +- `fonts` (`object`): various fonts styling properties under the text variant key used in component. + - [`variant` e.g. `labelMedium`] (`object`): + - `fontFamily` + - `letterSpacing` + - `fontWeight` + - `lineHeight` + - `fontSize` +- `animation` (`object`) + - `scale` - scale for all animations + +When creating a custom theme, you will need to provide all of these properties. + +If you don't use a custom theme, Paper will automatically turn animations on/off, depending on device settings. + +Otherwise, your custom theme will need to handle it manually, using React Native's [AccessibilityInfo API](https://reactnative.dev/docs/accessibilityinfo). + +## Extending the theme + +Keeping your own properties in the theme is fully supported by our library: + +```js +import * as React from 'react'; +import { + MD3LightTheme as DefaultTheme, + PaperProvider, +} from 'react-native-paper'; +import App from './src/App'; + +const theme = { + ...DefaultTheme, + // Specify custom property + myOwnProperty: true, + // Specify custom property in nested object + colors: { + ...DefaultTheme.colors, + myOwnColor: '#BADA55', + }, +}; + +export default function Main() { + return ( + + + + ); +} +``` + +## Creating dynamic theme colors + +Dynamic Color Themes allows for generating two color schemes lightScheme and darkScheme, based on the provided source color. +Created schemes are following the Material Design 3 color system and covering colors structure from the Paper theme. User may generate these schemes using the following tool: + + + +
    + +Passed source color into the util is translated into tones to automatically provide the range of tones that map to color roles. + +![customColors](../../../public/screenshots/custom-colors.png) + +_Source: [Material You Color System](https://m3.material.io/styles/color/the-color-system/custom-colors)_ + +### Using schemes + +Once we have copied the color schemes from the generated JSON above, we can use by passing it to the colors in `theme` object as below: + +```jsx +import * as React from 'react'; +import { + MD3LightTheme as DefaultTheme, + PaperProvider, +} from 'react-native-paper'; +import App from './src/App'; + +const theme = { + ...DefaultTheme, + colors: yourGeneratedLightOrDarkScheme.colors, // Copy it from the color codes scheme and then use it here +}; + +export default function Main() { + return ( + + + + ); +} +``` + +### Sync dynamic colors with system colors + +React Native Paper provides built-in support for Android dynamic colors through `DynamicLightTheme` and `DynamicDarkTheme`. These themes use React Native's `PlatformColor` API to map all Material Design 3 color roles to Android system colors, so any changes the user makes to their system palette are automatically reflected in the app. + +:::info +Dynamic colors require Android 12 (API 31+). On older Android versions and all other platforms, these themes fall back to the default `LightTheme` / `DarkTheme`. +::: + +```tsx +import { useColorScheme } from 'react-native'; +import { + DynamicDarkTheme, + DynamicLightTheme, + PaperProvider, +} from 'react-native-paper'; +import App from './src/App'; + +export default function Main() { + const isDarkMode = useColorScheme() === 'dark'; + + return ( + + + + ); +} +``` + +## Adapting React Navigation theme + +The `adaptNavigationTheme` function takes an existing React Navigation theme and returns a React Navigation theme using the colors from Material Design 3. This theme can be passed to `NavigationContainer` so that React Navigation's UI elements have the same color scheme as Paper. + +```ts +adaptNavigationTheme(themes); +``` + +:::info +For users of `react-navigation` version `7.0.0` and above, `adaptNavigationTheme` overrides the **fonts** from the navigation theme as follows: + +```ts +fonts: { + regular: { + fontFamily: materialTheme.fonts.bodyMedium.fontFamily, + fontWeight: materialTheme.fonts.bodyMedium.fontWeight, + letterSpacing: materialTheme.fonts.bodyMedium.letterSpacing, + }, + medium: { + fontFamily: materialTheme.fonts.titleMedium.fontFamily, + fontWeight: materialTheme.fonts.titleMedium.fontWeight, + letterSpacing: materialTheme.fonts.titleMedium.letterSpacing, + }, + bold: { + fontFamily: materialTheme.fonts.headlineSmall.fontFamily, + fontWeight: materialTheme.fonts.headlineSmall.fontWeight, + letterSpacing: materialTheme.fonts.headlineSmall.letterSpacing, + }, + heavy: { + fontFamily: materialTheme.fonts.headlineLarge.fontFamily, + fontWeight: materialTheme.fonts.headlineLarge.fontWeight, + letterSpacing: materialTheme.fonts.headlineLarge.letterSpacing, + }, +} +``` + +::: + +Parameters: + +| NAME | TYPE | +| ------ | ------ | +| themes | object | + +Valid `themes` keys are: + +- `reactNavigationLight` () - React Navigation compliant light theme. +- `reactNavigationDark` () - React Navigation compliant dark theme. +- `materialLight` () - React Native Paper compliant light theme. +- `materialDark` () - React Native Paper compliant dark theme. + +```ts +// App.tsx +import { NavigationContainer, DefaultTheme } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { + PaperProvider, + MD3LightTheme, + adaptNavigationTheme, +} from 'react-native-paper'; + +const Stack = createNativeStackNavigator(); + +const { LightTheme } = adaptNavigationTheme({ + reactNavigationLight: DefaultTheme, +}); + +export default function App() { + return ( + + + + + + + + + ); +} +``` + +## TypeScript + +By default, TypeScript works well whenever you change the value of the built-in theme's properties. It gets more complicated when you want to extend the theme's properties or change their types. In order to fully support TypeScript, you will need to follow the guide that fits your use-case most accurately: + +There are two supported ways of overriding the theme: + +1. Simple built-in theme overrides - when you only customize the values + and the whole theme schema remains the same +2. Advanced theme overrides - when you add new properties or + change the built-in schema shape + +:::caution +TypeScript support for `withTheme` is currently limited to Material You (MD3) theme only. + + + We are planning to provide a better support of handling custom theme overrides + in future releases. + +::: + +### Simple built-in theme overrides + +You can provide a `theme` prop with a theme object with the same properties as the default theme: + +```js +import * as React from 'react'; +import { MD3LightTheme, PaperProvider } from 'react-native-paper'; +import App from './src/App'; + +const theme = { + ...MD3LightTheme, // or MD3DarkTheme + roundness: 2, + colors: { + ...MD3LightTheme.colors, + primary: '#3498db', + secondary: '#f1c40f', + tertiary: '#a1b2c3', + }, +}; + +export default function Main() { + return ( + + + + ); +} +``` + +### Advanced theme overrides + +If you need to modify the built-in theme schema by adding a new property or changing its type, you need to follow these steps: + +1. Pass your theme overrides to the PaperProvider component + +```ts +import * as React from 'react'; +import { MD3LightTheme, PaperProvider } from 'react-native-paper'; +import App from './src/App'; + +const theme = { + ...MD3LightTheme, + + // Specify a custom property + custom: 'property', + + // Specify a custom property in nested object + colors: { + ...MD3LightTheme.colors, + brandPrimary: '#fefefe', + brandSecondary: 'red', + }, +}; + +export default function Main() { + return ( + + + + ); +} +``` + +2. Create a typed `useAppTheme()` hook in your project + +```ts +import * as React from 'react'; +import { MD3LightTheme, PaperProvider, useTheme } from 'react-native-paper'; +import App from './src/App'; + +const theme = { + ...MD3LightTheme, + + // Specify a custom property + custom: 'property', + + // Specify a custom property in nested object + colors: { + ...MD3LightTheme.colors, + brandPrimary: '#fefefe', + brandSecondary: 'red', + }, +}; + +export type AppTheme = typeof theme; + +export const useAppTheme = () => useTheme(); + +export default function Main() { + return ( + + + + ); +} +``` + +3. Start using the `useAppTheme()` hook across your components in the whole app + +```ts +import * as React from 'react'; +import { useAppTheme } from './App'; + +export default function HomeScreen() { + const { + colors: { brandPrimary }, + } = useAppTheme(); + + return ...; +} +``` + +## Material Design 3 + +React Native Paper ships with Material Design 3 (Material You) only. Customize the default experience by extending `MD3LightTheme` or `MD3DarkTheme` (see [Extending the theme](#extending-the-theme) and [Advanced theme overrides](#advanced-theme-overrides)). + +## Applying a theme to a paper component + +If you want to change the theme for a certain component from the library, you can directly pass the `theme` prop to the component. The theme passed as the prop is merged with the theme from the `PaperProvider`: + +```js +import * as React from 'react'; +import { Button } from 'react-native-paper'; + +export default function ButtonExample() { + return ( + + ); +} +``` + +## Customizing all instances of a component + +Sometimes you want to style a component in a different way everywhere, but don't want to change the properties in the theme, so that other components are not affected. For example, say you want to change the font for all your buttons, but don't want to change `theme.fonts.labelLarge` because it affects other components. + +We don't have an API to do this, because you can already do it with components: + +```js +import * as React from 'react'; +import { Button } from 'react-native-paper'; + +export default function FancyButton(props) { + return ( + - {}} /> + {}} /> {}} /> {}} /> @@ -109,13 +129,13 @@ const BannerExample = () => { setText(text)} + onChangeText={(value) => setText(value)} /> setText(text)} + onChangeText={(value) => setText(value)} /> @@ -142,34 +162,16 @@ const BannerExample = () => { }; const Shimmer = () => { - /* Docusaurus won't call StyleSheet.create() server-side */ - /* eslint-disable react-native/no-inline-styles, react-native/no-color-literals */ - - return ( - - ); + return ; }; const ThemedBannerExample = () => { const isDarkTheme = useColorMode().colorMode === 'dark'; + return ( - + - + ); }; diff --git a/docs/src/components/CodeBlock.tsx b/docs/src/components/CodeBlock.tsx new file mode 100644 index 0000000000..5e58ba8768 --- /dev/null +++ b/docs/src/components/CodeBlock.tsx @@ -0,0 +1,16 @@ +import type { ReactNode } from 'react'; + +type CodeBlockProps = { + children: ReactNode; + language?: string; +}; + +export default function CodeBlock({ children, language }: CodeBlockProps) { + return ( +
    +      
    +        {children}
    +      
    +    
    + ); +} diff --git a/docs/src/components/DynamicColorTheme.tsx b/docs/src/components/DynamicColorTheme.tsx index 24e1191b21..1203992e7d 100644 --- a/docs/src/components/DynamicColorTheme.tsx +++ b/docs/src/components/DynamicColorTheme.tsx @@ -1,8 +1,8 @@ -import React, { useState, ReactNode } from 'react'; +import React, { useState } from 'react'; +import type { ReactNode } from 'react'; import Color from 'color'; //@ts-ignore -// eslint-disable-next-line import/no-unresolved import { BlockPicker } from 'react-color'; import Switch from './Switch'; @@ -160,7 +160,7 @@ const CodePreview = ({ }; const onCopy = () => { - navigator.clipboard.writeText(getColorScheme()); + void navigator.clipboard.writeText(getColorScheme()); setCopied(true); setTimeout(() => setCopied(false), 1000); diff --git a/docs/src/components/ExtendedExample.tsx b/docs/src/components/ExtendedExample.tsx index c3eac15fdf..2773d528e3 100644 --- a/docs/src/components/ExtendedExample.tsx +++ b/docs/src/components/ExtendedExample.tsx @@ -1,11 +1,6 @@ -import React from 'react'; - -//@ts-ignore -import CodeBlock from '@theme/CodeBlock'; -//@ts-ignore -import TabItem from '@theme/TabItem'; -//@ts-ignore -import Tabs from '@theme/Tabs'; +import CodeBlock from './CodeBlock'; +import TabItem from './TabItem'; +import Tabs from './Tabs'; interface ExtendedExampleProps { extendedExamplesData: { diff --git a/docs/src/components/ExtendsLink.tsx b/docs/src/components/ExtendsLink.tsx index 9b39db2c8c..03b9462a48 100644 --- a/docs/src/components/ExtendsLink.tsx +++ b/docs/src/components/ExtendsLink.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - export default function ExtendsLink({ name, link, diff --git a/docs/src/components/GetStartedButtons.tsx b/docs/src/components/GetStartedButtons.tsx index 4debe5b5ac..3b5378b194 100644 --- a/docs/src/components/GetStartedButtons.tsx +++ b/docs/src/components/GetStartedButtons.tsx @@ -1,110 +1,20 @@ -import * as React from 'react'; -import { StyleSheet, View } from 'react-native'; - -//@ts-ignore -import BrowserOnly from '@docusaurus/BrowserOnly'; -//@ts-ignore -import Link from '@docusaurus/Link'; -//@ts-ignore -import { useColorMode } from '@docusaurus/theme-common'; -import { - Button, - MD3DarkTheme as DarkTheme, - MD3LightTheme as DefaultTheme, - PaperProvider, -} from 'react-native-paper'; - -const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - justifyContent: 'center', - paddingBottom: 16, - }, - button: { - marginRight: 16, - }, -}); - -const noTextDecoration = { - textDecoration: 'none', -}; - -// Needed for ripple effect when pressing `Button`, however navigation is handled by `Link` -const noop = () => {}; - -const GetStartedButton = () => { +export default function GetStartedButtons() { return ( - - - - - - - ); -}; - -const Shimmer = () => { - /* Docusaurus won't call StyleSheet.create() server-side */ - /* eslint-disable react-native/no-inline-styles, react-native/no-color-literals */ - - return ( - - - - - ); -}; - -const ThemedGetStarted = () => { - const isDarkTheme = useColorMode().colorMode === 'dark'; - return ( - - - - ); -}; - -export default function ClientSideGetStarted() { - return ( - }> - {() => } - + + ); } diff --git a/docs/src/components/GithubIcon.tsx b/docs/src/components/GithubIcon.tsx index c94cc381af..d768e3cdc9 100644 --- a/docs/src/components/GithubIcon.tsx +++ b/docs/src/components/GithubIcon.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - type Props = { color: string; }; diff --git a/docs/src/components/GooglePlayIcon.tsx b/docs/src/components/GooglePlayIcon.tsx index a0a88e36c5..e086eefd8c 100644 --- a/docs/src/components/GooglePlayIcon.tsx +++ b/docs/src/components/GooglePlayIcon.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - type Props = { color: string; }; diff --git a/docs/src/components/IconsList.tsx b/docs/src/components/IconsList.tsx index 73f26c91d7..76fc8bef1b 100644 --- a/docs/src/components/IconsList.tsx +++ b/docs/src/components/IconsList.tsx @@ -1,7 +1,7 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; const latestIcons: { [key in string]: number } = { - ...require('../../node_modules/@react-native-vector-icons/material-design-icons/glyphmaps/MaterialDesignIcons.json'), + ...require('@react-native-vector-icons/material-design-icons/glyphmaps/MaterialDesignIcons.json'), }; const oldIcons: { [key in string]: number } = { diff --git a/docs/src/components/Link.tsx b/docs/src/components/Link.tsx new file mode 100644 index 0000000000..92f82456d2 --- /dev/null +++ b/docs/src/components/Link.tsx @@ -0,0 +1,32 @@ +import type { AnchorHTMLAttributes, ReactNode } from 'react'; + +import { useSite } from '@rspress/core/dist/runtime/index.js'; + +type LinkProps = AnchorHTMLAttributes & { + children?: ReactNode; + to?: string; +}; + +const withBase = (href: string, base: string) => { + if (/^(https?:)?\/\//.test(href) || href.startsWith('#')) { + return href; + } + + if (!href.startsWith('/')) { + return href; + } + + const normalizedBase = base.endsWith('/') ? base.slice(0, -1) : base; + return `${normalizedBase}${href}`; +}; + +export default function Link({ children, href, to, ...rest }: LinkProps) { + const { site } = useSite(); + const targetHref = to ?? href ?? '#'; + + return ( + + {children} + + ); +} diff --git a/docs/src/components/Markdown.tsx b/docs/src/components/Markdown.tsx index 27d429900f..2e0635ade0 100644 --- a/docs/src/components/Markdown.tsx +++ b/docs/src/components/Markdown.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - const { marked } = require('marked'); interface MarkdownProps { diff --git a/docs/src/components/PaperHomeShowcase.tsx b/docs/src/components/PaperHomeShowcase.tsx new file mode 100644 index 0000000000..2b9db2a775 --- /dev/null +++ b/docs/src/components/PaperHomeShowcase.tsx @@ -0,0 +1,82 @@ +import BannerExample from './BannerExample'; + +const lightImages = [ + 'button.png', + 'input.png', + 'card.png', + 'searchbar.png', + 'appbar.png', + 'snackbar.png', + 'chip.png', + 'list.png', + 'list-accordion.png', + 'typography.png', + 'bottom-navigation.png', + 'fab.png', +]; + +const darkImages = [ + 'button-dark.png', + 'input-dark.png', + 'card-dark.png', + 'searchbar-dark.png', + 'appbar-dark.png', + 'snackbar-dark.png', + 'chip-dark.png', + 'list-dark.png', + 'list-accordion-dark.png', + 'typography-dark.png', + 'bottom-navigation-dark.png', + 'fab-dark.png', +]; + +const renderGallery = (images: string[]) => + images.map((imageName) => ( + + )); + +export default function PaperHomeShowcase() { + return ( +
    +
    +
    +

    In practice

    +

    + Material components, typography, and controls +

    +
    +

    + Browse the component surface the way it appears in real product UI, + not as generic documentation chrome. +

    +
    + +
    + Or check the demo app on{' '} + + iOS + {' '} + or{' '} + + Android + + . +
    + + + +
    + {renderGallery(lightImages)} +
    + +
    + {renderGallery(darkImages)} +
    +
    + ); +} diff --git a/docs/src/components/PaperVersionSelector.tsx b/docs/src/components/PaperVersionSelector.tsx new file mode 100644 index 0000000000..1c2de87152 --- /dev/null +++ b/docs/src/components/PaperVersionSelector.tsx @@ -0,0 +1,102 @@ +import { useEffect, useMemo, useRef } from 'react'; + +import { + usePage, + useVersion, + withBase, +} from '@rspress/core/dist/runtime/index.js'; + +import { getNextRoute, getStableRoute } from '../utils/versionRoutes'; + +const legacyVersions = [ + { label: '4.x', href: '/4.0/' }, + { label: '3.x', href: '/3.0/' }, + { label: '2.x', href: '/2.0/' }, + { label: '1.x', href: '/1.0/' }, +]; + +type VersionLink = { + active: boolean; + external?: boolean; + href: string; + label: string; +}; + +export default function PaperVersionSelector() { + const { page } = usePage(); + const version = useVersion() || '5.x'; + const detailsRef = useRef(null); + + const links = useMemo(() => { + const routePath = page.routePath ?? '/'; + const stableRoute = getStableRoute(routePath); + const nextRoute = getNextRoute(routePath); + + const primaryLink = + version === '6.x' + ? { active: true, href: withBase(nextRoute), label: '6.x' } + : { active: true, href: withBase(stableRoute), label: '5.x' }; + const secondaryLink = + version === '6.x' + ? { active: false, href: withBase(stableRoute), label: '5.x' } + : { active: false, href: withBase(nextRoute), label: '6.x' }; + + return [ + primaryLink, + secondaryLink, + ...legacyVersions.map((item) => ({ + active: false, + external: true, + href: withBase(item.href), + label: item.label, + })), + ]; + }, [page.routePath, version]); + + useEffect(() => { + detailsRef.current?.removeAttribute('open'); + }, [page.routePath]); + + return ( +
    + + {version} + + + +
    + ); +} diff --git a/docs/src/components/PrereleaseNotice.tsx b/docs/src/components/PrereleaseNotice.tsx new file mode 100644 index 0000000000..5e32804c8e --- /dev/null +++ b/docs/src/components/PrereleaseNotice.tsx @@ -0,0 +1,35 @@ +import Link from './Link'; + +type PrereleaseNoticeProps = { + stableHref: string; + stableLabel: string; + version: string; +}; + +export default function PrereleaseNotice({ + stableHref, + stableLabel, + version, +}: PrereleaseNoticeProps) { + return ( + + ); +} diff --git a/docs/src/components/PropTable.tsx b/docs/src/components/PropTable.tsx index 35f5069433..2437f58bce 100644 --- a/docs/src/components/PropTable.tsx +++ b/docs/src/components/PropTable.tsx @@ -1,103 +1,150 @@ -import React from 'react'; - -// @ts-ignore -// eslint-disable-next-line import/no-unresolved -import useDoc from '@site/component-docs-plugin/useDocs'; - import Markdown from './Markdown'; -const typeDefinitions = { +const typeDefinitions: Record = { IconSource: - 'https://github.com/callstack/react-native-paper/blob/main/src/components/Icon.tsx#L16', + 'https://github.com/callstack/react-native-paper/blob/main/components/Icon.tsx#L16', ThemeProp: 'https://callstack.github.io/react-native-paper/docs/guides/theming#theme-properties', + '(props: TextInputAccessoryProps) => React.ReactNode': + 'https://github.com/callstack/react-native-paper/blob/main/components/TextInput/TextInputIcon.tsx#L11', + '(props: TextInputRenderProps) => React.ReactNode': + 'https://github.com/callstack/react-native-paper/blob/main/components/TextInput/TextInput.tsx#L168', + 'React.Ref': + 'https://github.com/callstack/react-native-paper/blob/main/components/TextInput/TextInput.tsx#L172', AccessibilityState: 'https://reactnative.dev/docs/accessibility#accessibilitystate', 'StyleProp': 'https://reactnative.dev/docs/view-style-props', 'StyleProp': 'https://reactnative.dev/docs/text-style-props', + TextProps: 'https://reactnative.dev/docs/text#props', + AccessibilityProps: + 'https://reactnative.dev/docs/accessibility#accessibilityprops', }; const renderBadge = (annotation: string) => { const [annotType, ...annotLabel] = annotation.split(' '); - // eslint-disable-next-line prettier/prettier - return `${annotLabel.join(' ')}`; + return `${annotLabel.join(' ')}`; +}; + +type LegacyProp = { + defaultValue?: { + value: string; + } | null; + description: string; + required: boolean; + tsType?: { + name: string; + raw?: string; + } | null; }; -export default function PropTable({ - componentLink, - prop, -}: { +type LegacyComponentDocs = Record< + string, + { + data: { + props: Record; + }; + } +>; + +type LegacyPropTableProps = { + componentDocs: LegacyComponentDocs; componentLink: string; prop: string; -}) { - const doc = useDoc(componentLink); +}; - if (!doc?.data?.props) { - return null; - } +type GeneratedPropTableProps = { + defaultValue?: string; + description: string; + type: string; +}; + +type PropTableProps = LegacyPropTableProps | GeneratedPropTableProps; - const props = doc.data.props; +const renderPropDetails = ({ + defaultValue, + description, + type, +}: GeneratedPropTableProps) => { + const tsTypeLink = typeDefinitions[type]; return (
    - {Object.keys(props).map((key) => { - if (key !== prop) { - return null; - } - let descriptionByLines = props[key].description.split('\n'); - - // Slice leading badge - it's handled in `generatePageMDX` - if (descriptionByLines[0].includes('@')) { - descriptionByLines = descriptionByLines.slice(1); - } - descriptionByLines = descriptionByLines.map((line: string) => { - // Replace annotations with styled badges. - if (line.includes('@')) { - const annotIndex = line.indexOf('@'); - // eslint-disable-next-line prettier/prettier - return `${line.substr(0, annotIndex)} ${renderBadge(line.substr(annotIndex))}`; - } else { - return line; - } - }); - - const description = descriptionByLines.join('\n'); - const tsType = props[key].tsType?.raw ?? props[key].tsType?.name; - // @ts-ignore - const tsTypeLink = typeDefinitions[tsType]; - - return ( -
    -

    - Type:{' '} - {tsTypeLink ? ( - - {tsType} - - ) : ( - {tsType} - )} -

    - {props[key].defaultValue && ( -

    - Default value: {props[key].defaultValue.value} -

    - )} - -
    - ); - })} +

    + Type:{' '} + {tsTypeLink ? ( + + {type} + + ) : ( + {type} + )} +

    + {defaultValue && ( +

    + Default value: {defaultValue} +

    + )} +
    ); +}; + +export default function PropTable(tableProps: PropTableProps) { + if ('type' in tableProps) { + return renderPropDetails(tableProps); + } + + const doc = tableProps.componentDocs[tableProps.componentLink]; + + if (!doc?.data?.props) { + return null; + } + + const prop = doc.data.props[tableProps.prop]; + + if (!prop) { + return null; + } + + let descriptionByLines = prop.description.split('\n'); + + // Slice leading badge - it's handled in `generatePageMDX` + if (descriptionByLines[0].includes('@')) { + descriptionByLines = descriptionByLines.slice(1); + } + + const description = descriptionByLines + .map((line) => { + // Replace annotations with styled badges. + if (!line.includes('@')) { + return line; + } + + const annotIndex = line.indexOf('@'); + + return `${line.substr(0, annotIndex)} ${renderBadge( + line.substr(annotIndex) + )}`; + }) + .join('\n'); + + return renderPropDetails({ + defaultValue: prop.defaultValue?.value, + description, + type: prop.tsType?.raw ?? prop.tsType?.name ?? '', + }); } diff --git a/docs/src/components/ScreenshotTabs.tsx b/docs/src/components/ScreenshotTabs.tsx index e352c951d7..ee45872920 100644 --- a/docs/src/components/ScreenshotTabs.tsx +++ b/docs/src/components/ScreenshotTabs.tsx @@ -1,15 +1,13 @@ -import React, { ReactNode } from 'react'; +import type { ReactNode } from 'react'; -//@ts-ignore -import TabItem from '@theme/TabItem'; -//@ts-ignore -import Tabs from '@theme/Tabs'; +import { withBase } from '@rspress/core/runtime'; +import TabItem from './TabItem'; +import Tabs from './Tabs'; import type { DataObject } from '../utils/themeColors'; type ScreenshotTabsProps = { screenshotData: DataObject | string; - baseUrl: string; }; const getClassName = (value: string) => @@ -17,21 +15,18 @@ const getClassName = (value: string) => ? 'gifScreenshot' : `tabScreenshot${value.includes('full-width') ? 'full-width' : ''}`; -const ScreenshotTabs: React.FC = ({ - screenshotData, - baseUrl, -}) => { - const renderScreenhot = (src: string): ReactNode => ( - +const ScreenshotTabs = ({ screenshotData }: ScreenshotTabsProps) => { + const renderScreenshot = (src: string): ReactNode => ( + ); if (typeof screenshotData === 'string') { - return renderScreenhot(screenshotData); + return renderScreenshot(screenshotData); } const screenshots = Object.entries(screenshotData).map(([key, value]) => ( - {renderScreenhot(value as string)} + {typeof value === 'string' ? renderScreenshot(value) : null} )); diff --git a/docs/src/components/Showcase.tsx b/docs/src/components/Showcase.tsx index 53c44f8999..56b942ac70 100644 --- a/docs/src/components/Showcase.tsx +++ b/docs/src/components/Showcase.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - import color from 'color'; import AppStoreIcon from './AppStoreIcon'; @@ -184,8 +182,8 @@ export default function Showcase() { {data.map((item) => { const tintColor = color(item.color).isLight() ? '#000000' : '#FFFFFF'; return ( -
    -
    +
    +
    void; diff --git a/docs/src/components/TabItem.tsx b/docs/src/components/TabItem.tsx new file mode 100644 index 0000000000..b91c24d5a1 --- /dev/null +++ b/docs/src/components/TabItem.tsx @@ -0,0 +1,14 @@ +import type { ReactNode } from 'react'; + +import { Tab } from '@rspress/core/dist/theme/index.js'; + +type TabItemProps = { + children: ReactNode; + default?: boolean; + label?: string; + value?: string; +}; + +export default function TabItem({ children, label, value }: TabItemProps) { + return {children}; +} diff --git a/docs/src/components/Tabs.tsx b/docs/src/components/Tabs.tsx new file mode 100644 index 0000000000..e8b4d3f023 --- /dev/null +++ b/docs/src/components/Tabs.tsx @@ -0,0 +1,11 @@ +import type { ReactNode } from 'react'; + +import { Tabs as RspressTabs } from '@rspress/core/dist/theme/index.js'; + +type TabsProps = { + children: ReactNode; +}; + +export default function Tabs({ children }: TabsProps) { + return {children}; +} diff --git a/docs/src/components/ThemeColorsTable.tsx b/docs/src/components/ThemeColorsTable.tsx index ef7e5cd7f8..d9c5cec724 100644 --- a/docs/src/components/ThemeColorsTable.tsx +++ b/docs/src/components/ThemeColorsTable.tsx @@ -1,17 +1,10 @@ -import React, { ReactNode } from 'react'; +import type { ReactNode } from 'react'; -//@ts-ignore -import Admonition from '@theme/Admonition'; -//@ts-ignore -import TabItem from '@theme/TabItem'; -//@ts-ignore -import Tabs from '@theme/Tabs'; - -import { - DataObject, - getMaxNestedLevel, - getUniqueNestedKeys, -} from '../utils/themeColors'; +import Admonition from './Admonition'; +import TabItem from './TabItem'; +import Tabs from './Tabs'; +import { getMaxNestedLevel, getUniqueNestedKeys } from '../utils/themeColors'; +import type { DataObject } from '../utils/themeColors'; const getTableHeader = (keys: string[]): ReactNode[] => { return keys.map((key) => {key}); @@ -27,6 +20,9 @@ const getTableCell = (keys: string[], modes: DataObject): ReactNode[] => { }); }; +const isDataObject = (value: DataObject[string]): value is DataObject => + typeof value === 'object'; + const FlatTable = ({ themeColorsData, uniqueKeys, @@ -35,10 +31,12 @@ const FlatTable = ({ uniqueKeys: string[]; }): ReactNode => { const rows = Object.keys(themeColorsData).map((mode) => { + const value = themeColorsData[mode]; + return ( {mode} - {getTableCell(uniqueKeys, themeColorsData[mode] as DataObject)} + {isDataObject(value) ? getTableCell(uniqueKeys, value) : null} ); }); @@ -69,30 +67,37 @@ const TabbedTable = ({ themeColorsData: DataObject; uniqueKeys: string[]; }): ReactNode => { - const tabTableContent = Object.keys(themeColorsData).map((key) => { - const modes = themeColorsData[key] as DataObject; - const rows = Object.keys(modes).map((mode) => { + const tabTableContent = Object.entries(themeColorsData).map( + ([key, modes]) => { + if (!isDataObject(modes)) { + return null; + } + + const rows = Object.keys(modes).map((mode) => { + const value = modes[mode]; + + return ( + + {mode} + {isDataObject(value) ? getTableCell(uniqueKeys, value) : null} + + ); + }); return ( - - {mode} - {getTableCell(uniqueKeys, modes[mode] as DataObject)} - + + + + + + {getTableHeader(uniqueKeys)} + + + {rows} +
    mode
    +
    ); - }); - return ( - - - - - - {getTableHeader(uniqueKeys)} - - - {rows} -
    mode
    -
    - ); - }); + } + ); return ( <> diff --git a/docs/src/components/ThemeIcon.tsx b/docs/src/components/ThemeIcon.tsx index 0f643f50db..d787e4b2a3 100644 --- a/docs/src/components/ThemeIcon.tsx +++ b/docs/src/components/ThemeIcon.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - type Props = { type: 'dark' | 'light'; }; diff --git a/docs/src/components/VersionedPrereleaseNotice.tsx b/docs/src/components/VersionedPrereleaseNotice.tsx new file mode 100644 index 0000000000..9c36931191 --- /dev/null +++ b/docs/src/components/VersionedPrereleaseNotice.tsx @@ -0,0 +1,30 @@ +import { usePage, useVersion } from '@rspress/core/dist/runtime/index.js'; + +import PrereleaseNotice from './PrereleaseNotice'; +import { getStableRoute, hasSameStableRoute } from '../utils/versionRoutes'; + +const DOCS_PREFIX = '/6.x/docs/'; + +export default function VersionedPrereleaseNotice() { + const version = useVersion() || '5.x'; + const { page } = usePage(); + const routePath = page.routePath ?? '/'; + + if (version !== '6.x' || !routePath.startsWith(DOCS_PREFIX)) { + return null; + } + + const stableHref = getStableRoute(routePath); + const stableLabel = + hasSameStableRoute(routePath) && page.title + ? `${page.title} (5.x)` + : 'Latest stable docs (5.x)'; + + return ( + + ); +} diff --git a/docs/src/components/theme-common.ts b/docs/src/components/theme-common.ts new file mode 100644 index 0000000000..335ce8eaa1 --- /dev/null +++ b/docs/src/components/theme-common.ts @@ -0,0 +1,9 @@ +import { useDark } from '@rspress/core/dist/runtime/index.js'; + +export const useColorMode = () => { + const isDark = useDark(); + + return { + colorMode: isDark ? 'dark' : 'light', + }; +}; diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 6594e61fb7..2aae0f8b90 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -1,32 +1,11 @@ -/** - * Any CSS included here will be global. The classic template - * bundles Infima by default. Infima is a CSS framework designed to - * work well for content-centric websites. - */ - -/* You can override the default Infima variables here. */ :root { - --ifm-color-primary: #21005d; - --ifm-color-primary-dark: #1e0054; - --ifm-color-primary-darker: #1c004f; - --ifm-color-primary-darkest: #170041; - --ifm-color-primary-light: #240066; - --ifm-color-primary-lighter: #26006b; - --ifm-color-primary-lightest: #2b0079; - --ifm-code-font-size: 95%; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); -} - -/* For readability concerns, you should choose a lighter palette in dark mode. */ + --paper-nav-link-hover: var(--rp-c-bg-soft, rgba(148, 163, 184, 0.16)); + --paper-nav-link-active: var(--rp-c-bg-mute, rgba(148, 163, 184, 0.24)); +} + [data-theme='dark'] { - --ifm-color-primary: #cfbcff; - --ifm-color-primary-dark: #af90ff; - --ifm-color-primary-darker: #9f7aff; - --ifm-color-primary-darkest: #7037ff; - --ifm-color-primary-light: #efe8ff; - --ifm-color-primary-lighter: #fffeff; - --ifm-color-primary-lightest: #ffffff; - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); + --paper-nav-link-hover: var(--rp-c-bg-soft, rgba(255, 255, 255, 0.12)); + --paper-nav-link-active: var(--rp-c-bg-mute, rgba(255, 255, 255, 0.18)); } .nav-link { @@ -43,15 +22,15 @@ border-radius: 50%; background-color: transparent; z-index: -1; - transition: background-color var(--ifm-transition-fast); + transition: background-color 150ms ease; } .nav-link:hover::before, .nav-link:active::before, .nav-link:focus::before { - background-color: var(--ifm-color-emphasis-200); + background-color: var(--paper-nav-link-hover); } .nav-link:active::before { - background-color: var(--ifm-menu-color-background-active); + background-color: var(--paper-nav-link-active); } .nav-link::after { @@ -79,7 +58,6 @@ html[data-theme='dark'] .nav-link::after { } .screenshots img, - .tabScreenshot { max-width: 45%; } @@ -88,7 +66,7 @@ html[data-theme='dark'] .nav-link::after { max-width: 40%; } -.screenshots figure>img { +.screenshots figure > img { max-width: 100%; } @@ -102,7 +80,6 @@ html[data-theme='dark'] .nav-link::after { width: 33%; } - html[data-theme='dark'] .gallery-dark, html[data-theme='light'] .gallery-light { display: block; @@ -128,8 +105,10 @@ html[data-theme='light'] .gallery-dark { height: 124px; align-items: center; text-align: center; +} - @media (max-width: 680px) { +@media (max-width: 680px) { + .icons-list-icon-container { width: 96px; } } @@ -153,10 +132,10 @@ html[data-theme='light'] .gallery-dark { flex-wrap: wrap; justify-content: center; margin: 24px 0; +} - &:last-child { - justify-content: flex-start; - } +.icons-list-results:last-child { + justify-content: flex-start; } .icons-list-searchbar { @@ -171,20 +150,21 @@ html[data-theme='light'] .gallery-dark { border-radius: 3px; transition: background-color 0.3s; outline: 0; +} - &:focus, - &:hover { - background-color: #e7e7e7; - } +.icons-list-searchbar:focus, +.icons-list-searchbar:hover { + background-color: #e7e7e7; +} - *:focus-visible { - outline: auto; - } +.icons-list-searchbar:focus-visible { + outline: auto; } @font-face { font-family: 'MaterialDesignIcons'; - src: url('../../node_modules/@react-native-vector-icons/material-design-icons/fonts/MaterialDesignIcons.ttf') format('truetype'); + src: url('@react-native-vector-icons/material-design-icons/fonts/MaterialDesignIcons.ttf') + format('truetype'); } .badge:is(a .badge) { @@ -213,7 +193,7 @@ a .badge .badge-text { } [data-theme='dark'] .badge { - color: white + color: white; } .badge-supported { @@ -245,10 +225,13 @@ a .badge .badge-text { display: flex; flex-direction: row; flex-wrap: wrap; + gap: 32px; min-width: 0; + align-items: flex-start; + overflow: visible; } -.color-picker { +table.color-picker { display: table; width: 100%; overflow: visible; @@ -261,7 +244,7 @@ a .badge .badge-text { left: 0; right: 0; bottom: 0; - background: rgba(0,0,0,.2); + background: rgba(0, 0, 0, 0.2); z-index: 1; } @@ -362,7 +345,6 @@ a .badge .badge-text { flex-direction: row; gap: 20px; margin-top: 20px; - } .color-picker-themes > div { flex: 1 0 300px; @@ -388,66 +370,76 @@ a .badge .badge-text { .color-picker-preview-box { padding: 6px 6px 18px 6px; font-size: 12px; - display:block; + display: block; } -.color-picker-button:hover, .color-picker-button:focus, -.color-picker-button-additional:hover, .color-picker-button-additional:focus, -.color-picker-button-cancel:hover, .color-picker-button-cancel:focus{ +.color-picker-button:hover, +.color-picker-button:focus, +.color-picker-button-additional:hover, +.color-picker-button-additional:focus, +.color-picker-button-cancel:hover, +.color-picker-button-cancel:focus { opacity: 0.8; } - @media (max-width: 680px) { .showcase-gallery { justify-content: center; } } +.showcase-card { + flex: 0 0 auto; + overflow: visible; +} + .showcase-image-container { + position: relative; overflow: hidden; - margin: 16px; + width: 270px; + height: 480px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); + background: #ffffff; + isolation: isolate; } -@media (min-width: 420px) { - .showcase-image-container { - height: 528px; - } - - .showcase-image-container:hover .showcase-info, - .showcase-image-container:focus .showcase-info { - transform: translateY(-48px); - } +.showcase-image-container:hover, +.showcase-image-container:focus-within { + z-index: 2; + box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18); } +.showcase-image-container:hover .showcase-info, +.showcase-image-container:focus-within .showcase-info { + transform: translateY(0); +} .showcase-image { display: block; - max-height: 480px; - width: auto; -} - -@media (min-width: 420px) { - .showcase-image { - height: 480px; - width: 270px; - } + width: 100%; + height: 100%; + object-fit: cover; } .showcase-info { - height: 96px; - padding: 12px; - transform: translateY(0); - transition: 150ms; -} - -.showcase-app-name { - font-size: 16px; - margin-top: 0; + position: absolute; + left: 0; + right: 0; + bottom: 0; + min-height: 136px; + padding: 18px 24px 20px; + transform: translateY(calc(100% - 68px)); + transition: transform 180ms ease; + box-shadow: 0 -16px 32px rgba(15, 23, 42, 0.18); +} + +.showcase-info .showcase-app-name { + font-size: 17px; + line-height: 1.2; + margin-block-start: 0; + margin-block-end: 12px; margin-bottom: 12px; - - width: 246px; + width: auto; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; @@ -456,14 +448,36 @@ a .badge .badge-text { .showcase-badge-container { display: flex; flex-direction: row; + align-items: center; margin-top: 16px; - padding-left: 3px; + padding-left: 1px; } .showcase-separation { margin: 0 10px; } +@media (max-width: 420px) { + .showcase-gallery { + gap: 24px; + } + + .showcase-image-container { + width: min(270px, 100%); + height: auto; + } + + .showcase-image { + height: auto; + aspect-ratio: 9 / 16; + } + + .showcase-info { + min-height: 124px; + transform: translateY(calc(100% - 60px)); + } +} + /* Switch.tsx */ .react-switch-checkbox { @@ -482,7 +496,7 @@ a .badge .badge-text { background: grey; border-radius: 100px; position: relative; - transition: background-color .2s; + transition: background-color 0.2s; } .react-switch-label .react-switch-button { @@ -498,7 +512,7 @@ a .badge .badge-text { box-shadow: 0 0 2px 0 rgba(10, 10, 10, 0.29); } -.react-switch-checkbox:checked+.react-switch-label .react-switch-button { +.react-switch-checkbox:checked + .react-switch-label .react-switch-button { left: calc(100% - 2px); transform: translateX(-100%); } @@ -532,4 +546,743 @@ a .badge .badge-text { .extends { margin-bottom: 20px; -} \ No newline at end of file +} + +.paper-version-selector { + position: relative; + z-index: 10; + min-width: 88px; + border: 1px solid var(--rp-c-divider, rgba(148, 163, 184, 0.24)); + border-radius: 999px; + background: var(--rp-c-bg-soft, rgba(18, 18, 20, 0.96)); + color: var(--rp-c-text-1, #fff); + backdrop-filter: blur(12px); +} + +.paper-version-selector summary { + list-style: none; + cursor: pointer; + padding: 8px 14px; + font-size: 13px; + font-weight: 600; + text-align: center; + white-space: nowrap; +} + +.paper-version-selector summary::-webkit-details-marker { + display: none; +} + +.paper-version-selector[open] { + border-radius: 18px; +} + +.paper-version-selector-menu { + position: absolute; + top: calc(100% + 8px); + right: 0; + display: grid; + min-width: 88px; + padding: 8px; + gap: 4px; + border: 1px solid var(--rp-c-divider, rgba(148, 163, 184, 0.24)); + border-radius: 18px; + background: inherit; + box-shadow: 0 18px 40px rgba(15, 23, 42, 0.18); +} + +.paper-version-selector-link { + display: inline-flex; + align-items: center; + justify-content: space-between; + border-radius: 12px; + padding: 8px 10px; + color: inherit; + text-decoration: none; + gap: 6px; +} + +.paper-version-selector-link:hover, +.paper-version-selector-link.active { + background: rgba(104, 88, 196, 0.22); +} + +html:not(.dark) .paper-version-selector { + background: rgba(255, 255, 255, 0.92); + color: #1c1b1f; +} + +html:not(.dark) .paper-version-selector-menu { + box-shadow: 0 18px 40px rgba(15, 23, 42, 0.08); +} + +@media (max-width: 960px) { + .paper-version-selector { + min-width: 76px; + } + + .paper-version-selector summary { + padding-inline: 12px; + } +} + +.rp-nav__others > .rp-nav-menu__item, +.rp-nav-hamburger__md__hover-group > .rp-nav-menu__item { + display: none; +} + +.rp-doc-layout__outline:empty, +.rp-doc-layout__outline:not(:has(.rp-toc-item)) { + display: none; +} + +.rp-outline__action-row:has(.rp-llms-copy-button), +.rp-outline__action-row:has(.rp-llms-view-options__trigger) { + display: none; +} + +.rp-outline__bottom:not(:has(.rp-edit-link, .rp-scroll-to-top)) { + display: none; +} + +.rp-outline__bottom:not(:has(.rp-edit-link, .rp-scroll-to-top)) + + .rp-outline__divider, +.rp-outline__divider:has( + + .rp-outline__bottom:not(:has(.rp-edit-link, .rp-scroll-to-top)) +) { + display: none; +} + +.rp-llms-container { + display: none !important; +} + +.paper-home-showcase { + margin: 56px auto 0; + box-sizing: border-box; + width: min(72rem, calc(100% - 16px)); + padding: 0 40px 56px; +} + +.paper-home-showcase__intro { + display: grid; + gap: 16px; + margin-bottom: 18px; +} + +.paper-home-showcase__eyebrow { + margin: 0 0 12px; + color: #7c4dff; + font-size: 14px; + font-weight: 600; + letter-spacing: 0; + text-transform: uppercase; +} + +.paper-home-showcase__title { + margin: 0; + font-size: clamp(32px, 4vw, 54px); + line-height: 1.04; +} + +.paper-home-showcase__copy, +.paper-home-showcase__links { + color: var(--rp-c-text-2); + font-size: 18px; + line-height: 1.55; +} + +.paper-home-showcase__copy { + margin: 0; + max-width: 760px; +} + +.paper-home-showcase__links { + margin: 0 0 24px; +} + +.paper-home-showcase__links a { + color: inherit; +} + +.paper-get-started-buttons { + display: flex; + flex-wrap: wrap; + gap: 1rem; + justify-content: center; + padding-bottom: 1rem; +} + +.paper-get-started-button { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 2.75rem; + padding: 0 1.25rem; + border: 1px solid; + border-radius: 999px; + font-size: 0.95rem; + font-weight: 600; + text-decoration: none; + transition: + transform 120ms ease, + background-color 120ms ease; +} + +.paper-get-started-button:hover { + transform: translateY(-1px); + text-decoration: none; +} + +.paper-get-started-button--primary { + border-color: #6750a4; + background-color: #6750a4; + color: #ffffff; +} + +.paper-get-started-button--secondary { + border-color: #79747e; + background-color: transparent; + color: #49454f; +} + +[data-theme='dark'] .paper-get-started-button--primary { + border-color: #d0bcff; + background-color: #d0bcff; + color: #1d192b; +} + +[data-theme='dark'] .paper-get-started-button--secondary { + border-color: #938f99; + color: #e6e1e5; +} + +.paper-home-showcase__gallery { + display: grid; + gap: 16px; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 24px; +} + +.paper-home-showcase__gallery-image { + width: min(100%, 348px); + margin: 0 auto; + border-radius: 20px; + border: 1px solid rgba(124, 77, 255, 0.12); + background: rgba(124, 77, 255, 0.03); +} + +.paper-prerelease-notice { + display: grid; + grid-template-columns: auto 1fr; + gap: 20px; + margin: 0 0 32px; + padding: 20px 24px; + border: 1px solid rgba(245, 158, 11, 0.28); + border-left: 4px solid #f59e0b; + border-radius: 20px; + background: rgba(255, 251, 235, 0.82); + box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06); +} + +.paper-prerelease-notice__icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border: 2px solid #f59e0b; + border-radius: 12px; + color: #f59e0b; + font-size: 24px; + font-weight: 700; + line-height: 1; +} + +.paper-prerelease-notice__content { + min-width: 0; +} + +.paper-prerelease-notice__title, +.paper-prerelease-notice__body { + margin: 0; +} + +.paper-prerelease-notice__title { + color: #1f2937; + font-size: 18px; + font-weight: 600; +} + +.paper-prerelease-notice__body { + margin-top: 10px; + color: #374151; + font-size: 16px; + line-height: 1.6; +} + +.paper-prerelease-notice__body a { + color: #7c4dff; + font-weight: 600; +} + +.paper-banner-example { + margin: 0; + padding: 28px; + border-radius: 32px; + border: 1px solid var(--paper-example-border); + background: linear-gradient( + 180deg, + var(--paper-example-panel-alt) 0%, + var(--paper-example-panel) 100% + ); + color: var(--paper-example-text); + box-shadow: var(--paper-example-shadow); +} + +.paper-banner-example__toolbar { + display: flex; + flex-wrap: wrap; + gap: 12px; + align-items: center; + margin-bottom: 18px; +} + +.paper-banner-example__button, +.paper-banner-example__avatar, +.paper-banner-example__fab, +.paper-banner-example__folder-button { + border: 1px solid transparent; + font: inherit; + letter-spacing: 0; + transition: + transform 120ms ease, + box-shadow 120ms ease, + background-color 120ms ease, + border-color 120ms ease; +} + +.paper-banner-example__button:hover, +.paper-banner-example__avatar:hover, +.paper-banner-example__fab:hover, +.paper-banner-example__folder-button:hover { + transform: translateY(-1px); +} + +.paper-banner-example__button { + display: inline-flex; + align-items: center; + gap: 12px; + min-height: 48px; + padding: 0 24px; + border-radius: 999px; + font-size: 16px; + font-weight: 600; +} + +.paper-banner-example__button-status { + width: 10px; + height: 10px; + border-radius: 999px; + background: color-mix( + in srgb, + var(--paper-example-accent) 36%, + var(--paper-example-panel-soft) + ); +} + +.paper-banner-example__button-status--busy { + background: var(--paper-example-accent); + box-shadow: 0 0 0 6px rgba(103, 80, 164, 0.12); +} + +.paper-banner-example__button--outlined, +.paper-banner-example__button--tonal, +.paper-banner-example__avatar, +.paper-banner-example__folder-button { + border-color: var(--paper-example-border); + background: var(--paper-example-panel-soft); + color: var(--paper-example-text); +} + +.paper-banner-example__button--tonal { + box-shadow: 0 12px 24px rgba(103, 80, 164, 0.08); +} + +.paper-banner-example__button--selected { + border-color: color-mix( + in srgb, + var(--paper-example-accent) 40%, + var(--paper-example-border) + ); +} + +.paper-banner-example__camera-icon { + position: relative; + width: 18px; + height: 12px; + border: 2px solid currentColor; + border-radius: 4px; +} + +.paper-banner-example__camera-icon::before { + content: ''; + position: absolute; + top: -5px; + left: 3px; + width: 8px; + height: 4px; + border-radius: 2px 2px 0 0; + background: currentColor; +} + +.paper-banner-example__button--contained, +.paper-banner-example__fab, +.paper-banner-example__avatar { + background: var(--paper-example-accent); + color: var(--paper-example-accent-text); + box-shadow: 0 10px 18px rgba(103, 80, 164, 0.22); +} + +.paper-banner-example__fab-group { + display: flex; + gap: 14px; + align-items: center; +} + +.paper-banner-example__fab, +.paper-banner-example__avatar, +.paper-banner-example__folder-button { + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 24px; + font-weight: 600; +} + +.paper-banner-example__fab { + width: 64px; + height: 64px; + border-radius: 24px; +} + +.paper-banner-example__fab--small { + width: 54px; + height: 54px; + border-radius: 20px; +} + +.paper-banner-example__fab--medium { + width: 78px; + height: 78px; + border-radius: 28px; +} + +.paper-banner-example__fab--large { + width: 108px; + height: 108px; + border-radius: 34px; + font-size: 34px; +} + +.paper-banner-example__avatar { + width: 84px; + height: 84px; + border-radius: 999px; + font-size: 40px; + margin-left: auto; +} + +.paper-banner-example__hero-row { + display: flex; + margin-bottom: 16px; +} + +.paper-banner-example__folder-button { + width: 84px; + height: 84px; + border-radius: 999px; + box-shadow: 0 14px 26px rgba(103, 80, 164, 0.12); +} + +.paper-banner-example__folder-button--raised { + box-shadow: 0 18px 32px rgba(103, 80, 164, 0.2); +} + +.paper-banner-example__folder-icon { + position: relative; + width: 34px; + height: 24px; + border-radius: 6px; + background: currentColor; +} + +.paper-banner-example__folder-icon::before { + content: ''; + position: absolute; + top: -5px; + left: 3px; + width: 14px; + height: 8px; + border-radius: 4px 4px 0 0; + background: currentColor; +} + +.paper-banner-example__progress { + height: 8px; + border-radius: 999px; + overflow: hidden; + background: var(--paper-example-panel-soft); + margin-bottom: 28px; +} + +.paper-banner-example__progress-bar { + width: 64%; + height: 100%; + background: linear-gradient( + 90deg, + var(--paper-example-accent) 0%, + var(--paper-example-accent-strong) 100% + ); +} + +.paper-banner-example__headline-group { + margin-bottom: 24px; +} + +.paper-banner-example__display-large, +.paper-banner-example__display-medium { + margin: 0; + letter-spacing: 0; +} + +.paper-banner-example__display-large { + font-size: clamp(44px, 6vw, 72px); + line-height: 0.98; +} + +.paper-banner-example__display-medium { + margin-top: 14px; + font-size: clamp(28px, 4vw, 44px); + line-height: 1.08; +} + +.paper-banner-example__body { + margin: 20px 0 0; + max-width: 780px; + color: var(--paper-example-muted); + font-size: 18px; + line-height: 1.65; +} + +.paper-banner-example__switches, +.paper-banner-example__radios { + display: flex; + flex-wrap: wrap; + gap: 20px; +} + +.paper-banner-example__switches { + margin-bottom: 24px; +} + +.paper-banner-example__switch, +.paper-banner-example__radio { + display: inline-flex; + align-items: center; + gap: 10px; + font-size: 18px; +} + +.paper-banner-example__switch input, +.paper-banner-example__radio input { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.paper-banner-example__switch-control { + position: relative; + display: inline-flex; + align-items: center; + width: 54px; + height: 32px; +} + +.paper-banner-example__switch-track { + width: 54px; + height: 32px; + border-radius: 999px; + border: 2px solid var(--paper-example-muted); + background: transparent; + transition: + background-color 120ms ease, + border-color 120ms ease; +} + +.paper-banner-example__switch-thumb { + position: absolute; + top: 7px; + left: 7px; + width: 14px; + height: 14px; + border-radius: 999px; + background: var(--paper-example-muted); + transition: + transform 120ms ease, + background-color 120ms ease; +} + +.paper-banner-example__switch + input:checked + + .paper-banner-example__switch-control + .paper-banner-example__switch-track { + border-color: var(--paper-example-accent); + background: color-mix(in srgb, var(--paper-example-accent) 28%, transparent); +} + +.paper-banner-example__switch + input:checked + + .paper-banner-example__switch-control + .paper-banner-example__switch-thumb { + transform: translateX(22px); + background: var(--paper-example-accent); +} + +.paper-banner-example__fields { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 24px; + margin-bottom: 24px; +} + +.paper-banner-example__field { + display: grid; + gap: 10px; + color: var(--paper-example-muted); + font-size: 15px; +} + +.paper-banner-example__field input { + min-height: 64px; + padding: 0 18px; + border: 1px solid var(--paper-example-border); + border-radius: 20px; + background: var(--paper-example-panel-soft); + color: var(--paper-example-text); + font: inherit; +} + +.paper-banner-example__field input::placeholder { + color: var(--paper-example-muted); +} + +.paper-banner-example__field-input--outlined { + background: transparent !important; + border-color: var(--paper-example-accent) !important; +} + +.paper-banner-example__radio-control { + width: 28px; + height: 28px; + border: 2px solid var(--paper-example-muted); + border-radius: 999px; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.paper-banner-example__radio-dot { + width: 12px; + height: 12px; + border-radius: 999px; + background: transparent; +} + +.paper-banner-example__radio + input:checked + + .paper-banner-example__radio-control { + border-color: var(--paper-example-accent); +} + +.paper-banner-example__radio + input:checked + + .paper-banner-example__radio-control + .paper-banner-example__radio-dot { + background: var(--paper-example-accent); +} + +@media (min-width: 997px) { + .paper-home-showcase__intro { + grid-template-columns: minmax(0, 1.25fr) minmax(320px, 0.8fr); + align-items: end; + } +} + +@media (max-width: 996px) { + .paper-home-showcase { + width: min(100%, calc(100% - 16px)); + padding: 0 16px 40px; + margin-top: 44px; + } + + .paper-home-showcase__gallery { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .paper-banner-example { + padding: 22px; + border-radius: 28px; + } + + .paper-banner-example__avatar { + margin-left: 0; + } + + .paper-banner-example__fields { + grid-template-columns: 1fr; + gap: 18px; + } + + .paper-prerelease-notice { + padding: 20px 22px; + } +} + +@media (max-width: 640px) { + .paper-home-showcase__gallery { + grid-template-columns: 1fr; + } + + .paper-home-showcase__gallery-image { + width: 100%; + } + + .paper-banner-example__switch, + .paper-banner-example__radio { + font-size: 16px; + } + + .paper-banner-example__fab-group { + width: 100%; + justify-content: space-between; + } + + .paper-banner-example__fab--large { + width: 88px; + height: 88px; + } + + .paper-banner-example__avatar { + width: 72px; + height: 72px; + font-size: 30px; + } + + .paper-prerelease-notice { + grid-template-columns: 1fr; + gap: 16px; + margin-bottom: 24px; + padding: 18px; + } +} diff --git a/docs/src/data/extendedExamples.js b/docs/src/data/extendedExamples.ts similarity index 54% rename from docs/src/data/extendedExamples.js rename to docs/src/data/extendedExamples.ts index f7b196afff..20800e2005 100644 --- a/docs/src/data/extendedExamples.js +++ b/docs/src/data/extendedExamples.ts @@ -1,9 +1,9 @@ -const { - staticCode, +import { dynamicCode, -} = require('./extendedExamples/BottomNavigationBar'); + staticCode, +} from './extendedExamples/BottomNavigationBar.ts'; -const extendedExamples = { +export const extendedExamples = { 'BottomNavigation.Bar': { 'with React Navigation': { static: staticCode, @@ -11,7 +11,3 @@ const extendedExamples = { }, }, }; - -module.exports = { - extendedExamples, -}; diff --git a/docs/src/data/extendedExamples/BottomNavigationBar.js b/docs/src/data/extendedExamples/BottomNavigationBar.ts similarity index 93% rename from docs/src/data/extendedExamples/BottomNavigationBar.js rename to docs/src/data/extendedExamples/BottomNavigationBar.ts index 81bb261a96..7a49db0bce 100644 --- a/docs/src/data/extendedExamples/BottomNavigationBar.js +++ b/docs/src/data/extendedExamples/BottomNavigationBar.ts @@ -1,7 +1,7 @@ -const staticCode = `import { Text, View } from 'react-native'; +export const staticCode = `import { Text, View } from 'react-native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { Provider, BottomNavigation } from 'react-native-paper'; -import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'; +import MaterialCommunityIcons from '@react-native-vector-icons/material-design-icons'; import { CommonActions, createStaticNavigation, @@ -99,11 +99,11 @@ export default function App() { ); }`; -const dynamicCode = `import { Text, View } from 'react-native'; +export const dynamicCode = `import { Text, View } from 'react-native'; import { NavigationContainer, CommonActions } from '@react-navigation/native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { Provider, BottomNavigation } from 'react-native-paper'; -import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons'; +import MaterialCommunityIcons from '@react-native-vector-icons/material-design-icons'; function HomeScreen() { return ( @@ -195,8 +195,3 @@ export default function App() { ); } `; - -module.exports = { - staticCode, - dynamicCode, -}; diff --git a/docs/src/data/screenshots.js b/docs/src/data/screenshots.ts similarity index 89% rename from docs/src/data/screenshots.js rename to docs/src/data/screenshots.ts index c1afa99a6a..b7c59d424d 100644 --- a/docs/src/data/screenshots.js +++ b/docs/src/data/screenshots.ts @@ -1,4 +1,4 @@ -const screenshots = { +export const screenshots = { ActivityIndicator: 'screenshots/activity-indicator.gif', Appbar: 'screenshots/appbar.png', 'Appbar.Action': 'screenshots/appbar-action-android.png', @@ -79,7 +79,6 @@ const screenshots = { }, AnimatedFAB: 'screenshots/animated-fab.gif', 'FAB.Group': 'screenshots/fab-group.gif', - HelperText: 'screenshots/helper-text.gif', Icon: 'screenshots/icon.png', IconButton: { default: 'screenshots/icon-button-1.png', @@ -147,20 +146,9 @@ const screenshots = { }, Text: 'screenshots/typography.png', TextInput: { - 'flat (focused)': 'screenshots/textinput-flat.focused.png', - 'flat (disabled)': 'screenshots/textinput-flat.disabled.png', - 'outlined (focused)': 'screenshots/textinput-outlined.focused.png', - 'outlined (disabled)': 'screenshots/textinput-outlined.disabled.png', - }, - 'TextInput.Affix': 'screenshots/textinput-outline.affix.png', - 'TextInput.Icon': 'screenshots/textinput-flat.icon.png', - ToggleButton: 'screenshots/toggle-button.png', - 'ToggleButton.Group': 'screenshots/toggle-button-group.gif', - 'ToggleButton.Row': 'screenshots/toggle-button-row.gif', + filled: 'screenshots/text-input-filled.png', + outlined: 'screenshots/text-input-outlined.png', + }, Tooltip: 'screenshots/tooltip.png', TouchableRipple: 'screenshots/touchable-ripple.gif', }; - -module.exports = { - screenshots, -}; diff --git a/docs/src/data/themeColors.js b/docs/src/data/themeColors.ts similarity index 95% rename from docs/src/data/themeColors.js rename to docs/src/data/themeColors.ts index f12b955341..118114da27 100644 --- a/docs/src/data/themeColors.js +++ b/docs/src/data/themeColors.ts @@ -1,4 +1,4 @@ -const themeColors = { +export const themeColors = { ActivityIndicator: { '-': { borderColor: 'theme.colors.primary', @@ -164,7 +164,7 @@ const themeColors = { 'textColor/iconColor': 'theme.colors.onTertiaryContainer', }, surface: { - backgroundColor: 'theme.colors.elevarion.level3', + backgroundColor: 'theme.colors.elevation.level3', 'textColor/iconColor': 'theme.colors.primary', }, }, @@ -186,21 +186,10 @@ const themeColors = { 'textColor/iconColor': 'theme.colors.onTertiaryContainer', }, surface: { - backgroundColor: 'theme.colors.elevarion.level3', + backgroundColor: 'theme.colors.elevation.level3', 'textColor/iconColor': 'theme.colors.primary', }, }, - HelperText: { - disabled: { - textColor: 'theme.colors.onSurfaceDisabled', - }, - default: { - textColor: 'theme.colors.onSurfaceVariant', - }, - error: { - textColor: 'theme.colors.error', - }, - }, IconButton: { selected: { default: { @@ -338,7 +327,3 @@ const themeColors = { }, }, }; - -module.exports = { - themeColors, -}; diff --git a/docs/src/data/versionRouteFallbacks.json b/docs/src/data/versionRouteFallbacks.json new file mode 100644 index 0000000000..326741b5b5 --- /dev/null +++ b/docs/src/data/versionRouteFallbacks.json @@ -0,0 +1,17 @@ +{ + "next": { + "/docs/components/Checkbox/CheckboxAndroid": "/6.x/docs/components/Checkbox/Checkbox", + "/docs/components/Checkbox/CheckboxIOS": "/6.x/docs/components/Checkbox/Checkbox", + "/docs/components/FAB/AnimatedFAB": "/6.x/docs/components/FAB/FAB", + "/docs/components/FAB/FABGroup": "/6.x/docs/components/FAB/FAB", + "/docs/components/HelperText/HelperText": "/6.x/", + "/docs/components/TextInput/TextInputAffix": "/6.x/docs/components/TextInput/TextInput", + "/docs/guides/migration-guide-to-5.0": "/6.x/docs/guides/migration" + }, + "stable": { + "/6.x/docs/components/FAB/FABExtended": "/docs/components/FAB/FAB", + "/6.x/docs/components/FAB/FABMenu": "/docs/components/FAB/FAB", + "/6.x/docs/guides/migration": "/docs/guides/migration-guide-to-5.0", + "/6.x/docs/guides/rtl": "/" + } +} diff --git a/docs/src/pages/index.mdx b/docs/src/pages/index.mdx deleted file mode 100644 index 2870fda5b7..0000000000 --- a/docs/src/pages/index.mdx +++ /dev/null @@ -1,63 +0,0 @@ -![paperLogo](../../static/images/sidebar-logo.svg) - -# Cross-platform Material Design for React Native - -import {Button} from 'react-native-paper'; -import GetStartedButtons from '../components/GetStartedButtons'; -import BannerExample from '../components/BannerExample'; - -Paper is a collection of customizable and production-ready components for React Native, following Google’s Material Design guidelines. - - - -Or check the demo app on [iOS](https://apps.apple.com/app/react-native-paper/id1548934513) or [Android](https://play.google.com/store/apps/details?id=com.callstack.reactnativepaperexample). - - - - - - - - - - - - - - - - - - diff --git a/docs/src/utils/__tests__/themeColors.test.tsx b/docs/src/utils/__tests__/themeColors.test.tsx deleted file mode 100644 index 4803ea7a6b..0000000000 --- a/docs/src/utils/__tests__/themeColors.test.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { getMaxNestedLevel, getUniqueNestedKeys } from '../themeColors'; - -const Button = { - active: { - elevated: { - backgroundColor: 'theme.colors.elevation.level1', - color: 'theme.colors.primary', - }, - contained: { - backgroundColor: 'theme.colors.primary', - color: 'theme.colors.onPrimary', - }, - 'contained-tonal': { - backgroundColor: 'theme.colors.secondaryContainer', - color: 'theme.colors.onSecondaryContainer', - }, - outlined: { - color: 'theme.colors.primary', - borderColor: 'theme.colors.outline', - }, - text: { - color: 'theme.colors.primary', - }, - }, - disabled: { - elevated: { - backgroundColor: 'theme.colors.surfaceDisabled', - color: 'theme.colors.onSurfaceDisabled', - }, - contained: { - backgroundColor: 'theme.colors.surfaceDisabled', - color: 'theme.colors.onSurfaceDisabled', - }, - 'contained-tonal': { - backgroundColor: 'theme.colors.surfaceDisabled', - color: 'theme.colors.onSurfaceDisabled', - }, - outlined: { - color: 'theme.colors.onSurfaceDisabled', - borderColor: 'theme.colors.surfaceDisabled', - }, - text: { - color: 'theme.colors.onSurfaceDisabled', - }, - }, -}; - -const Card = { - contained: { - backgroundColor: 'theme.colors.surfaceVariant', - }, - outlined: { - backgroundColor: 'theme.colors.surface', - borderColor: 'theme.colors.outline', - }, -}; - -const Custom = { - active: { - contained: { - pressed: { - backgroundColor: 'theme.colors.primary', - }, - }, - outlined: { - backgroundColor: 'theme.colors.surface', - }, - }, -}; - -describe('getUniqueNestedKeys', () => { - it('should return an array of unique keys for flat structure', () => { - expect(getUniqueNestedKeys(Card)).toEqual([ - 'backgroundColor', - 'borderColor', - ]); - }); - - it('should return an array of unique keys for nested structure', () => { - expect(getUniqueNestedKeys(Button)).toEqual([ - 'backgroundColor', - 'color', - 'borderColor', - ]); - }); - - it('should return an empty array if data object is empty', () => { - expect(getUniqueNestedKeys({})).toEqual([]); - }); -}); - -describe('getMaxNestedLevel', () => { - it('should return the correct nesting level', () => { - const buttonLevel = getMaxNestedLevel(Button); - const cardLevel = getMaxNestedLevel(Card); - - expect(buttonLevel).toEqual(2); - expect(cardLevel).toEqual(1); - }); - - it('should return 0 if the object is not nested', () => { - const level = getMaxNestedLevel({ - foo: 'bar', - }); - expect(level).toEqual(0); - }); - - it('should handle nested objects at different levels', () => { - const level = getMaxNestedLevel(Custom); - expect(level).toEqual(3); - }); -}); diff --git a/docs/src/utils/themeColors.tsx b/docs/src/utils/themeColors.tsx index dcc099ec58..befc487d80 100644 --- a/docs/src/utils/themeColors.tsx +++ b/docs/src/utils/themeColors.tsx @@ -23,9 +23,9 @@ export const getUniqueNestedKeys = (data: DataObject): string[] => { export const getMaxNestedLevel = (obj: DataObject): number => { let maxNestedLevel = 0; - for (const key in obj) { - if (typeof obj[key] === 'object') { - const nestedLevel = getMaxNestedLevel(obj[key] as DataObject) + 1; + for (const value of Object.values(obj)) { + if (typeof value === 'object') { + const nestedLevel = getMaxNestedLevel(value) + 1; maxNestedLevel = Math.max(maxNestedLevel, nestedLevel); } } diff --git a/docs/src/utils/themes.tsx b/docs/src/utils/themes.tsx index 13738cca61..182298a21d 100644 --- a/docs/src/utils/themes.tsx +++ b/docs/src/utils/themes.tsx @@ -1,13 +1,11 @@ import { argbFromHex, themeFromSourceColor, - // eslint-disable-next-line import/no-unresolved } from '@material/material-color-utilities'; import camelCase from 'camelcase'; import Color from 'color'; type ARGBTheme = ReturnType; -type CSSTheme = Record; type RGBColorList = { primary: string; @@ -47,22 +45,22 @@ const argbThemeFromColor = ( return themeFromSourceColor(argbFromHex(color)).schemes[type].toJSON(); }; +const hexFromArgb = (value: number) => + `#${(value & 0xffffff).toString(16).padStart(6, '0')}`; + const argbThemeToHexTheme = (theme: ARGBTheme) => { return Object.fromEntries( - //@ts-ignore - Object.entries(theme).map(([key, value]) => [key, Color(value).hex()]) - ) as CSSTheme; + Object.entries(theme).map(([key, value]) => [key, hexFromArgb(value)]) + ); }; const argbThemeToRgbTheme = (theme: ARGBTheme) => { return Object.fromEntries( - //@ts-ignore Object.entries(theme).map(([key, value]) => [ key, - //@ts-ignore - Color(value).rgb().string(), + Color(hexFromArgb(value)).rgb().string(), ]) - ) as CSSTheme; + ); }; export const hexThemeFromColor = ( diff --git a/docs/src/utils/versionRoutes.ts b/docs/src/utils/versionRoutes.ts new file mode 100644 index 0000000000..a20e6fe8e0 --- /dev/null +++ b/docs/src/utils/versionRoutes.ts @@ -0,0 +1,40 @@ +import routeFallbacks from '../data/versionRouteFallbacks.json'; + +const NEXT_VERSION_PREFIX = '/6.x'; +const NEXT_HOME_ROUTE = '/6.x/'; +const STABLE_HOME_ROUTE = '/'; +const stableFallbacks: Record = routeFallbacks.stable; +const nextFallbacks: Record = routeFallbacks.next; + +const stripNextVersionPrefix = (routePath: string) => { + const stableRoute = routePath.replace(/^\/6\.x(?=\/|$)/, ''); + + return stableRoute === '' ? STABLE_HOME_ROUTE : stableRoute; +}; + +export const getStableRoute = (routePath: string) => { + if (!routePath.startsWith(NEXT_VERSION_PREFIX)) { + return routePath || STABLE_HOME_ROUTE; + } + + if (routePath === NEXT_VERSION_PREFIX || routePath === NEXT_HOME_ROUTE) { + return STABLE_HOME_ROUTE; + } + + return stableFallbacks[routePath] ?? stripNextVersionPrefix(routePath); +}; + +export const getNextRoute = (routePath: string) => { + if (routePath.startsWith(NEXT_VERSION_PREFIX)) { + return routePath; + } + + if (routePath === STABLE_HOME_ROUTE || routePath === '') { + return NEXT_HOME_ROUTE; + } + + return nextFallbacks[routePath] ?? `${NEXT_VERSION_PREFIX}${routePath}`; +}; + +export const hasSameStableRoute = (routePath: string) => + getStableRoute(routePath) === stripNextVersionPrefix(routePath); diff --git a/docs/static/llms.txt b/docs/static/llms.txt deleted file mode 100644 index 46da789d41..0000000000 --- a/docs/static/llms.txt +++ /dev/null @@ -1,80 +0,0 @@ -# React Native Paper - -> Material Design components for React Native. -> See the full documentation at: https://callstack.github.io/react-native-paper/ - -## Getting Started - -- Install: npm install react-native-paper react-native-safe-area-context -- Wrap your app with PaperProvider -- Default theme is Material You (MD3); set theme.version: 2 for MD2 -- Requires React Native 0.72+ and React 18+ - -## Guides - -- Getting Started: https://callstack.github.io/react-native-paper/docs/guides/getting-started -- Theming: https://callstack.github.io/react-native-paper/docs/guides/theming -- Icons: https://callstack.github.io/react-native-paper/docs/guides/icons -- Fonts: https://callstack.github.io/react-native-paper/docs/guides/fonts -- React Native Web: https://callstack.github.io/react-native-paper/docs/guides/react-native-web -- Migration to v5: https://callstack.github.io/react-native-paper/docs/guides/migration-guide-to-5.0 -- Recommended Libraries: https://callstack.github.io/react-native-paper/docs/guides/recommended-libraries -- Contributing: https://callstack.github.io/react-native-paper/docs/guides/contributing -- Bottom Navigation: https://callstack.github.io/react-native-paper/docs/guides/bottom-navigation -- Ripple Effect: https://callstack.github.io/react-native-paper/docs/guides/ripple-effect - -## Components - -- ActivityIndicator: https://callstack.github.io/react-native-paper/docs/components/ActivityIndicator -- Appbar: https://callstack.github.io/react-native-paper/docs/components/Appbar -- Avatar: https://callstack.github.io/react-native-paper/docs/components/Avatar -- Badge: https://callstack.github.io/react-native-paper/docs/components/Badge -- Banner: https://callstack.github.io/react-native-paper/docs/components/Banner -- BottomNavigation: https://callstack.github.io/react-native-paper/docs/components/BottomNavigation -- Button: https://callstack.github.io/react-native-paper/docs/components/Button -- Card: https://callstack.github.io/react-native-paper/docs/components/Card -- Checkbox: https://callstack.github.io/react-native-paper/docs/components/Checkbox -- Chip: https://callstack.github.io/react-native-paper/docs/components/Chip -- DataTable: https://callstack.github.io/react-native-paper/docs/components/DataTable -- Dialog: https://callstack.github.io/react-native-paper/docs/components/Dialog -- Divider: https://callstack.github.io/react-native-paper/docs/components/Divider -- Drawer: https://callstack.github.io/react-native-paper/docs/components/Drawer -- FAB: https://callstack.github.io/react-native-paper/docs/components/FAB -- HelperText: https://callstack.github.io/react-native-paper/docs/components/HelperText -- IconButton: https://callstack.github.io/react-native-paper/docs/components/IconButton -- Icon: https://callstack.github.io/react-native-paper/docs/components/Icon -- List: https://callstack.github.io/react-native-paper/docs/components/List -- Menu: https://callstack.github.io/react-native-paper/docs/components/Menu -- Modal: https://callstack.github.io/react-native-paper/docs/components/Modal -- Portal: https://callstack.github.io/react-native-paper/docs/components/Portal -- ProgressBar: https://callstack.github.io/react-native-paper/docs/components/ProgressBar -- RadioButton: https://callstack.github.io/react-native-paper/docs/components/RadioButton -- Searchbar: https://callstack.github.io/react-native-paper/docs/components/Searchbar -- SegmentedButtons: https://callstack.github.io/react-native-paper/docs/components/SegmentedButtons -- Snackbar: https://callstack.github.io/react-native-paper/docs/components/Snackbar -- Surface: https://callstack.github.io/react-native-paper/docs/components/Surface -- Switch: https://callstack.github.io/react-native-paper/docs/components/Switch -- Text: https://callstack.github.io/react-native-paper/docs/components/Text -- TextInput: https://callstack.github.io/react-native-paper/docs/components/TextInput -- TouchableRipple: https://callstack.github.io/react-native-paper/docs/components/TouchableRipple -- Tooltip: https://callstack.github.io/react-native-paper/docs/components/Tooltip - -## Theming - -- Theme object accepts colors, dark, roundness, fonts, animation, and version properties -- Use useTheme() hook or ThemeProvider for nested theme overrides -- MD3 (Material You) is the default; set version: 2 for MD2 compatibility -- See theming guide: https://callstack.github.io/react-native-paper/docs/guides/theming - -## Provider Pattern - -- Wrap root component with PaperProvider (required) -- PaperProvider accepts an optional theme prop -- Supports theming with React Navigation via react-native-paper theming integration - -## Important Notes - -- This is a cross-platform library (iOS, Android, web) -- Built with TypeScript -- MIT licensed -- Maintained by Callstack diff --git a/docs/static/screenshots/textinput-flat.disabled.png b/docs/static/screenshots/textinput-flat.disabled.png deleted file mode 100644 index 5c47f3dcb1..0000000000 Binary files a/docs/static/screenshots/textinput-flat.disabled.png and /dev/null differ diff --git a/docs/static/screenshots/textinput-flat.focused.png b/docs/static/screenshots/textinput-flat.focused.png deleted file mode 100644 index 209ad28539..0000000000 Binary files a/docs/static/screenshots/textinput-flat.focused.png and /dev/null differ diff --git a/docs/static/screenshots/textinput-flat.icon.png b/docs/static/screenshots/textinput-flat.icon.png deleted file mode 100644 index 9de68a4bc9..0000000000 Binary files a/docs/static/screenshots/textinput-flat.icon.png and /dev/null differ diff --git a/docs/static/screenshots/textinput-outline.affix.png b/docs/static/screenshots/textinput-outline.affix.png deleted file mode 100644 index 410ecbd556..0000000000 Binary files a/docs/static/screenshots/textinput-outline.affix.png and /dev/null differ diff --git a/docs/static/screenshots/textinput-outlined.disabled.png b/docs/static/screenshots/textinput-outlined.disabled.png deleted file mode 100644 index 0bd3763260..0000000000 Binary files a/docs/static/screenshots/textinput-outlined.disabled.png and /dev/null differ diff --git a/docs/static/screenshots/textinput-outlined.focused.png b/docs/static/screenshots/textinput-outlined.focused.png deleted file mode 100644 index 6001c1cdd1..0000000000 Binary files a/docs/static/screenshots/textinput-outlined.focused.png and /dev/null differ diff --git a/docs/theme/Outline.tsx b/docs/theme/Outline.tsx new file mode 100644 index 0000000000..ded2f49549 --- /dev/null +++ b/docs/theme/Outline.tsx @@ -0,0 +1,38 @@ +import { useI18n } from '@rspress/core/dist/runtime/index.js'; +import { + EditLink, + ReadPercent, + Toc, + useDynamicToc, + useEditLink, +} from '@rspress/core/dist/theme/index.js'; + +export default function Outline() { + const t = useI18n(); + const headers = useDynamicToc(); + const editLink = useEditLink(); + + if (headers.length === 0) { + return null; + } + + return ( +
    +
    + {t('outlineTitle')} + +
    + + {editLink ? ( + <> +
    +
    + +
    + + ) : null} +
    + ); +} diff --git a/docs/theme/index.tsx b/docs/theme/index.tsx new file mode 100644 index 0000000000..3cd7557c48 --- /dev/null +++ b/docs/theme/index.tsx @@ -0,0 +1,34 @@ +import { Fragment, type ComponentProps } from 'react'; + +import { HomeBanner } from '@callstack/rspress-theme'; +import { Layout as BasicLayout } from '@rspress/core/dist/theme/index.js'; + +import Outline from './Outline'; +import PaperHomeShowcase from '../src/components/PaperHomeShowcase'; +import PaperVersionSelector from '../src/components/PaperVersionSelector'; +import VersionedPrereleaseNotice from '../src/components/VersionedPrereleaseNotice'; + +type LayoutProps = ComponentProps; + +const Layout = (props: LayoutProps) => ( + + + + + } + afterNavMenu={} + afterOutline={false} + beforeDocContent={} + /> +); + +export * from '@rspress/core/dist/theme/index.js'; +export { Layout, Outline }; diff --git a/docs/tsconfig.json b/docs/tsconfig.json index 56f834939e..3b8c64a185 100644 --- a/docs/tsconfig.json +++ b/docs/tsconfig.json @@ -2,7 +2,9 @@ "extends": "../tsconfig.json", "compilerOptions": { "paths": { - "react-native-paper": ["./src"] + "@docs/*": ["./src/*"], + "react-native-paper": ["../src/index"] } - } + }, + "include": ["**/*.ts", "**/*.tsx"] } diff --git a/eslint-local-rules/index.js b/eslint-local-rules/index.js deleted file mode 100644 index 04cafac316..0000000000 --- a/eslint-local-rules/index.js +++ /dev/null @@ -1,2 +0,0 @@ -Object.assign(exports, require('./no-import-react-forwardref')); -Object.assign(exports, require('./no-react-forwardref-usage')); diff --git a/eslint-local-rules/no-import-react-forwardref.js b/eslint-local-rules/no-import-react-forwardref.js deleted file mode 100644 index dff3cb07c9..0000000000 --- a/eslint-local-rules/no-import-react-forwardref.js +++ /dev/null @@ -1,27 +0,0 @@ -exports['no-import-react-forwardref'] = { - meta: { - docs: { - description: 'Disallow importing of React.forwardRef', - category: 'Possible Errors', - recommended: false, - }, - schema: [], - }, - create(context) { - return { - ImportDeclaration(node) { - if (node.source.value !== 'react') return; - - for (const specifier of node.specifiers) { - if (specifier.type !== 'ImportSpecifier') continue; - if (specifier.imported.name !== 'forwardRef') continue; - - context.report({ - loc: specifier.loc, - message: 'Import forwardRef from src/utils/forwardRef instead', - }); - } - }, - }; - }, -}; diff --git a/eslint-local-rules/no-react-forwardref-usage.js b/eslint-local-rules/no-react-forwardref-usage.js deleted file mode 100644 index e2d16a1645..0000000000 --- a/eslint-local-rules/no-react-forwardref-usage.js +++ /dev/null @@ -1,28 +0,0 @@ -exports['no-react-forwardref-usage'] = { - meta: { - docs: { - description: 'Disallow usage of React.forwardRef', - category: 'Possible Errors', - recommended: false, - }, - schema: [], - }, - create(context) { - return { - CallExpression(node) { - if (node.callee?.type !== 'MemberExpression') return; - - const { callee } = node; - if (callee.object.type !== 'Identifier') return; - if (callee.object.name !== 'React') return; - if (callee.property.type !== 'Identifier') return; - if (callee.property.name !== 'forwardRef') return; - - context.report({ - loc: callee.loc, - message: 'Use forwardRef from src/utils/forwardRef instead', - }); - }, - }; - }, -}; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000..ac6e65951f --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,258 @@ +import callstack from '@callstack/eslint-config/react-native.flat.js'; +import { defineConfig } from 'eslint/config'; +import testingLibrary from 'eslint-plugin-testing-library'; +import tseslint from 'typescript-eslint'; + +const testingLibraryReact = testingLibrary.configs['flat/react']; + +export default defineConfig( + { + ignores: [ + // jest + 'coverage/', + 'cache/jest/', + 'jest/testSetup.js', + '**/__fixtures__/', + '__ts-tests__/', + + // Build + 'build/', + 'dist/', + 'web-build/', + 'docs/.cache-loader/', + 'docs/build/', + 'docs/dist/', + 'docs/docs/components/', + 'docs/public/', + 'example/coverage/', + 'example/dist/', + + // generated docs + 'docs/5.x/component-docs.json', + 'docs/5.x/_nav.json', + 'docs/5.x/docs/_meta.json', + 'docs/5.x/docs/guides/_meta.json', + 'docs/5.x/docs/components/', + 'docs/6.x/docs/components/', + + // generated by bob + 'lib/', + + // Expo + 'example/.expo/', + 'example/android/', + 'example/ios/', + ], + }, + callstack, + { + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + languageOptions: { + parserOptions: { + project: [ + './tsconfig.source.json', + './docs/tsconfig.json', + './example/tsconfig.json', + ], + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + '@typescript-eslint/no-unused-vars': [ + 'error', + { + argsIgnorePattern: '^_', + varsIgnorePattern: '^_', + caughtErrors: 'none', + }, + ], + + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: true, + packageDir: ['.', './docs', './example'], + }, + ], + 'import/order': [ + 'error', + { + groups: [ + ['external', 'builtin'], + 'internal', + ['sibling', 'parent'], + 'index', + ], + pathGroups: [ + { + pattern: '@(react|react-native)', + group: 'external', + position: 'before', + }, + { + pattern: '@src/**', + group: 'internal', + }, + ], + pathGroupsExcludedImportTypes: ['internal', 'react'], + 'newlines-between': 'always', + alphabetize: { + order: 'asc', + caseInsensitive: true, + }, + }, + ], + + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'react-native', + importNames: ['Animated', 'PanResponder'], + message: + 'Use react-native-reanimated for animations and react-native-gesture-handler for gestures.', + }, + ], + }, + ], + + '@react-native/platform-colors': 'error', + + 'promise/no-callback-in-promise': 'error', + 'promise/no-nesting': 'error', + 'promise/no-promise-in-callback': 'error', + 'promise/no-return-in-finally': 'error', + 'promise/prefer-await-to-then': 'error', + 'promise/valid-params': 'error', + + 'react-hooks/exhaustive-deps': 'error', + + 'react/no-multi-comp': 'error', + 'react/react-in-jsx-scope': 'off', + + 'react-native/no-color-literals': 'error', + 'react-native/no-inline-styles': 'error', + 'react-native/no-raw-text': 'off', + 'react-native/no-unused-styles': 'error', + + 'react-native-a11y/has-valid-accessibility-descriptors': 'off', + }, + settings: { + 'import/extensions': ['.js', '.mjs', '.ts', '.tsx'], + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import/resolver': { + node: { + extensions: ['.js', '.mjs', '.ts', '.tsx'], + }, + }, + }, + }, + { + files: ['**/*.{ts,tsx}'], + rules: { + '@typescript-eslint/no-non-null-assertion': 'error', + '@typescript-eslint/no-unsafe-type-assertion': 'error', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', + }, + }, + { + files: ['**/*.test.{js,ts,tsx}', '**/__tests__/**/*.{js,ts,tsx}'], + + ...testingLibraryReact, + + settings: { + ...testingLibraryReact.settings, + + 'testing-library/custom-renders': ['render'], + 'testing-library/utils-module': 'test-utils', + }, + rules: { + ...testingLibraryReact.rules, + + 'no-restricted-syntax': [ + 'error', + { + selector: + "MemberExpression[property.name='props']:not([object.type='ThisExpression'])", + message: + 'Do not read TestInstance props directly. Prefer user-visible assertions.', + }, + { + selector: + 'CallExpression[callee.object.name="root"][callee.property.name=/^(find|findAll|findByType|findAllByType|findByProps|findAllByProps|query|queryAll)$/], CallExpression[callee.object.property.name="root"][callee.property.name=/^(find|findAll|findByType|findAllByType|findByProps|findAllByProps|query|queryAll)$/]', + message: + 'Do not traverse react-test-renderer trees directly. Prefer user-visible queries.', + }, + { + selector: + "CallExpression[callee.object.name='fireEvent'][callee.property.name=/^(press|longPress|changeText|scroll)$/]", + message: 'Use userEvent for user interactions instead of fireEvent.', + }, + { + selector: + "CallExpression[callee.name='fireEvent'][arguments.1.value=/^(press|longPress|onPress|onLongPress)$/][arguments.length=2]", + message: 'Use userEvent for user interactions instead of fireEvent.', + }, + { + selector: + "CallExpression[callee.name='fireEvent'][arguments.1.value=/^(changeText|onChangeText|scroll|onScroll)$/]", + message: 'Use userEvent for user interactions instead of fireEvent.', + }, + ], + + 'jest/expect-expect': 'error', + 'jest/no-commented-out-tests': 'error', + 'jest/no-disabled-tests': 'error', + 'jest/prefer-importing-jest-globals': 'error', + + 'react-native/no-inline-styles': 'off', + + 'testing-library/no-await-sync-events': 'off', + 'testing-library/no-debugging-utils': 'error', + }, + }, + { + files: [ + 'src/components/__tests__/Appbar/Appbar.test.tsx', + 'src/components/__tests__/Dialog.test.tsx', + 'src/components/__tests__/Searchbar.test.tsx', + ], + rules: { + 'testing-library/no-node-access': 'off', + }, + }, + { + files: ['src/components/__tests__/Tooltip.test.tsx'], + rules: { + 'testing-library/no-node-access': 'off', + 'testing-library/prefer-screen-queries': 'off', + 'testing-library/render-result-naming-convention': 'off', + }, + }, + { + files: ['example/**/*.{js,ts,tsx}'], + rules: { + 'react/prop-types': 'off', + }, + settings: { + 'import/core-modules': ['react-native-paper'], + }, + }, + { + files: ['docs/**/*.{js,ts,tsx}'], + settings: { + 'import/core-modules': ['@rspress/core/runtime', 'react-native-paper'], + }, + }, + { + files: ['eslint.config.mjs'], + rules: { + 'import/no-unresolved': 'off', + }, + } +); diff --git a/example/.eslintrc b/example/.eslintrc deleted file mode 100644 index 141d6b426d..0000000000 --- a/example/.eslintrc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../.eslintrc", - - "settings": { - "import/core-modules": ["react-native-paper", "react-native-paper/react-navigation"] - }, - - "rules": { - "react/prop-types": "off" - } -} diff --git a/example/.fingerprintignore b/example/.fingerprintignore new file mode 100644 index 0000000000..b3c5af777d --- /dev/null +++ b/example/.fingerprintignore @@ -0,0 +1,2 @@ +# built by pod install, would make the fingerprint differ from the one EAS computes +**/expo-modules-jsi/apple/Products/** diff --git a/example/.gitignore b/example/.gitignore index 42b97fb625..59ab056180 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -1,12 +1,9 @@ # XDE # .expo/ -/coverage +/dist -# Yarn -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions \ No newline at end of file +# Expo CNG / prebuild output: native projects are regenerated by +# `expo prebuild` (and `expo run:*`) from app.json + config plugins. +/android +/ios diff --git a/example/.nvmrc b/example/.nvmrc new file mode 120000 index 0000000000..3d48ffa461 --- /dev/null +++ b/example/.nvmrc @@ -0,0 +1 @@ +../.nvmrc \ No newline at end of file diff --git a/example/android/.gitignore b/example/android/.gitignore deleted file mode 100644 index 8a6be07718..0000000000 --- a/example/android/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -# OSX -# -.DS_Store - -# Android/IntelliJ -# -build/ -.idea -.gradle -local.properties -*.iml -*.hprof -.cxx/ - -# Bundle artifacts -*.jsbundle diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle deleted file mode 100644 index b563ea2eaf..0000000000 --- a/example/android/app/build.gradle +++ /dev/null @@ -1,176 +0,0 @@ -apply plugin: "com.android.application" -apply plugin: "org.jetbrains.kotlin.android" -apply plugin: "com.facebook.react" - -def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath() - -/** - * This is the configuration block to customize your React Native Android app. - * By default you don't need to apply any configuration, just uncomment the lines you need. - */ -react { - entryFile = file(["node", "-e", "require('expo/scripts/resolveAppEntry')", projectRoot, "android", "absolute"].execute(null, rootDir).text.trim()) - reactNativeDir = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() - hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc" - codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile() - - // Use Expo CLI to bundle the app, this ensures the Metro config - // works correctly with Expo projects. - cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim()) - bundleCommand = "export:embed" - - /* Folders */ - // The root of your project, i.e. where "package.json" lives. Default is '../..' - // root = file("../../") - // The folder where the react-native NPM package is. Default is ../../node_modules/react-native - // reactNativeDir = file("../../node_modules/react-native") - // The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen - // codegenDir = file("../../node_modules/@react-native/codegen") - - /* Variants */ - // The list of variants to that are debuggable. For those we're going to - // skip the bundling of the JS bundle and the assets. By default is just 'debug'. - // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. - // debuggableVariants = ["liteDebug", "prodDebug"] - - /* Bundling */ - // A list containing the node command and its flags. Default is just 'node'. - // nodeExecutableAndArgs = ["node"] - - // - // The path to the CLI configuration file. Default is empty. - // bundleConfig = file(../rn-cli.config.js) - // - // The name of the generated asset file containing your JS bundle - // bundleAssetName = "MyApplication.android.bundle" - // - // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' - // entryFile = file("../js/MyApplication.android.js") - // - // A list of extra flags to pass to the 'bundle' commands. - // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle - // extraPackagerArgs = [] - - /* Hermes Commands */ - // The hermes compiler command to run. By default it is 'hermesc' - // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" - // - // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" - // hermesFlags = ["-O", "-output-source-map"] - - /* Autolinking */ - autolinkLibrariesWithApp() -} - -/** - * Set this to true to Run Proguard on Release builds to minify the Java bytecode. - */ -def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean() - -/** - * The preferred build flavor of JavaScriptCore (JSC) - * - * For example, to use the international variant, you can use: - * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` - * - * The international variant includes ICU i18n library and necessary data - * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that - * give correct results when using with locales other than en-US. Note that - * this variant is about 6MiB larger per architecture than default. - */ -def jscFlavor = 'org.webkit:android-jsc:+' - -android { - ndkVersion rootProject.ext.ndkVersion - - buildToolsVersion rootProject.ext.buildToolsVersion - compileSdk rootProject.ext.compileSdkVersion - - namespace 'com.callstack.reactnativepaperexample' - defaultConfig { - applicationId 'com.callstack.reactnativepaperexample' - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 30 - versionName "3.16.0" - } - signingConfigs { - debug { - storeFile file('debug.keystore') - storePassword 'android' - keyAlias 'androiddebugkey' - keyPassword 'android' - } - } - buildTypes { - debug { - signingConfig signingConfigs.debug - } - release { - // Caution! In production, you need to generate your own keystore file. - // see https://reactnative.dev/docs/signed-apk-android. - signingConfig signingConfigs.debug - shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false) - minifyEnabled enableProguardInReleaseBuilds - proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" - crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true) - } - } - packagingOptions { - jniLibs { - useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false) - } - } - androidResources { - ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~' - } -} - -// Apply static values from `gradle.properties` to the `android.packagingOptions` -// Accepts values in comma delimited lists, example: -// android.packagingOptions.pickFirsts=/LICENSE,**/picasa.ini -["pickFirsts", "excludes", "merges", "doNotStrip"].each { prop -> - // Split option: 'foo,bar' -> ['foo', 'bar'] - def options = (findProperty("android.packagingOptions.$prop") ?: "").split(","); - // Trim all elements in place. - for (i in 0.. 0) { - println "android.packagingOptions.$prop += $options ($options.length)" - // Ex: android.packagingOptions.pickFirsts += '**/SCCS/**' - options.each { - android.packagingOptions[prop] += it - } - } -} - -dependencies { - // The version of react-native is set by the React Native Gradle Plugin - implementation("com.facebook.react:react-android") - - def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true"; - def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true"; - def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true"; - - if (isGifEnabled) { - // For animated gif support - implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}") - } - - if (isWebpEnabled) { - // For webp support - implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}") - if (isWebpAnimatedEnabled) { - // Animated webp support - implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}") - } - } - - if (hermesEnabled.toBoolean()) { - implementation("com.facebook.react:hermes-android") - } else { - implementation jscFlavor - } -} diff --git a/example/android/app/debug.keystore b/example/android/app/debug.keystore deleted file mode 100644 index 364e105ed3..0000000000 Binary files a/example/android/app/debug.keystore and /dev/null differ diff --git a/example/android/app/proguard-rules.pro b/example/android/app/proguard-rules.pro deleted file mode 100644 index 551eb41da2..0000000000 --- a/example/android/app/proguard-rules.pro +++ /dev/null @@ -1,14 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# react-native-reanimated --keep class com.swmansion.reanimated.** { *; } --keep class com.facebook.react.turbomodule.** { *; } - -# Add any project specific keep options here: diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml deleted file mode 100644 index 3ec2507bab..0000000000 --- a/example/android/app/src/debug/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 2e9f1dfc02..0000000000 --- a/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/example/android/app/src/main/java/com/callstack/reactnativepaperexample/MainActivity.kt b/example/android/app/src/main/java/com/callstack/reactnativepaperexample/MainActivity.kt deleted file mode 100644 index 9c5d6f5f65..0000000000 --- a/example/android/app/src/main/java/com/callstack/reactnativepaperexample/MainActivity.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.callstack.reactnativepaperexample -import expo.modules.splashscreen.SplashScreenManager - -import android.os.Build -import android.os.Bundle - -import com.facebook.react.ReactActivity -import com.facebook.react.ReactActivityDelegate -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled -import com.facebook.react.defaults.DefaultReactActivityDelegate - -import expo.modules.ReactActivityDelegateWrapper - -class MainActivity : ReactActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - // Set the theme to AppTheme BEFORE onCreate to support - // coloring the background, status bar, and navigation bar. - // This is required for expo-splash-screen. - // setTheme(R.style.AppTheme); - // @generated begin expo-splashscreen - expo prebuild (DO NOT MODIFY) sync-f3ff59a738c56c9a6119210cb55f0b613eb8b6af - SplashScreenManager.registerOnActivity(this) - // @generated end expo-splashscreen - super.onCreate(null) - } - - /** - * Returns the name of the main component registered from JavaScript. This is used to schedule - * rendering of the component. - */ - override fun getMainComponentName(): String = "main" - - /** - * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] - * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] - */ - override fun createReactActivityDelegate(): ReactActivityDelegate { - return ReactActivityDelegateWrapper( - this, - BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, - object : DefaultReactActivityDelegate( - this, - mainComponentName, - fabricEnabled - ){}) - } - - /** - * Align the back button behavior with Android S - * where moving root activities to background instead of finishing activities. - * @see onBackPressed - */ - override fun invokeDefaultOnBackPressed() { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { - if (!moveTaskToBack(false)) { - // For non-root activities, use the default implementation to finish them. - super.invokeDefaultOnBackPressed() - } - return - } - - // Use the default back button implementation on Android S - // because it's doing more than [Activity.moveTaskToBack] in fact. - super.invokeDefaultOnBackPressed() - } -} diff --git a/example/android/app/src/main/java/com/callstack/reactnativepaperexample/MainApplication.kt b/example/android/app/src/main/java/com/callstack/reactnativepaperexample/MainApplication.kt deleted file mode 100644 index b6cd6d88a9..0000000000 --- a/example/android/app/src/main/java/com/callstack/reactnativepaperexample/MainApplication.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.callstack.reactnativepaperexample - -import android.app.Application -import android.content.res.Configuration - -import com.facebook.react.PackageList -import com.facebook.react.ReactApplication -import com.facebook.react.ReactNativeHost -import com.facebook.react.ReactPackage -import com.facebook.react.ReactHost -import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load -import com.facebook.react.defaults.DefaultReactNativeHost -import com.facebook.react.soloader.OpenSourceMergedSoMapping -import com.facebook.soloader.SoLoader - -import expo.modules.ApplicationLifecycleDispatcher -import expo.modules.ReactNativeHostWrapper - -class MainApplication : Application(), ReactApplication { - - override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( - this, - object : DefaultReactNativeHost(this) { - override fun getPackages(): List { - val packages = PackageList(this).packages - // Packages that cannot be autolinked yet can be added manually here, for example: - // packages.add(new MyReactNativePackage()); - return packages - } - - override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry" - - override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG - - override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED - override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED - } - ) - - override val reactHost: ReactHost - get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost) - - override fun onCreate() { - super.onCreate() - SoLoader.init(this, OpenSourceMergedSoMapping) - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - // If you opted-in for the New Architecture, we load the native entry point for this app. - load() - } - ApplicationLifecycleDispatcher.onApplicationCreate(this) - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig) - } -} diff --git a/example/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png b/example/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png deleted file mode 100644 index d2e4ebb523..0000000000 Binary files a/example/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png and /dev/null differ diff --git a/example/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png b/example/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png deleted file mode 100644 index d2905ac97e..0000000000 Binary files a/example/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png and /dev/null differ diff --git a/example/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png b/example/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png deleted file mode 100644 index c2d00af737..0000000000 Binary files a/example/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png and /dev/null differ diff --git a/example/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png b/example/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png deleted file mode 100644 index 7979a98391..0000000000 Binary files a/example/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png and /dev/null differ diff --git a/example/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png b/example/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png deleted file mode 100644 index e8d6a6af53..0000000000 Binary files a/example/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png and /dev/null differ diff --git a/example/android/app/src/main/res/drawable/ic_launcher_background.xml b/example/android/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 883b2a080f..0000000000 --- a/example/android/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/example/android/app/src/main/res/drawable/rn_edit_text_material.xml b/example/android/app/src/main/res/drawable/rn_edit_text_material.xml deleted file mode 100644 index 5c25e728ea..0000000000 --- a/example/android/app/src/main/res/drawable/rn_edit_text_material.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - diff --git a/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 3941bea9b9..0000000000 --- a/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 3941bea9b9..0000000000 --- a/example/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index 2fff66b74c..0000000000 Binary files a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp deleted file mode 100644 index 0537194be8..0000000000 Binary files a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index c463eb2367..0000000000 Binary files a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index db69a94b18..0000000000 Binary files a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp deleted file mode 100644 index 2e3c35551d..0000000000 Binary files a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62d8439b72..0000000000 Binary files a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index a8210487da..0000000000 Binary files a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 5a10eeb1fc..0000000000 Binary files a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index c632bb43d7..0000000000 Binary files a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index a249833e21..0000000000 Binary files a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 47ca7d7f47..0000000000 Binary files a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 1c38eb6136..0000000000 Binary files a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index fba93f074f..0000000000 Binary files a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp deleted file mode 100644 index 1899f38acf..0000000000 Binary files a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp and /dev/null differ diff --git a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index c0dbf433c3..0000000000 Binary files a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/example/android/app/src/main/res/values-night/colors.xml b/example/android/app/src/main/res/values-night/colors.xml deleted file mode 100644 index 3c05de5be8..0000000000 --- a/example/android/app/src/main/res/values-night/colors.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/example/android/app/src/main/res/values/colors.xml b/example/android/app/src/main/res/values/colors.xml deleted file mode 100644 index 5ad704b32e..0000000000 --- a/example/android/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - #6200EE - #6200EE - #6200EE - #6200EE - \ No newline at end of file diff --git a/example/android/app/src/main/res/values/strings.xml b/example/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 076a2376b0..0000000000 --- a/example/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - React Native Paper Example - cover - false - exposdk:52.0.0 - \ No newline at end of file diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 7f305ebf13..0000000000 --- a/example/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - \ No newline at end of file diff --git a/example/android/build.gradle b/example/android/build.gradle deleted file mode 100644 index e119b72df9..0000000000 --- a/example/android/build.gradle +++ /dev/null @@ -1,41 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - ext { - buildToolsVersion = findProperty('android.buildToolsVersion') ?: '35.0.0' - minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '24') - compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '35') - targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '34') - kotlinVersion = findProperty('android.kotlinVersion') ?: '2.0.21' - - ndkVersion = "27.1.12297006" - } - repositories { - google() - mavenCentral() - } - dependencies { - classpath('com.android.tools.build:gradle') - classpath('com.facebook.react:react-native-gradle-plugin') - classpath('org.jetbrains.kotlin:kotlin-gradle-plugin') - } -} - -apply plugin: "com.facebook.react.rootproject" - -allprojects { - repositories { - maven { - // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm - url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android')) - } - maven { - // Android JSC is installed from npm - url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist')) - } - - google() - mavenCentral() - maven { url 'https://www.jitpack.io' } - } -} diff --git a/example/android/gradle.properties b/example/android/gradle.properties deleted file mode 100644 index 7531e9eb23..0000000000 --- a/example/android/gradle.properties +++ /dev/null @@ -1,56 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m -org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true - -# Enable AAPT2 PNG crunching -android.enablePngCrunchInReleaseBuilds=true - -# Use this property to specify which architecture you want to build. -# You can also override it from the CLI using -# ./gradlew -PreactNativeArchitectures=x86_64 -reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 - -# Use this property to enable support to the new architecture. -# This will allow you to use TurboModules and the Fabric render in -# your application. You should enable this flag either if you want -# to write custom TurboModules/Fabric components OR use libraries that -# are providing them. -newArchEnabled=true - -# Use this property to enable or disable the Hermes JS engine. -# If set to false, you will be using JSC instead. -hermesEnabled=true - -# Enable GIF support in React Native images (~200 B increase) -expo.gif.enabled=true -# Enable webp support in React Native images (~85 KB increase) -expo.webp.enabled=true -# Enable animated webp support (~3.4 MB increase) -# Disabled by default because iOS doesn't support animated webp -expo.webp.animated=false - -# Enable network inspector -EX_DEV_CLIENT_NETWORK_INSPECTOR=true - -# Use legacy packaging to compress native libraries in the resulting APK. -expo.useLegacyPackaging=false diff --git a/example/android/gradle/wrapper/gradle-wrapper.jar b/example/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index a4b76b9530..0000000000 Binary files a/example/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 79eb9d003f..0000000000 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/example/android/gradlew b/example/android/gradlew deleted file mode 100755 index f5feea6d6b..0000000000 --- a/example/android/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/example/android/gradlew.bat b/example/android/gradlew.bat deleted file mode 100644 index 9b42019c79..0000000000 --- a/example/android/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/example/android/settings.gradle b/example/android/settings.gradle deleted file mode 100644 index d0cca28108..0000000000 --- a/example/android/settings.gradle +++ /dev/null @@ -1,38 +0,0 @@ -pluginManagement { - includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile().toString()) -} -plugins { id("com.facebook.react.settings") } - -extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> - if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') { - ex.autolinkLibrariesFromCommand() - } else { - def command = [ - 'node', - '--no-warnings', - '--eval', - 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))', - 'react-native-config', - '--json', - '--platform', - 'android' - ].toList() - ex.autolinkLibrariesFromCommand(command) - } -} - -rootProject.name = 'React Native Paper Example' - -dependencyResolutionManagement { - versionCatalogs { - reactAndroidLibs { - from(files(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../gradle/libs.versions.toml"))) - } - } -} - -apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle"); -useExpoModules() - -include ':app' -includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile()) diff --git a/example/app.json b/example/app.json index 57c5d74b41..4dd835b98f 100644 --- a/example/app.json +++ b/example/app.json @@ -3,47 +3,56 @@ "name": "React Native Paper Example", "description": "Example app for React Native Paper: https://callstack.github.io/react-native-paper/", "slug": "react-native-paper-example", - "privacy": "public", "version": "3.16.0", "orientation": "default", "primaryColor": "#6200EE", "userInterfaceStyle": "automatic", "icon": "./assets/images/paper-icon.png", - "newArchEnabled": true, - "packagerOpts": { - "config": "./metro.config.js", - "projectRoots": "" - }, - "platforms": ["android", "ios", "web"], + "platforms": [ + "android", + "ios", + "web" + ], "owner": "react-native-paper", "ios": { "bundleIdentifier": "com.callstack.reactnativepaperexample", - "buildNumber": "26.0.5" + "infoPlist": { + "ITSAppUsesNonExemptEncryption": false + } }, "android": { "package": "com.callstack.reactnativepaperexample", - "versionCode": 38, - "permissions": ["READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"], + "permissions": [ + "READ_EXTERNAL_STORAGE", + "WRITE_EXTERNAL_STORAGE" + ], "icon": "./assets/images/paper-icon.png", "adaptiveIcon": { "foregroundImage": "./assets/images/android-icon.png", "backgroundColor": "#6200EE" } }, - "splash": { - "image": "./assets/images/splash.png", - "backgroundColor": "#6200EE", - "resizeMode": "cover" - }, "extra": { "eas": { "projectId": "ba8f5139-58fe-48f0-b2f2-2d675b6eb2d4" } }, - "runtimeVersion": "exposdk:52.0.0", + "runtimeVersion": { + "policy": "fingerprint" + }, "updates": { "url": "https://u.expo.dev/ba8f5139-58fe-48f0-b2f2-2d675b6eb2d4" }, - "plugins": ["expo-font"] + "plugins": [ + "expo-font", + [ + "expo-splash-screen", + { + "image": "./assets/images/splash.png", + "backgroundColor": "#6200EE", + "resizeMode": "cover" + } + ] + ] } } diff --git a/example/assets/styles/fonts.css b/example/assets/styles/fonts.css index 1b282d638f..6c6a8a2866 100644 --- a/example/assets/styles/fonts.css +++ b/example/assets/styles/fonts.css @@ -1,47 +1,37 @@ +/* + * Roboto is loaded via Google Fonts in public/index.html. + * These @font-face aliases map the legacy "Roboto-" family names + * used in the app code to the right weights of the loaded Roboto font. + */ + @font-face { font-family: 'Roboto-Thin'; font-style: normal; font-display: swap; font-weight: 100; - src: - local('Roboto Thin '), - local('Roboto-Thin'), - url('~typeface-roboto/files/roboto-latin-100.woff2') format('woff2'), /* Super Modern Browsers */ - url('~typeface-roboto/files/roboto-latin-100.woff') format('woff'); /* Modern Browsers */ + src: local('Roboto Thin'), local('Roboto-Thin'), local('Roboto'); } - + @font-face { font-family: 'Roboto-Light'; font-style: normal; font-display: swap; font-weight: 300; - src: - local('Roboto Light '), - local('Roboto-Light'), - url('~typeface-roboto/files/roboto-latin-300.woff2') format('woff2'), /* Super Modern Browsers */ - url('~typeface-roboto/files/roboto-latin-300.woff') format('woff'); /* Modern Browsers */ + src: local('Roboto Light'), local('Roboto-Light'), local('Roboto'); } - + @font-face { font-family: 'Roboto'; font-style: normal; font-display: swap; font-weight: 400; - src: - local('Roboto Regular '), - local('Roboto-Regular'), - url('~typeface-roboto/files/roboto-latin-400.woff2') format('woff2'), /* Super Modern Browsers */ - url('~typeface-roboto/files/roboto-latin-400.woff') format('woff'); /* Modern Browsers */ + src: local('Roboto Regular'), local('Roboto-Regular'), local('Roboto'); } - + @font-face { font-family: 'Roboto-Medium'; font-style: normal; font-display: swap; font-weight: 500; - src: - local('Roboto Medium '), - local('Roboto-Medium'), - url('~typeface-roboto/files/roboto-latin-500.woff2') format('woff2'), /* Super Modern Browsers */ - url('~typeface-roboto/files/roboto-latin-500.woff') format('woff'); /* Modern Browsers */ + src: local('Roboto Medium'), local('Roboto-Medium'), local('Roboto'); } \ No newline at end of file diff --git a/example/babel.config.js b/example/babel.config.js index 2908500a54..637f023a93 100644 --- a/example/babel.config.js +++ b/example/babel.config.js @@ -1,30 +1,8 @@ -const path = require('path'); - -const pak = require('../package.json'); - module.exports = function (api) { api.cache(true); return { presets: ['babel-preset-expo'], - plugins: [ - [ - 'module-resolver', - { - extensions: ['.tsx', '.ts', '.js', '.json'], - alias: { - [`${pak.name}/package.json`]: path.join( - __dirname, - '..', - 'package.json' - ), - // For development, we want to alias the library to the source - [pak.name]: path.join(__dirname, '..', 'src'), - }, - }, - ], - ['@babel/plugin-proposal-export-namespace-from'], - ['react-native-reanimated/plugin'], - ], + plugins: [['@babel/plugin-proposal-export-namespace-from']], }; }; diff --git a/example/eas.json b/example/eas.json new file mode 100644 index 0000000000..482e583d8e --- /dev/null +++ b/example/eas.json @@ -0,0 +1,34 @@ +{ + "cli": { + "appVersionSource": "remote" + }, + "build": { + "production": { + "autoIncrement": true, + "android": { + "buildType": "app-bundle" + }, + "channel": "production" + }, + "preview": { + "extends": "production", + "channel": "preview" + } + }, + "submit": { + "production": { + "android": { + "track": "production" + }, + "ios": { + "ascAppId": "1548934513" + } + }, + "preview": { + "extends": "production", + "android": { + "track": "internal" + } + } + } +} diff --git a/example/fingerprint.config.js b/example/fingerprint.config.js new file mode 100644 index 0000000000..501ccf5ba3 --- /dev/null +++ b/example/fingerprint.config.js @@ -0,0 +1,7 @@ +/** @type {import('expo/fingerprint').Config} */ +module.exports = { + sourceSkips: [ + 'ExpoConfigVersions', + 'PackageJsonAndroidAndIosScriptsIfNotContainRun', + ], +}; diff --git a/example/index.web.js b/example/index.web.js index 88adf8f637..e69c84e6dd 100644 --- a/example/index.web.js +++ b/example/index.web.js @@ -1,3 +1,7 @@ import './assets/styles/fonts.css'; -export { default } from './src/index'; +import { registerRootComponent } from 'expo'; + +import PaperExample from './src/index'; + +registerRootComponent(PaperExample); diff --git a/example/metro.config.js b/example/metro.config.js index 687d72ec22..b288ba7c49 100644 --- a/example/metro.config.js +++ b/example/metro.config.js @@ -1,9 +1,10 @@ const { getDefaultConfig } = require('@expo/metro-config'); -const path = require('path'); +const path = require('node:path'); const { withMetroConfig } = require('react-native-monorepo-config'); /** @type {import('metro-config').MetroConfig} */ module.exports = withMetroConfig(getDefaultConfig(__dirname), { root: path.resolve(__dirname, '..'), dirname: __dirname, + conditions: ['react-native-paper-source'], }); diff --git a/example/package.json b/example/package.json index 667c56cb19..8d32e2a364 100644 --- a/example/package.json +++ b/example/package.json @@ -6,49 +6,43 @@ "private": true, "scripts": { "eas-build-pre-install": "cd .. && yarn", - "start": "EXPO_NO_TYPESCRIPT_SETUP=1 expo start --go", - "android": "EXPO_NO_TYPESCRIPT_SETUP=1 expo start --android", - "android:native": "EXPO_NO_TYPESCRIPT_SETUP=1 expo run:android", - "ios:native": "EXPO_NO_TYPESCRIPT_SETUP=1 expo run:ios", - "ios": "EXPO_NO_TYPESCRIPT_SETUP=1 expo start --ios", - "web": "EXPO_NO_TYPESCRIPT_SETUP=1 expo start --web" + "start": "EXPO_NO_TYPESCRIPT_SETUP=1 expo start --dev-client", + "android": "EXPO_NO_TYPESCRIPT_SETUP=1 expo run:android", + "ios": "EXPO_NO_TYPESCRIPT_SETUP=1 expo run:ios" }, "dependencies": { - "@expo/vector-icons": "^15.0.2", - "@expo/webpack-config": "~19.0.1", - "@pchmn/expo-material3-theme": "^1.3.2", + "@expo/metro-config": "~56.0.0", "@react-native-async-storage/async-storage": "2.2.0", "@react-native-masked-view/masked-view": "0.3.2", - "@react-navigation/bottom-tabs": "^7.3.10", - "@react-navigation/drawer": "^7.3.9", - "@react-navigation/native": "^7.1.6", - "@react-navigation/stack": "^7.2.10", - "expo": "^54.0.0", - "expo-crypto": "~15.0.7", - "expo-dev-client": "~6.0.14", - "expo-font": "~14.0.9", - "expo-keep-awake": "~15.0.7", - "expo-splash-screen": "~31.0.10", - "expo-status-bar": "~3.0.8", - "expo-updates": "~29.0.12", - "file-loader": "^6.2.0", - "react": "19.1.0", - "react-dom": "19.1.0", - "react-native": "0.81.4", - "react-native-gesture-handler": "~2.28.0", - "react-native-monorepo-config": "^0.1.6", - "react-native-reanimated": "~4.1.1", - "react-native-safe-area-context": "~5.6.0", - "react-native-screens": "~4.16.0", + "@react-navigation/bottom-tabs": "^8.0.0-alpha.33", + "@react-navigation/drawer": "^8.0.0-alpha.34", + "@react-navigation/elements": "3.0.0-alpha.31", + "@react-navigation/native": "^8.0.0-alpha.28", + "@react-navigation/native-stack": "^8.0.0-alpha.34", + "expo": "~56.0.0", + "expo-constants": "~56.0.18", + "expo-crypto": "~56.0.3", + "expo-dev-client": "~56.0.13", + "expo-font": "~56.0.5", + "expo-keep-awake": "~56.0.3", + "expo-splash-screen": "~56.0.9", + "expo-status-bar": "~56.0.4", + "expo-updates": "~56.0.14", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-native": "0.85.3", + "react-native-gesture-handler": "~2.31.1", + "react-native-reanimated": "4.3.1", + "react-native-safe-area-context": "~5.7.0", + "react-native-screens": "4.25.1", "react-native-web": "^0.21.0", - "react-native-worklets": "0.5.1", - "typeface-roboto": "^1.1.13" + "react-native-worklets": "0.8.3" }, "devDependencies": { - "@babel/core": "^7.25.2", - "babel-plugin-module-resolver": "^5.0.0", - "babel-preset-expo": "~54.0.0", - "url-loader": "^4.1.1" + "@babel/core": "^7.29.0", + "babel-preset-expo": "~56.0.0", + "react-native-builder-bob": "^0.42.1", + "react-native-monorepo-config": "^0.4.0" }, "engines": { "node": ">=20" diff --git a/example/public/index.html b/example/public/index.html index 62cc885b40..f1ef43f74d 100644 --- a/example/public/index.html +++ b/example/public/index.html @@ -1,11 +1,19 @@ + - - - + + App + + + + +