├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── actions │ └── setup │ │ └── action.yml └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── .watchmanconfig ├── .yarnrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets └── example.png ├── babel.config.js ├── biome.json ├── example ├── App.js ├── app.json ├── assets │ ├── adaptive-icon.png │ ├── favicon.png │ ├── icon.png │ └── splash.png ├── babel.config.js ├── metro.config.js ├── package.json ├── src │ └── App.tsx ├── tsconfig.json ├── webpack.config.js └── yarn.lock ├── lefthook.yml ├── package.json ├── renovate.json ├── scripts └── bootstrap.js ├── src ├── index.ts ├── lib │ ├── CodeHighlighter.tsx │ └── __tests__ │ │ ├── CodeHighlighter.spec.tsx │ │ └── __snapshots__ │ │ └── CodeHighlighter.spec.tsx.snap └── utils │ └── styles.ts ├── tsconfig.build.json ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | # specific for windows script files 3 | *.bat text eol=crlf -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [gmsgowtham] 4 | # patreon: # Replace with a single Patreon username 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # otechie: # Replace with a single Otechie username 12 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://www.buymeacoffee.com/gmsgowtham'] 14 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Setup Node.js and install dependencies 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup Node.js 8 | uses: actions/setup-node@v4 9 | with: 10 | node-version-file: .nvmrc 11 | 12 | - name: Cache dependencies 13 | id: yarn-cache 14 | uses: actions/cache@v4 15 | with: 16 | path: | 17 | **/node_modules 18 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 19 | restore-keys: | 20 | ${{ runner.os }}-yarn- 21 | 22 | - name: Install dependencies 23 | if: steps.yarn-cache.outputs.cache-hit != 'true' 24 | run: | 25 | yarn install --cwd example --frozen-lockfile 26 | yarn install --frozen-lockfile 27 | shell: bash 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup 18 | uses: ./.github/actions/setup 19 | 20 | - name: Lint files 21 | run: yarn lint 22 | 23 | - name: Typecheck files 24 | run: yarn typecheck 25 | 26 | test: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | 32 | - name: Setup 33 | uses: ./.github/actions/setup 34 | 35 | - name: Run unit tests 36 | run: yarn test --maxWorkers=2 --coverage 37 | 38 | - name: Coveralls 39 | uses: coverallsapp/github-action@master 40 | with: 41 | github-token: ${{ secrets.GITHUB_TOKEN }} 42 | 43 | build: 44 | runs-on: ubuntu-latest 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v4 48 | 49 | - name: Setup 50 | uses: ./.github/actions/setup 51 | 52 | - name: Build package 53 | run: yarn build 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # XDE 6 | .expo/ 7 | 8 | # VSCode 9 | .vscode/ 10 | jsconfig.json 11 | 12 | # Xcode 13 | # 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | 32 | # Android/IJ 33 | # 34 | .classpath 35 | .cxx 36 | .gradle 37 | .idea 38 | .project 39 | .settings 40 | local.properties 41 | android.iml 42 | 43 | # Cocoapods 44 | # 45 | example/ios/Pods 46 | 47 | # Ruby 48 | example/vendor/ 49 | 50 | # node.js 51 | # 52 | node_modules/ 53 | npm-debug.log 54 | yarn-debug.log 55 | yarn-error.log 56 | 57 | # BUCK 58 | buck-out/ 59 | \.buckd/ 60 | android/app/libs 61 | android/keystores/debug.keystore 62 | 63 | # Expo 64 | .expo/ 65 | 66 | # Turborepo 67 | .turbo/ 68 | 69 | # generated by bob 70 | dist/ 71 | coverage/ 72 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.14.0 2 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | # Override Yarn command so we can automatically setup the repo on running `yarn` 2 | 3 | yarn-path "scripts/bootstrap.js" 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | webappsbygowtham@gmail.com. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are always welcome, no matter how large or small! 4 | 5 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md). 6 | 7 | ## Development workflow 8 | 9 | To get started with the project, run `yarn` in the root directory to install the required dependencies for each package: 10 | 11 | ```sh 12 | yarn 13 | ``` 14 | 15 | > While it's possible to use [`npm`](https://github.com/npm/cli), the tooling is built around [`yarn`](https://classic.yarnpkg.com/), so you'll have an easier time if you use `yarn` for development. 16 | 17 | While developing, you can run the [example app](/example/) to test your changes. Any changes you make in your library's JavaScript code will be reflected in the example app without a rebuild. If you change any native code, then you'll need to rebuild the example app. 18 | 19 | To start the packager: 20 | 21 | ```sh 22 | yarn example start 23 | ``` 24 | 25 | To run the example app on Android: 26 | 27 | ```sh 28 | yarn example android 29 | ``` 30 | 31 | To run the example app on iOS: 32 | 33 | ```sh 34 | yarn example ios 35 | ``` 36 | 37 | To run the example app on Web: 38 | 39 | ```sh 40 | yarn example web 41 | ``` 42 | 43 | Make sure your code passes TypeScript and Lint. Run the following to verify: 44 | 45 | ```sh 46 | yarn typecheck 47 | yarn lint 48 | ``` 49 | 50 | To fix formatting errors, run the following: 51 | 52 | ```sh 53 | yarn lint --fix 54 | ``` 55 | 56 | Remember to add tests for your change if possible. Run the unit tests by: 57 | 58 | ```sh 59 | yarn test 60 | ``` 61 | 62 | 63 | ### Commit message convention 64 | 65 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages: 66 | 67 | - `fix`: bug fixes, e.g. fix crash due to deprecated method. 68 | - `feat`: new features, e.g. add new method to the module. 69 | - `refactor`: code refactor, e.g. migrate from class components to hooks. 70 | - `docs`: changes into documentation, e.g. add usage example for the module.. 71 | - `test`: adding or updating tests, e.g. add integration tests using detox. 72 | - `chore`: tooling changes, e.g. change CI config. 73 | 74 | Our pre-commit hooks verify that your commit message matches this format when committing. 75 | 76 | ### Linting and tests 77 | 78 | [Biome](https://biomejs.dev), [TypeScript](https://www.typescriptlang.org/) 79 | 80 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [Biome](https://biomejs.dev/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing. 81 | 82 | Our pre-commit hooks verify that the linter and tests pass when committing. 83 | 84 | ### Publishing to npm 85 | 86 | We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc. 87 | 88 | To publish new versions, run the following: 89 | 90 | ```sh 91 | yarn release 92 | ``` 93 | 94 | ### Scripts 95 | 96 | The `package.json` file contains various scripts for common tasks: 97 | 98 | - `yarn bootstrap`: setup project by installing all dependencies and pods. 99 | - `yarn typecheck`: type-check files with TypeScript. 100 | - `yarn lint`: lint files with Biome. 101 | - `yarn test`: run unit tests with Jest. 102 | - `yarn example start`: start the Metro server for the example app. 103 | - `yarn example android`: run the example app on Android. 104 | - `yarn example ios`: run the example app on iOS. 105 | 106 | ### Sending a pull request 107 | 108 | > **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://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github). 109 | 110 | When you're sending a pull request: 111 | 112 | - Prefer small pull requests focused on one change. 113 | - Verify that linters and tests are passing. 114 | - Review the documentation to make sure it looks good. 115 | - Follow the pull request template when opening a pull request. 116 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue. 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Gowtham G 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-code-highlighter 2 | 3 | ![GitHub](https://img.shields.io/github/license/gmsgowtham/react-native-code-highlighter) 4 | [![CI](https://github.com/gmsgowtham/react-native-code-highlighter/actions/workflows/ci.yml/badge.svg)](https://github.com/gmsgowtham/react-native-code-highlighter/actions/workflows/ci.yml) 5 | [![Coverage Status](https://coveralls.io/repos/github/gmsgowtham/react-native-code-highlighter/badge.svg?branch=main)](https://coveralls.io/github/gmsgowtham/react-native-code-highlighter?branch=main) 6 | [![npm](https://img.shields.io/npm/v/react-native-code-highlighter)](https://www.npmjs.com/package/react-native-code-highlighter) 7 | [![npm](https://img.shields.io/npm/dw/react-native-code-highlighter)](https://www.npmjs.com/package/react-native-code-highlighter) 8 | 9 | 10 | Code/Syntax highlighter for React Native. Inspired by [react-native-syntax-highlighter](https://github.com/conorhastings/react-native-syntax-highlighter), using [react-syntax-highlighter](https://github.com/react-syntax-highlighter/react-syntax-highlighter) 11 | 12 | ## Installation 13 | 14 | #### NPM 15 | 16 | ```sh 17 | npm install react-native-code-highlighter react-syntax-highlighter 18 | ``` 19 | 20 | #### Yarn 21 | 22 | ```sh 23 | yarn add react-native-code-highlighter react-syntax-highlighter 24 | ``` 25 | 26 | > Additional for typescript 27 | 28 | #### NPM 29 | 30 | ```sh 31 | npm install --save-dev @types/react-syntax-highlighter 32 | ``` 33 | 34 | #### Yarn 35 | 36 | ```sh 37 | yarn add -D @types/react-syntax-highlighter 38 | ``` 39 | 40 | ## Usage 41 | 42 | ### Props 43 | 44 | | Prop | Description | Type | Optional | 45 | | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | -------- | 46 | | hljsStyle | Highlight.js style imported from `react-syntax-highlighter/dist/esm/styles/hljs` | `{ [key: string]: React.CSSProperties }` | false | 47 | | textStyle | Style for the text text components. Note: `color` property will be overridden | `StyleProp` | true | 48 | | scrollViewProps | Props for the underlying scroll view. `horizontal` is ignored | [ScrollViewProps](https://reactnative.dev/docs/scrollview#props) | true | 49 | | containerStyle | Deprecated. `containerStyle` for the underlying `ScrollView`. Use `scrollViewProps.contentContainerStyle` instead | `StyleProp` | true | 50 | | [react-syntax-highlighter](https://github.com/react-syntax-highlighter/react-syntax-highlighter) Props | Props supported by react-syntax-highlighter. i.e. `language` | | | 51 | 52 | ### Example 53 | 54 | ```tsx 55 | import React from "react"; 56 | import { StyleSheet } from "react-native"; 57 | import CodeHighlighter from "react-native-code-highlighter"; 58 | import { atomOneDarkReasonable } from "react-syntax-highlighter/dist/esm/styles/hljs"; 59 | 60 | const CODE_STR = `var hello = "world"`; 61 | 62 | export default function HighlightComponent() { 63 | return ( 64 | 70 | {CODE_STR} 71 | 72 | ); 73 | } 74 | 75 | const styles = StyleSheet.create({ 76 | codeContainer: { 77 | padding: 16, 78 | minWidth: "100%", 79 | }, 80 | text: { 81 | fontSize: 16, 82 | }, 83 | }); 84 | ``` 85 | 86 | > CodeSandbox: https://codesandbox.io/s/react-native-code-highligher-knfsyx?file=/src/App.js 87 | 88 | ## Screenshots 89 | 90 | ![Image](assets/example.png?raw=true "Image") 91 | 92 | ## Contributing 93 | 94 | See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. 95 | 96 | ## License 97 | 98 | MIT 99 | 100 | --- 101 | 102 | ## Built using 103 | 104 | - [create-react-native-library](https://github.com/callstack/react-native-builder-bob) 105 | - [react-syntax-highlighter](https://github.com/react-syntax-highlighter/react-syntax-highlighter) 106 | - [trim-newlines](https://github.com/sindresorhus/trim-newlines) 107 | - [css-to-react-native](https://github.com/styled-components/css-to-react-native) 108 | 109 | 110 | Buy Me A Coffee 111 | -------------------------------------------------------------------------------- /assets/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmsgowtham/react-native-code-highlighter/cc9ab63f14b826048022572946b47f45e3de083d/assets/example.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["module:metro-react-native-babel-preset"], 3 | }; 4 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.3.1/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "recommended": true 10 | } 11 | }, 12 | "formatter": { 13 | "enabled": true 14 | }, 15 | "files": { 16 | "ignore": [ 17 | "dist", 18 | "node_modules", 19 | "example/node_modules", 20 | "example/.expo", 21 | "coverage" 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/App.js: -------------------------------------------------------------------------------- 1 | export { default } from "./src/App"; 2 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "example", 4 | "slug": "example", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": ["**/*"], 15 | "ios": { 16 | "supportsTablet": true 17 | }, 18 | "android": { 19 | "adaptiveIcon": { 20 | "foregroundImage": "./assets/adaptive-icon.png", 21 | "backgroundColor": "#ffffff" 22 | } 23 | }, 24 | "web": { 25 | "favicon": "./assets/favicon.png" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmsgowtham/react-native-code-highlighter/cc9ab63f14b826048022572946b47f45e3de083d/example/assets/adaptive-icon.png -------------------------------------------------------------------------------- /example/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmsgowtham/react-native-code-highlighter/cc9ab63f14b826048022572946b47f45e3de083d/example/assets/favicon.png -------------------------------------------------------------------------------- /example/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmsgowtham/react-native-code-highlighter/cc9ab63f14b826048022572946b47f45e3de083d/example/assets/icon.png -------------------------------------------------------------------------------- /example/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gmsgowtham/react-native-code-highlighter/cc9ab63f14b826048022572946b47f45e3de083d/example/assets/splash.png -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const pak = require("../package.json"); 3 | 4 | module.exports = function (api) { 5 | api.cache(true); 6 | 7 | return { 8 | presets: ["babel-preset-expo"], 9 | plugins: [ 10 | [ 11 | "module-resolver", 12 | { 13 | extensions: [".tsx", ".ts", ".js", ".json"], 14 | alias: { 15 | // For development, we want to alias the library to the source 16 | [pak.name]: path.join(__dirname, "..", pak.source), 17 | }, 18 | }, 19 | ], 20 | ], 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const escapeStringRegExp = require("escape-string-regexp"); 3 | const { getDefaultConfig } = require("@expo/metro-config"); 4 | const exclusionList = require("metro-config/src/defaults/exclusionList"); 5 | const pak = require("../package.json"); 6 | 7 | const root = path.resolve(__dirname, ".."); 8 | 9 | const modules = Object.keys({ 10 | ...pak.peerDependencies, 11 | }); 12 | 13 | const defaultConfig = getDefaultConfig(__dirname); 14 | 15 | module.exports = { 16 | ...defaultConfig, 17 | 18 | projectRoot: __dirname, 19 | watchFolders: [root], 20 | 21 | // We need to make sure that only one version is loaded for peerDependencies 22 | // So we block them at the root, and alias them to the versions in example's node_modules 23 | resolver: { 24 | ...defaultConfig.resolver, 25 | 26 | blacklistRE: exclusionList( 27 | modules.map( 28 | (m) => 29 | new RegExp( 30 | `^${escapeStringRegExp(path.join(root, "node_modules", m))}\\/.*$`, 31 | ), 32 | ), 33 | ), 34 | 35 | extraNodeModules: modules.reduce((acc, name) => { 36 | acc[name] = path.join(__dirname, "node_modules", name); 37 | return acc; 38 | }, {}), 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "expo": "~48.0.15", 13 | "expo-status-bar": "~1.4.4", 14 | "react": "18.2.0", 15 | "react-dom": "18.2.0", 16 | "react-native": "0.71.8", 17 | "react-native-safe-area-context": "4.5.0", 18 | "react-native-web": "~0.18.10", 19 | "react-syntax-highlighter": "15.5.0", 20 | "webpack": "5.94.0" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "7.22.1", 24 | "@babel/preset-env": "7.22.4", 25 | "@expo/webpack-config": "18.0.1", 26 | "@types/react": "18.2.8", 27 | "@types/react-native": "0.72.2", 28 | "@types/react-syntax-highlighter": "15.5.7", 29 | "babel-loader": "9.0.0", 30 | "babel-plugin-module-resolver": "5.0.0", 31 | "react-test-renderer": "18.2.0" 32 | }, 33 | "private": true 34 | } 35 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ScrollView, StyleSheet, Text } from "react-native"; 3 | import CodeHighlighter from "react-native-code-highlighter"; 4 | import { SafeAreaView } from "react-native-safe-area-context"; 5 | import { atomOneDarkReasonable as hljsStyle } from "react-syntax-highlighter/dist/esm/styles/hljs"; 6 | 7 | const CODE_STR = ` 8 | import React from 'react'; 9 | import {SectionList, StyleSheet, Text, View} from 'react-native'; 10 | 11 | const styles = StyleSheet.create({ 12 | container: { 13 | flex: 1, 14 | paddingTop: 22, 15 | }, 16 | sectionHeader: { 17 | paddingTop: 2, 18 | paddingLeft: 10, 19 | paddingRight: 10, 20 | paddingBottom: 2, 21 | fontSize: 14, 22 | fontWeight: 'bold', 23 | backgroundColor: 'rgba(247,247,247,1.0)', 24 | }, 25 | item: { 26 | padding: 10, 27 | fontSize: 18, 28 | height: 44, 29 | }, 30 | }); 31 | 32 | const SectionListBasics = () => { 33 | return ( 34 | 35 | {item}} 52 | renderSectionHeader={({section}) => ( 53 | {section.title} 54 | )} 55 | keyExtractor={item => \`basicListEntry-$\{item\}\`} 56 | /> 57 | 58 | ); 59 | }; 60 | 61 | export default SectionListBasics; 62 | `; 63 | 64 | export default function App() { 65 | return ( 66 | 67 | Example from https://reactnative.dev/docs/using-a-listview 68 | 69 | 78 | {CODE_STR} 79 | 80 | 81 | 82 | ); 83 | } 84 | 85 | const styles = StyleSheet.create({ 86 | codeContainer: { 87 | paddingHorizontal: 16, 88 | }, 89 | text: { 90 | fontSize: 16, 91 | }, 92 | }); 93 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "compilerOptions": { 4 | // Avoid expo-cli auto-generating a tsconfig 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const createExpoWebpackConfigAsync = require("@expo/webpack-config"); 3 | const { resolver } = require("./metro.config"); 4 | 5 | const root = path.resolve(__dirname, ".."); 6 | const node_modules = path.join(__dirname, "node_modules"); 7 | 8 | module.exports = async function (env, argv) { 9 | const config = await createExpoWebpackConfigAsync(env, argv); 10 | 11 | config.module.rules.push({ 12 | test: /\.(js|jsx|ts|tsx)$/, 13 | include: path.resolve(root, "src"), 14 | use: "babel-loader", 15 | }); 16 | 17 | // We need to make sure that only one version is loaded for peerDependencies 18 | // So we alias them to the versions in example's node_modules 19 | Object.assign(config.resolve.alias, { 20 | ...resolver.extraNodeModules, 21 | "react-native-web": path.join(node_modules, "react-native-web"), 22 | }); 23 | 24 | return config; 25 | }; 26 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | parallel: true 3 | commands: 4 | lint: 5 | files: git diff --name-only @{push} 6 | glob: "*.{js,ts,jsx,tsx}" 7 | run: yarn run biome check {staged_files} 8 | format: 9 | files: git diff --name-only @{push} 10 | glob: "*.{js,ts,jsx,tsx}" 11 | run: yarn run biome format {staged_files} --write && git add {staged_files} 12 | types: 13 | files: git diff --name-only @{push} 14 | glob: "*.{js,ts,jsx, tsx}" 15 | run: npx tsc --noEmit 16 | commit-msg: 17 | parallel: true 18 | commands: 19 | commitlint: 20 | run: npx commitlint --edit 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-code-highlighter", 3 | "version": "1.2.3", 4 | "description": "test", 5 | "main": "dist/commonjs/index", 6 | "module": "dist/module/index", 7 | "types": "dist/typescript/index.d.ts", 8 | "react-native": "src/index", 9 | "source": "src/index", 10 | "files": [ 11 | "src", 12 | "dist", 13 | "android", 14 | "ios", 15 | "cpp", 16 | "*.podspec", 17 | "!dist/typescript/example", 18 | "!ios/build", 19 | "!android/build", 20 | "!android/gradle", 21 | "!android/gradlew", 22 | "!android/gradlew.bat", 23 | "!android/local.properties", 24 | "!**/__tests__", 25 | "!**/__fixtures__", 26 | "!**/__mocks__", 27 | "!**/.*" 28 | ], 29 | "scripts": { 30 | "test": "jest", 31 | "typecheck": "tsc --noEmit --project tsconfig.build.json", 32 | "lint": "biome check ./ --apply-unsafe", 33 | "format": "biome format ./ --write", 34 | "prepare": "yarn build", 35 | "build": "bob build", 36 | "release": "yarn build && release-it", 37 | "example": "yarn --cwd example", 38 | "bootstrap": "yarn example && yarn install" 39 | }, 40 | "keywords": [ 41 | "react-native", 42 | "ios", 43 | "android", 44 | "web", 45 | "code-hightlighter", 46 | "syntax-highlighter" 47 | ], 48 | "repository": "https://github.com/gmsgowtham/react-native-code-highlighter", 49 | "author": "Gowtham G (https://github.com/gmsgowtham)", 50 | "license": "MIT", 51 | "bugs": { 52 | "url": "https://github.com/gmsgowtham/react-native-code-highlighter/issues" 53 | }, 54 | "homepage": "https://github.com/gmsgowtham/react-native-code-highlighter#readme", 55 | "publishConfig": { 56 | "registry": "https://registry.npmjs.org/" 57 | }, 58 | "devDependencies": { 59 | "@babel/core": "7.26.10", 60 | "@babel/preset-env": "7.26.9", 61 | "@biomejs/biome": "1.9.4", 62 | "@commitlint/config-conventional": "19.8.0", 63 | "@evilmartians/lefthook": "1.7.18", 64 | "@release-it/conventional-changelog": "10.0.0", 65 | "@testing-library/react-native": "12.7.2", 66 | "@types/jest": "29.5.14", 67 | "@types/node": "22.14.0", 68 | "@types/react": "18.3.20", 69 | "@types/react-native": "0.72.8", 70 | "@types/react-syntax-highlighter": "15.5.13", 71 | "@types/react-test-renderer": "18.3.1", 72 | "commitlint": "19.8.0", 73 | "cosmiconfig": "9.0.0", 74 | "del-cli": "6.0.0", 75 | "jest": "29.7.0", 76 | "metro-react-native-babel-preset": "0.77.0", 77 | "pod-install": "0.3.4", 78 | "react": "18.3.1", 79 | "react-native": "0.73.4", 80 | "react-native-builder-bob": "0.23.2", 81 | "react-test-renderer": "18.3.1", 82 | "release-it": "18.1.2", 83 | "typescript": "5.8.2" 84 | }, 85 | "resolutions": { 86 | "@types/react": "18.3.20" 87 | }, 88 | "peerDependencies": { 89 | "react": "16.8.6 || ^17.0.0 || ^18.0.0", 90 | "react-native": ">=0.60.0", 91 | "react-syntax-highlighter": ">=15.5.0" 92 | }, 93 | "engines": { 94 | "node": ">= 16.0.0" 95 | }, 96 | "packageManager": "yarn@1.22.22", 97 | "jest": { 98 | "preset": "react-native", 99 | "modulePathIgnorePatterns": [ 100 | "/example/node_modules", 101 | "/dist/" 102 | ], 103 | "transformIgnorePatterns": [ 104 | "node_modules/(?!(jest-)?react-native|@react-native|@react-native-community|react-native-table-component|trim-newlines|react-syntax-highlighter)" 105 | ] 106 | }, 107 | "commitlint": { 108 | "extends": [ 109 | "@commitlint/config-conventional" 110 | ] 111 | }, 112 | "release-it": { 113 | "git": { 114 | "commitMessage": "chore: release ${version}", 115 | "tagName": "v${version}" 116 | }, 117 | "npm": { 118 | "publish": true 119 | }, 120 | "github": { 121 | "release": true 122 | }, 123 | "plugins": { 124 | "@release-it/conventional-changelog": { 125 | "preset": "angular", 126 | "ignoreRecommendedBump": true 127 | } 128 | } 129 | }, 130 | "react-native-builder-bob": { 131 | "source": "src", 132 | "output": "dist", 133 | "targets": [ 134 | "commonjs", 135 | "module", 136 | [ 137 | "typescript", 138 | { 139 | "project": "tsconfig.build.json" 140 | } 141 | ] 142 | ] 143 | }, 144 | "dependencies": { 145 | "css-to-react-native": "3.2.0", 146 | "react-syntax-highlighter": "15.6.1", 147 | "trim-newlines": "5.0.0" 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "ignorePaths": ["**/node_modules/**", "**/example/**"] 5 | } 6 | -------------------------------------------------------------------------------- /scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const os = require("os"); 2 | const path = require("path"); 3 | const child_process = require("child_process"); 4 | 5 | const root = path.resolve(__dirname, ".."); 6 | const args = process.argv.slice(2); 7 | const options = { 8 | cwd: process.cwd(), 9 | env: process.env, 10 | stdio: "inherit", 11 | encoding: "utf-8", 12 | }; 13 | 14 | if (os.type() === "Windows_NT") { 15 | options.shell = true; 16 | } 17 | 18 | let result; 19 | 20 | if (process.cwd() !== root || args.length) { 21 | // We're not in the root of the project, or additional arguments were passed 22 | // In this case, forward the command to `yarn` 23 | result = child_process.spawnSync("yarn", args, options); 24 | } else { 25 | // If `yarn` is run without arguments, perform bootstrap 26 | result = child_process.spawnSync("yarn", ["bootstrap"], options); 27 | } 28 | 29 | process.exitCode = result.status; 30 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import CodeHighlighter, { 2 | type CodeHighlighterProps, 3 | } from "./lib/CodeHighlighter"; 4 | 5 | export type { CodeHighlighterProps }; 6 | 7 | export default CodeHighlighter; 8 | -------------------------------------------------------------------------------- /src/lib/CodeHighlighter.tsx: -------------------------------------------------------------------------------- 1 | import React, { type FunctionComponent, type ReactNode, useMemo } from "react"; 2 | import { 3 | ScrollView, 4 | type ScrollViewProps, 5 | type StyleProp, 6 | StyleSheet, 7 | Text, 8 | type TextStyle, 9 | View, 10 | type ViewStyle, 11 | } from "react-native"; 12 | import SyntaxHighlighter, { 13 | type SyntaxHighlighterProps, 14 | } from "react-syntax-highlighter"; 15 | import { trimNewlines } from "trim-newlines"; 16 | import { 17 | type HighlighterStyleSheet, 18 | type ReactStyle, 19 | getRNStylesFromHljsStyle, 20 | } from "./../utils/styles"; 21 | 22 | export interface CodeHighlighterProps extends SyntaxHighlighterProps { 23 | hljsStyle: ReactStyle; 24 | textStyle?: StyleProp; 25 | scrollViewProps?: ScrollViewProps; 26 | /** 27 | * @deprecated Use scrollViewProps.contentContainerStyle instead 28 | */ 29 | containerStyle?: StyleProp; 30 | } 31 | 32 | export const CodeHighlighter: FunctionComponent = ({ 33 | children, 34 | textStyle, 35 | hljsStyle, 36 | scrollViewProps, 37 | containerStyle, 38 | ...rest 39 | }) => { 40 | const stylesheet: HighlighterStyleSheet = useMemo( 41 | () => getRNStylesFromHljsStyle(hljsStyle), 42 | [hljsStyle], 43 | ); 44 | 45 | const getStylesForNode = (node: rendererNode): TextStyle[] => { 46 | const classes: string[] = node.properties?.className ?? []; 47 | return classes 48 | .map((c: string) => stylesheet[c]) 49 | .filter((c) => !!c) as TextStyle[]; 50 | }; 51 | 52 | const renderNode = (nodes: rendererNode[], keyPrefix = "row") => 53 | nodes.reduce((acc, node, index) => { 54 | const keyPrefixWithIndex = `${keyPrefix}_${index}`; 55 | if (node.children) { 56 | const styles = StyleSheet.flatten([ 57 | textStyle, 58 | { color: stylesheet.hljs?.color }, 59 | getStylesForNode(node), 60 | ]); 61 | acc.push( 62 | 63 | {renderNode(node.children, `${keyPrefixWithIndex}_child`)} 64 | , 65 | ); 66 | } 67 | 68 | if (node.value) { 69 | acc.push(trimNewlines(String(node.value))); 70 | } 71 | 72 | return acc; 73 | }, []); 74 | 75 | const renderer = (props: rendererProps) => { 76 | const { rows } = props; 77 | return ( 78 | 87 | true}>{renderNode(rows)} 88 | 89 | ); 90 | }; 91 | 92 | return ( 93 | 101 | {children} 102 | 103 | ); 104 | }; 105 | 106 | export default CodeHighlighter; 107 | -------------------------------------------------------------------------------- /src/lib/__tests__/CodeHighlighter.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from "@testing-library/react-native"; 2 | import { atomOneDarkReasonable as hljsStyle } from "react-syntax-highlighter/dist/esm/styles/hljs"; 3 | import CodeHighlighter from "../CodeHighlighter"; 4 | 5 | describe(CodeHighlighter, () => { 6 | it("render", async () => { 7 | const code = `const hello = "world"`; 8 | const r = render( 9 | 10 | {code} 11 | , 12 | ); 13 | 14 | expect(screen.queryByTestId("react-native-code-highlighter")).toBeDefined(); 15 | expect(screen.getByText("const")).toBeDefined(); 16 | expect(screen.getByText("hello =")).toBeDefined(); 17 | expect(screen.getByText('"world"')).toBeDefined(); 18 | 19 | const tree = r.toJSON(); 20 | expect(tree).toMatchSnapshot(); 21 | }); 22 | it("render with styles", async () => { 23 | const code = ` 24 | const hello = "world"; 25 | let foo = "bar"; 26 | `; 27 | const r = render( 28 | 36 | {code} 37 | , 38 | ); 39 | 40 | expect(screen.queryByTestId("react-native-code-highlighter")).toBeDefined(); 41 | expect(screen.getByText("const")).toBeDefined(); 42 | expect(screen.getByText("hello =")).toBeDefined(); 43 | expect(screen.getByText('"world"')).toBeDefined(); 44 | expect(screen.getByText("let")).toBeDefined(); 45 | expect(screen.getByText("foo =")).toBeDefined(); 46 | expect(screen.getByText('"bar"')).toBeDefined(); 47 | 48 | const tree = r.toJSON(); 49 | expect(tree).toMatchSnapshot(); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /src/lib/__tests__/__snapshots__/CodeHighlighter.spec.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`CodeHighlighter render 1`] = ` 4 | 12 | 20 | 33 | 34 | 37 | 44 | 51 | const 52 | 53 | 60 | hello = 61 | 62 | 69 | "world" 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | `; 78 | 79 | exports[`CodeHighlighter render with styles 1`] = ` 80 | 88 | 96 | 112 | 113 | 116 | 124 | 132 | 133 | 141 | 149 | 150 | 151 | 159 | const 160 | 161 | 169 | hello = 170 | 171 | 179 | "world" 180 | 181 | 189 | ; 190 | 191 | 192 | 200 | 208 | 209 | 210 | 218 | let 219 | 220 | 228 | foo = 229 | 230 | 238 | "bar" 239 | 240 | 248 | ; 249 | 250 | 251 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | `; 267 | -------------------------------------------------------------------------------- /src/utils/styles.ts: -------------------------------------------------------------------------------- 1 | import transform, { type StyleTuple } from "css-to-react-native"; 2 | import type { CSSProperties } from "react"; 3 | import type { TextStyle } from "react-native"; 4 | 5 | export type HighlighterStyleSheet = { [key: string]: TextStyle }; 6 | export type ReactStyle = Record; 7 | 8 | const ALLOWED_STYLE_PROPERTIES: Record = { 9 | color: true, 10 | background: true, 11 | backgroundColor: true, 12 | fontWeight: true, 13 | fontStyle: true, 14 | }; 15 | 16 | export const getRNStylesFromHljsStyle = ( 17 | hljsStyle: ReactStyle, 18 | ): HighlighterStyleSheet => { 19 | return Object.fromEntries( 20 | Object.entries(hljsStyle).map(([className, style]) => [ 21 | className, 22 | cleanStyle(style), 23 | ]), 24 | ); 25 | }; 26 | 27 | export const cleanStyle = (style: CSSProperties) => { 28 | const styles = Object.entries(style) 29 | .filter(([key]) => ALLOWED_STYLE_PROPERTIES[key]) 30 | .map(([key, value]) => [key, value]); 31 | 32 | return transform(styles); 33 | }; 34 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "exclude": ["example", "src/**/**/__tests__", "src/**/__tests__"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "react-native-code-highlighter": ["./src/index"] 6 | }, 7 | "allowUnreachableCode": false, 8 | "allowUnusedLabels": false, 9 | "esModuleInterop": true, 10 | "verbatimModuleSyntax": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "jsx": "react-native", 13 | "lib": ["esnext"], 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "noFallthroughCasesInSwitch": true, 17 | "noImplicitReturns": true, 18 | "noImplicitUseStrict": false, 19 | "noStrictGenericChecks": false, 20 | "noUncheckedIndexedAccess": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "resolveJsonModule": true, 24 | "skipLibCheck": true, 25 | "strict": true, 26 | "target": "esnext", 27 | "allowSyntheticDefaultImports": true 28 | } 29 | } 30 | --------------------------------------------------------------------------------