├── .babelrc ├── .eslintrc ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .npmignore ├── .prettierrc.js ├── README.md ├── demo ├── demo.gif ├── example.png └── logo.svg ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── blog │ ├── 2019-05-28-hola.md │ ├── 2019-05-29-hello-world.md │ └── 2019-05-30-welcome.md ├── docs │ ├── 1-introduction.md │ ├── 2-getting-started.md │ ├── 3-customization.md │ ├── 4-custom-component.md │ ├── 5-examples.md │ └── mdx.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ └── styles.module.css ├── static │ ├── .nojekyll │ └── img │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── logo.svg │ │ ├── logo_old.svg │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ ├── mstile-70x70.png │ │ ├── safari-pinned-tab.svg │ │ ├── site.webmanifest │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg └── yarn.lock ├── package-lock.json ├── package.json ├── src ├── components │ ├── SafeAreaView.tsx │ ├── Text.tsx │ ├── TextInput.tsx │ ├── TouchableOpacity.tsx │ └── View.tsx ├── core │ ├── createThemeProvider.tsx │ ├── createTheming.ts │ ├── createWithTheme.tsx │ └── theming.ts ├── hoc │ └── createPicassoComponent.tsx ├── index.ts ├── styles │ └── defaultTheme.ts ├── test.tsx └── util │ ├── classname-helpers.ts │ ├── constants.ts │ └── style-helpers.ts ├── tsconfig.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["module:metro-react-native-babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "airbnb-base", 4 | "plugin:prettier/recommended", 5 | "plugin:react/recommended", 6 | "plugin:@typescript-eslint/recommended" 7 | ], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "ecmaFeatures": { 11 | "jsx": true 12 | }, 13 | "useJSXTextNode": true, 14 | "project": "./tsconfig.json", 15 | "tsconfigRootDir": "." 16 | }, 17 | "env": { 18 | "react-native/react-native": true 19 | }, 20 | "rules": { 21 | "react-hooks/rules-of-hooks": "error", 22 | "react-hooks/exhaustive-deps": "warn", 23 | "react-native/no-unused-styles": 2, 24 | "react-native/split-platform-components": 2, 25 | "react-native/no-inline-styles": 2, 26 | "react-native/no-color-literals": 0, 27 | "react-native/no-raw-text": 2, 28 | 29 | "semi": ["warn", "never"], 30 | "import/no-extraneous-dependencies": [ 31 | "error", 32 | { 33 | "devDependencies": true 34 | } 35 | ], 36 | "quotes": ["warn", "single"], 37 | "no-restricted-syntax": 0, 38 | "no-await-in-loop": 0, 39 | "object-curly-newline": 0, 40 | "no-constant-condition": 2, 41 | "react/jsx-filename-extension": [ 42 | 1, 43 | { 44 | "extensions": [".js", ".jsx"] 45 | } 46 | ], 47 | "react/state-in-constructor": [2, "never"], 48 | "no-mixed-operators": 0, 49 | "react/forbid-prop-types": 0, 50 | "react/no-unused-prop-types": 2, 51 | "jsx-a11y/media-has-caption": 0, 52 | "no-console": ["error", { "allow": ["warn", "error"] }], 53 | "no-underscore-dangle": 0, 54 | "no-global-assign": 0, 55 | "prefer-const": [ 56 | "error", 57 | { 58 | "destructuring": "any", 59 | "ignoreReadBeforeAssign": false 60 | } 61 | ], 62 | "import/prefer-default-export": 0, 63 | "import/no-named-as-default": 0, 64 | "jsx-a11y/no-static-element-interactions": 0, 65 | "jsx-a11y/click-events-have-key-events": 0, 66 | "max-lines": [ 67 | "error", 68 | { 69 | "max": 500, 70 | "skipBlankLines": true, 71 | "skipComments": true 72 | } 73 | ], 74 | "max-len": [ 75 | "error", 76 | 100, 77 | { 78 | "ignoreComments": true, 79 | "ignoreTemplateLiterals": true, 80 | "ignoreStrings": true 81 | } 82 | ], 83 | "curly": 0, 84 | "consistent-return": 0, 85 | "arrow-parens": 0, 86 | "react/no-array-index-key": 0, 87 | "no-return-assign": 0, 88 | "comma-dangle": 0, 89 | "jsx-a11y/href-no-hash": "off", 90 | "jsx-a11y/anchor-is-valid": 0, 91 | "no-multi-str": 0, 92 | "newline-before-return": 2, 93 | "newline-after-var": 2, 94 | "newline-per-chained-call": 2, 95 | "import/newline-after-import": 2, 96 | "react/jsx-props-no-spreading": 0, 97 | "react/require-default-props": 0, 98 | "no-loops/no-loops": 0 99 | }, 100 | "plugins": [ 101 | "react-native", 102 | "prettier", 103 | "no-loops", 104 | "react-hooks", 105 | "plugin:@typescript-eslint/recommended" 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # ******** NOTE ******** 12 | 13 | name: "CodeQL" 14 | 15 | on: 16 | push: 17 | branches: [ master ] 18 | pull_request: 19 | # The branches below must be a subset of the branches above 20 | branches: [ master ] 21 | schedule: 22 | - cron: '23 5 * * 2' 23 | 24 | jobs: 25 | analyze: 26 | name: Analyze 27 | runs-on: ubuntu-latest 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | language: [ 'javascript' ] 33 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 34 | # Learn more: 35 | # https://docs.github.com/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 36 | 37 | steps: 38 | - name: Checkout repository 39 | uses: actions/checkout@v2 40 | 41 | # Initializes the CodeQL tools for scanning. 42 | - name: Initialize CodeQL 43 | uses: github/codeql-action/init@v1 44 | with: 45 | languages: ${{ matrix.language }} 46 | # If you wish to specify custom queries, you can do so here or in a config file. 47 | # By default, queries listed here will override any specified in a config file. 48 | # Prefix the list here with "+" to use these queries and those in the config file. 49 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 50 | 51 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 52 | # If this step fails, then you should remove it and run the build manually (see below) 53 | - name: Autobuild 54 | uses: github/codeql-action/autobuild@v1 55 | 56 | # ℹ️ Command-line programs to run using the OS shell. 57 | # 📚 https://git.io/JvXDl 58 | 59 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 60 | # and modify them (or add more) to build your code if your project 61 | # uses a compiled language 62 | 63 | #- run: | 64 | # make bootstrap 65 | # make release 66 | 67 | - name: Perform CodeQL Analysis 68 | uses: github/codeql-action/analyze@v1 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log 4 | 5 | # Runtime data 6 | tmp 7 | build 8 | dist 9 | 10 | # Dependency directory 11 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log 4 | 5 | # Dependency directory 6 | node_modules 7 | 8 | # Runtime data 9 | tmp 10 | 11 | # Examples (If applicable to your project) 12 | examples -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | semi: false, 4 | trailingComma: 'all', 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-picasso 2 | 3 | This is **NOT** yet another components library ! 4 | 5 |
6 |
7 |

8 | 9 |

10 |
11 |
12 | 13 | [![npm](https://img.shields.io/npm/dm/react-native-picasso.svg?style=for-the-badge)](https://www.npmjs.com/package/react-native-picasso) 14 | [![npm](https://img.shields.io/npm/l/react-native-picasso.svg?style=for-the-badge)](https://www.npmjs.com/package/react-native-picasso) 15 | 16 | ![demonstration](demo/demo.gif) 17 | 18 | ## Motivation 19 | 20 | Picasso is a toolkit to help you create layouts for your React Native apps in a clear and declarative way. 21 | No more ugly inline styles or duplicate stylesheets. You can now use classNames as you would do on the web. 22 | 23 | Having a strong design system helps you to focus on functionnality instead of design fine tuning and endless replacements. 24 | 25 | This will give your app a strong single source of truth. You declare your design system once and then consume it in each of your screens. 26 | 27 | This was heavily inspired by Bootstrap 28 | 29 | ## Installation 30 | 31 | ```bash 32 | yarn add react-native-picasso 33 | ``` 34 | 35 | or with npm 36 | 37 | ```bash 38 | npm i --save react-native-picasso 39 | ``` 40 | 41 | ## Documentation 42 | 43 | Full documentation can be found here : [https://meienberger.github.io/react-native-picasso/](https://meienberger.github.io/react-native-picasso/) 44 | 45 | ## Usage 46 | 47 | Picasso ships with a set of drop in replacements components. You can import the following three components : View, SafeAreaView, Text 48 | 49 | ```js 50 | import { View, Text, SafeAreaView } from 'react-native-picasso' 51 | ``` 52 | 53 | All those components are sharing the same API as their usual react-native counter party. But they have an aditionnal prop called `className`. The className property consists of a string of pre-defined classes separated by a space. 54 | 55 | Classes are constructed the following way : `{property}-{value}` 56 | 57 | Using this string, you can describe the apparence of your components. 58 | 59 | For example the following `className="ml-md p-lg"` would be translated to `margin-left: medium; padding: large`. 60 | 61 | All the values are declared in a defaultTheme variable. Which is : 62 | 63 | ```js 64 | { 65 | colors: { 66 | background: '#eeeeee', 67 | primary: '#00B386', 68 | secondary: '#dedede', 69 | }, 70 | textColors: { 71 | primary: '#333333', 72 | secondary: '#666666', 73 | white: '#ffffff', 74 | }, 75 | font: { 76 | family: 'Helvetica', 77 | sizes: { 78 | sm: 12, 79 | md: 16, 80 | lg: 24, 81 | xl: 32, 82 | xxl: 40, 83 | }, 84 | weights: { 85 | light: '100', 86 | normal: 'normal', 87 | bold: 'bold', 88 | extrabold: '900' 89 | }, 90 | }, 91 | elevated: { 92 | shadowColor: '#000000', 93 | shadowOffset: { width: 0, height: 5 }, 94 | shadowOpacity: 0.2, 95 | shadowRadius: 3, 96 | elevation: 5, 97 | }, 98 | radius: { 99 | sm: 5, 100 | md: 10, 101 | lg: 20, 102 | xl: 40, 103 | round: 1000, 104 | }, 105 | spacing: { 106 | sm: 8, 107 | md: 16, 108 | lg: 24, 109 | xl: 32, 110 | xxl: 40, 111 | }, 112 | } 113 | ``` 114 | 115 | If we take the previous example, margin and padding are using the "spacing" values. So the final result of `className="ml-md p-lg"` would be : 116 | 117 | ```js 118 | { 119 | marginLeft: 16, 120 | padding: 24 121 | } 122 | ``` 123 | 124 | Here is a table of all the possible properties and values you can use inside a className. 125 | 126 | ### Base (Available for all components) 127 | 128 | | Property | Possible values | Description | 129 | | --------- | ------------------------------------- | --------------------- | 130 | | p | sm, md, lg, xl, xxl | **padding** | 131 | | pl | sm, md, lg, xl, xxl | **paddingLeft** | 132 | | pr | sm, md, lg, xl, xxl | **paddingRight** | 133 | | pt | sm, md, lg, xl, xxl | **paddingTop** | 134 | | pb | sm, md, lg, xl, xxl | **paddingBottom** | 135 | | px | sm, md, lg, xl, xxl | **paddingHorizontal** | 136 | | py | sm, md, lg, xl, xxl | **paddingVertical** | 137 | | -------- | ------------------- | --------------------- | 138 | | m | sm, md, lg, xl, xxl | **margin** | 139 | | ml | sm, md, lg, xl, xxl | **marginLeft** | 140 | | mr | sm, md, lg, xl, xxl | **marginRight** | 141 | | mt | sm, md, lg, xl, xxl | **marginTop** | 142 | | mb | sm, md, lg, xl, xxl | **marginBottom** | 143 | | mx | sm, md, lg, xl, xxl | **marginHorizontal** | 144 | | my | sm, md, lg, xl, xxl | **marginVertical** | 145 | | -------- | ------------------- | --------------------- | 146 | | flex | row, column | **flexDirection** | 147 | | flex | _any number_ | **flex: value** | 148 | | alignself | center, start, end, stretch, baseline | **alignSelf** | 149 | 150 | ### View specific 151 | 152 | | Property | Possible values | Description | 153 | | -------------- | ------------------------------------------- | ----------------------------------- | 154 | | elevated | _no value_ | **Adds a drop shadow to your view** | 155 | | radius | sm, md, lg, xl | **borderRadius** | 156 | | radiustl | sm, md, lg, xl | **borderTopLeftRadius** | 157 | | radiustr | sm, md, lg, xl | **borderTopRightRadius** | 158 | | radiusbl | sm, md, lg, xl | **borderBottomLeftRadius** | 159 | | radiusbr | sm, md, lg, xl | **borderBottomRightRadius** | 160 | | bg | primary, secondary, background | **backgroundColor** | 161 | | justifycontent | center, start, end, around, between, evenly | **justifyContent** | 162 | | alignitems | center, start, end, stretch, baseline | **alignItems** | 163 | | bordercolor | primary, secondary, background, border | **borderColor** | 164 | | b | _any number_ | **borderWidth** | 165 | | br | _any number_ | **borderRightWidth** | 166 | | bl | _any number_ | **borderLeftWidth** | 167 | | bt | _any number_ | **borderTopWidth** | 168 | | bb | _any number_ | **borderBottomWidth** | 169 | 170 | ### Text specific 171 | 172 | | Property | Possible values | Description | 173 | | -------- | ------------------------------ | -------------- | 174 | | weight | light, normal, bold, extrabold | **fontWeight** | 175 | | align | center, left, right, justify | **textAlign** | 176 | | color | primary, secondary, white | **color** | 177 | | size | sm, md, lg, xl, xxl | **fontSize** | 178 | 179 | Each value of each property is mapped to a specific section of the theme. 180 | 181 | ## Examples 182 | 183 | ```js 184 | {/* Content */} 185 | ``` 186 | 187 | Translates into 188 | 189 | ```js 190 | 199 | {/* Content */} 200 | 201 | ``` 202 | 203 | ## Customizing the theme 204 | 205 | Picasso ships with a default theme. But you most probably won't use the default one and want to use your own colors, sizes and values. 206 | 207 | This can be achieved by using the `ThemeProvider` component. What you need to do is to simply import the theme provider and wrap your whole app into it. Then you can pass a custom `theme` variable to override specific parts of the default theme. 208 | 209 | For example in your App.js file : 210 | 211 | ```js 212 | import React from 'react' 213 | import { ThemeProvider } from 'react-native-picasso' 214 | 215 | const theme = { 216 | colors: { 217 | primary: '#00B386', 218 | secondary: '#dedede', 219 | }, 220 | textColors: { 221 | primary: '#333333', 222 | }, 223 | spacing: { 224 | sm: 8, 225 | md: 16, 226 | lg: 24, 227 | xl: 32, 228 | xxl: 40, 229 | }, 230 | } 231 | 232 | const App = () => { 233 | return {/* Rest of your app */} 234 | } 235 | 236 | export default App 237 | ``` 238 | 239 | The ThemeProvider will look at your custom theme and apply those values to the default theme. This way, you can only override specific aspects of the theme and keep the default values for the rest. 240 | 241 | Be careful to follow the original structure of the theme. Otherwise your styles may break. 242 | 243 | ## Merging with classic styles 244 | 245 | Sometimes, the classes available are not sufficient for your specific need. In order to give you enough flexibility with the components, you can still use the default `style` property in combination with picasso classes. 246 | 247 | ```js 248 | 249 | ``` 250 | 251 | ## Creating your own Picasso component 252 | 253 | If you want to add all the Picasso utility classes to any component, you can do so by using the `createPicassoComponent` HOC. Simply wrap your component inside the HOC and start using the className prop. 254 | 255 | ```js 256 | import React from 'react' 257 | import { Image } from 'react-native' 258 | import { createPicassoComponent } from 'react-native-picasso' 259 | 260 | const PicassoImage = createPicassoComponent(Image) 261 | 262 | const App = () => { 263 | return ( 264 | <> 265 | 266 | 267 | ) 268 | } 269 | 270 | export default App 271 | ``` 272 | 273 | ## Adding your own classes 274 | 275 | You can add your own values to use as classes. For example if you wanted to add a complex style for a card. You just need to add a new property at the root of your custom theme. And then you can simply use the class anywhere in your app on a Picasso component. 276 | 277 | ```js 278 | import React from 'react' 279 | import { ThemeProvider, View, Text } from 'react-native-picasso' 280 | 281 | const theme = { 282 | card: { 283 | backgroundColor: '#dedede', 284 | shadowColor: '#000000', 285 | shadowOffset: { width: 0, height: 5 }, 286 | shadowOpacity: 0.2, 287 | shadowRadius: 3, 288 | elevation: 5, 289 | alignItems: 'center', 290 | justifyContenr: 'center', 291 | borderRadius: 20, 292 | }, 293 | } 294 | 295 | const App = () => { 296 | return ( 297 | 298 | 299 | Hello from card 300 | 301 | 302 | ) 303 | } 304 | 305 | export default App 306 | ``` 307 | 308 | You can also add custom values to existing properties and all the properties which are using this specific set of values will have access to it. For example : 309 | 310 | ```js 311 | import React from 'react' 312 | import { ThemeProvider, View, Text } from 'react-native-picasso' 313 | 314 | const theme = { 315 | colors: { 316 | mycolor: '#00B386', 317 | }, 318 | textColors: { 319 | supercolor: '#452eff', 320 | }, 321 | radius: { 322 | insane: 100, 323 | }, 324 | } 325 | 326 | const App = () => { 327 | return ( 328 | 329 | 330 | Hello from custom color 331 | 332 | 333 | ) 334 | } 335 | 336 | export default App 337 | ``` 338 | 339 | ## Acknowledgements 340 | 341 | - [@callstack/react-theme-provider](@callstack/react-theme-provider) : For inspiration on how to create the ThemeProvider 342 | - [Bootstrap](https://getbootstrap.com/) : For the original idea 343 | 344 | ## TODO 345 | 346 | - [ ] TypeScript 347 | -------------------------------------------------------------------------------- /demo/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/demo/demo.gif -------------------------------------------------------------------------------- /demo/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/demo/example.png -------------------------------------------------------------------------------- /demo/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | 12 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Environment files 12 | .env 13 | 14 | # Misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | Full documentation can be found at [https://meienberger.github.io/react-native-picasso/](https://meienberger.github.io/react-native-picasso/) 4 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/blog/2019-05-28-hola.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hola 3 | title: Hola 4 | author: Gao Wei 5 | author_title: Docusaurus Core Team 6 | author_url: https://github.com/wgao19 7 | author_image_url: https://avatars1.githubusercontent.com/u/2055384?v=4 8 | tags: [hola, docusaurus] 9 | --- 10 | 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 12 | -------------------------------------------------------------------------------- /docs/blog/2019-05-29-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hello-world 3 | title: Hello 4 | author: Endilie Yacop Sucipto 5 | author_title: Maintainer of Docusaurus 6 | author_url: https://github.com/endiliey 7 | author_image_url: https://avatars1.githubusercontent.com/u/17883920?s=460&v=4 8 | tags: [hello, docusaurus] 9 | --- 10 | 11 | Welcome to this blog. This blog is created with [**Docusaurus 2 alpha**](https://v2.docusaurus.io/). 12 | 13 | 14 | 15 | This is a test post. 16 | 17 | A whole bunch of other information. 18 | -------------------------------------------------------------------------------- /docs/blog/2019-05-30-welcome.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome 3 | title: Welcome 4 | author: Yangshun Tay 5 | author_title: Front End Engineer @ Facebook 6 | author_url: https://github.com/yangshun 7 | author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4 8 | tags: [facebook, hello, docusaurus] 9 | --- 10 | 11 | Blog features are powered by the blog plugin. Simply add files to the `blog` directory. It supports tags as well! 12 | 13 | Delete the whole directory if you don't want the blog features. As simple as that! 14 | -------------------------------------------------------------------------------- /docs/docs/1-introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: introduction 3 | title: Introduction 4 | sidebar_label: Introduction 5 | slug: / 6 | --- 7 | 8 | ![demonstration](https://github.com/meienberger/react-native-picasso/raw/master/demo/demo.gif) 9 | 10 | Heavily inspired by Bootstrap, React Native Picasso is a toolkit to help you create layouts for your React Native apps in a clear and declarative way. 11 | No more ugly inline styles or duplicate stylesheets. You can now use classNames as you would do on the web. 12 | 13 | Having a strong design system helps you to focus on functionnality instead of design fine tuning and endless replacements. 14 | 15 | This will give your app a strong single source of truth. You declare your design system once and then consume it in each of your screens. 16 | -------------------------------------------------------------------------------- /docs/docs/2-getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: getting-started 3 | title: Getting started 4 | sidebar_label: Getting started 5 | slug: /getting-started 6 | --- 7 | 8 | import Tabs from '@theme/Tabs'; 9 | import TabItem from '@theme/TabItem'; 10 | 11 | What follows within the Fundamentals section of this documentation is a tour of the most important aspects of React Native Picasso. It should cover enough for you to know how to build your own design system, and give you the background that you need to dive deeper into the more advanced parts of Picasso. 12 | 13 | # Installation 14 | 15 | Install the required packages in your React Native project: 16 | 17 | 23 | 24 | 25 | ```bash 26 | yarn add react-native-picasso 27 | ``` 28 | 29 | 30 | 31 | 32 | ```bash 33 | npm install react-native-picasso 34 | ``` 35 | 36 | 37 | 38 | 39 | # Usage 40 | 41 | Picasso ships with a set of drop in replacements components. You can import the following three components : View, SafeAreaView, Text 42 | 43 | ```jsx 44 | import { View, Text, SafeAreaView } from 'react-native-picasso' 45 | ``` 46 | 47 | All those components are sharing the same API as their usual react-native counter party. But they have an aditionnal prop called `className`. The className property consists of a string of pre-defined classes separated by a space. 48 | 49 | Classes are constructed the following way : `{property}-{value}` 50 | 51 | Using this string, you can describe the apparence of your components. 52 | 53 | For example the following `className="ml-md p-lg"` would be translated to `margin-left: medium; padding: large`. 54 | 55 | All the values are declared in a defaultTheme variable. Which is : 56 | 57 | ```js 58 | { 59 | colors: { 60 | background: '#eeeeee', 61 | primary: '#00B386', 62 | secondary: '#dedede', 63 | }, 64 | textColors: { 65 | primary: '#333333', 66 | secondary: '#666666', 67 | white: '#ffffff', 68 | }, 69 | font: { 70 | family: 'Helvetica', 71 | sizes: { 72 | sm: 12, 73 | md: 16, 74 | lg: 24, 75 | xl: 32, 76 | xxl: 40, 77 | }, 78 | weights: { 79 | light: '100', 80 | normal: 'normal', 81 | bold: 'bold', 82 | extrabold: '800', 83 | }, 84 | }, 85 | elevated: { 86 | shadowColor: '#000000', 87 | shadowOffset: { width: 0, height: 5 }, 88 | shadowOpacity: 0.2, 89 | shadowRadius: 3, 90 | elevation: 5, 91 | }, 92 | radius: { 93 | sm: 5, 94 | md: 10, 95 | lg: 20, 96 | xl: 40, 97 | round: 1000, 98 | }, 99 | spacing: { 100 | sm: 8, 101 | md: 16, 102 | lg: 24, 103 | xl: 32, 104 | xxl: 40, 105 | }, 106 | } 107 | ``` 108 | 109 | If we take the previous example, margin and padding are using the "spacing" values. So the final result of `className="ml-md p-lg"` would be : 110 | 111 | ```js 112 | { 113 | marginLeft: 16, 114 | padding: 24 115 | } 116 | ``` 117 | 118 | Here is a table of all the possible properties and values you can use inside a className. 119 | 120 | ## Base (Available for all components) 121 | 122 | | Property | Possible values | Description | 123 | | --------- | ------------------------------------- | --------------------- | 124 | | p | sm, md, lg, xl, xxl | **padding** | 125 | | pl | sm, md, lg, xl, xxl | **paddingLeft** | 126 | | pr | sm, md, lg, xl, xxl | **paddingRight** | 127 | | pt | sm, md, lg, xl, xxl | **paddingTop** | 128 | | pb | sm, md, lg, xl, xxl | **paddingBottom** | 129 | | px | sm, md, lg, xl, xxl | **paddingHorizontal** | 130 | | py | sm, md, lg, xl, xxl | **paddingVertical** | 131 | | -------- | ------------------- | --------------------- | 132 | | m | sm, md, lg, xl, xxl | **margin** | 133 | | ml | sm, md, lg, xl, xxl | **marginLeft** | 134 | | mr | sm, md, lg, xl, xxl | **marginRight** | 135 | | mt | sm, md, lg, xl, xxl | **marginTop** | 136 | | mb | sm, md, lg, xl, xxl | **marginBottom** | 137 | | mx | sm, md, lg, xl, xxl | **marginHorizontal** | 138 | | my | sm, md, lg, xl, xxl | **marginVertical** | 139 | | -------- | ------------------- | --------------------- | 140 | | flex | row, column | **flexDirection** | 141 | | flex | _any number_ | **flex: value** | 142 | | alignself | center, start, end, stretch, baseline | **alignSelf** | 143 | 144 | ## View specific 145 | 146 | | Property | Possible values | Description | 147 | | -------------- | ------------------------------------------- | ----------------------------------- | 148 | | elevated | _no value_ | **Adds a drop shadow to your view** | 149 | | radius | sm, md, lg, xl | **borderRadius** | 150 | | radiustl | sm, md, lg, xl | **borderTopLeftRadius** | 151 | | radiustr | sm, md, lg, xl | **borderTopRightRadius** | 152 | | radiusbl | sm, md, lg, xl | **borderBottomLeftRadius** | 153 | | radiusbr | sm, md, lg, xl | **borderBottomRightRadius** | 154 | | bg | primary, secondary, background | **backgroundColor** | 155 | | justifycontent | center, start, end, around, between, evenly | **justifyContent** | 156 | | alignitems | center, start, end, stretch, baseline | **alignItems** | 157 | | bordercolor | primary, secondary, background, border | **borderColor** | 158 | | b | _any number_ | **borderWidth** | 159 | | br | _any number_ | **borderRightWidth** | 160 | | bl | _any number_ | **borderLeftWidth** | 161 | | bt | _any number_ | **borderTopWidth** | 162 | | bb | _any number_ | **borderBottomWidth** | 163 | 164 | ## Text specific 165 | 166 | | Property | Possible values | Description | 167 | | -------- | ------------------------------ | -------------- | 168 | | weight | light, normal, bold, extrabold | **fontWeight** | 169 | | align | center, left, right, justify | **textAlign** | 170 | | color | primary, secondary, white | **color** | 171 | | size | sm, md, lg, xl, xxl | **fontSize** | 172 | 173 | Each value of each property is mapped to a specific section of the theme. 174 | 175 | ## Examples 176 | 177 | ```js 178 | {/* Content */} 179 | ``` 180 | 181 | Translates into 182 | 183 | ```js 184 | 193 | {/* Content */} 194 | 195 | ``` 196 | -------------------------------------------------------------------------------- /docs/docs/3-customization.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: customization 3 | title: Customizing the theme 4 | sidebar_label: Customization 5 | slug: /customization 6 | --- 7 | 8 | Picasso ships with a default theme. But you most probably won't use the default one and want to use your own colors, sizes and values. 9 | 10 | This can be achieved by using the `ThemeProvider` component. What you need to do is to simply import the theme provider and wrap your whole app into it. Then you can pass a custom `theme` variable to override specific parts of the default theme. 11 | 12 | For example in your App.js file : 13 | 14 | ```jsx 15 | import React from 'react' 16 | import { ThemeProvider } from 'react-native-picasso' 17 | 18 | const theme = { 19 | colors: { 20 | primary: '#00B386', 21 | secondary: '#dedede', 22 | }, 23 | textColors: { 24 | primary: '#333333', 25 | }, 26 | spacing: { 27 | sm: 8, 28 | md: 16, 29 | lg: 24, 30 | xl: 32, 31 | xxl: 40, 32 | }, 33 | } 34 | 35 | const App = () => { 36 | return {/* Rest of your app */} 37 | } 38 | 39 | export default App 40 | ``` 41 | 42 | The ThemeProvider will look at your custom theme and apply those values to the default theme. This way, you can only override specific aspects of the theme and keep the default values for the rest. 43 | 44 | Be careful to follow the original structure of the theme. Otherwise your styles may break. 45 | 46 | # Adding your own classes 47 | 48 | You can add your own values to use as classes. For example if you wanted to add a complex style for a card. You just need to add a new property at the root of your custom theme. And then you can simply use the class anywhere in your app on a Picasso component. 49 | 50 | ```jsx 51 | import React from 'react' 52 | import { ThemeProvider, View, Text } from 'react-native-picasso' 53 | 54 | const theme = { 55 | card: { 56 | backgroundColor: '#dedede', 57 | shadowColor: '#000000', 58 | shadowOffset: { width: 0, height: 5 }, 59 | shadowOpacity: 0.2, 60 | shadowRadius: 3, 61 | elevation: 5, 62 | alignItems: 'center', 63 | justifyContenr: 'center', 64 | borderRadius: 20, 65 | }, 66 | } 67 | 68 | const App = () => { 69 | return ( 70 | 71 | 72 | Hello from card 73 | 74 | 75 | ) 76 | } 77 | 78 | export default App 79 | ``` 80 | 81 | You can also add custom values to existing properties and all the properties which are using this specific set of values will have access to it. For example : 82 | 83 | ```jsx 84 | import React from 'react' 85 | import { ThemeProvider, View, Text } from 'react-native-picasso' 86 | 87 | const theme = { 88 | colors: { 89 | mycolor: '#00B386', 90 | }, 91 | textColors: { 92 | supercolor: '#452eff', 93 | }, 94 | radius: { 95 | insane: 100, 96 | }, 97 | } 98 | 99 | const App = () => { 100 | return ( 101 | 102 | 103 | Hello from custom color 104 | 105 | 106 | ) 107 | } 108 | 109 | export default App 110 | ``` 111 | 112 | # Merging with classic styles 113 | 114 | Sometimes, the classes available in the theme are not sufficient for your specific need. In order to give you enough flexibility with the components, you can still use the default `style` property in combination with picasso classes. 115 | 116 | ```js 117 | 118 | ``` 119 | -------------------------------------------------------------------------------- /docs/docs/4-custom-component.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: custom-components 3 | title: Custom components 4 | sidebar_label: Custom components 5 | slug: /custom-component 6 | --- 7 | 8 | If you want to add all the Picasso utility classes to any component, you can do so by using the `createPicassoComponent` HOC. Simply wrap your component inside the HOC and start using the className prop. 9 | 10 | ```jsx 11 | import React from 'react' 12 | import { Image } from 'react-native' 13 | import { createPicassoComponent } from 'react-native-picasso' 14 | 15 | const PicassoImage = createPicassoComponent(Image) 16 | 17 | const App = () => { 18 | return ( 19 | <> 20 | 21 | 22 | ) 23 | } 24 | 25 | export default App 26 | ``` 27 | 28 | Be aware that the component you are passing to `createPicassoComponent` should accept a `style` property initially. 29 | -------------------------------------------------------------------------------- /docs/docs/5-examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: examples 3 | title: Examples 4 | sidebar_label: Examples 5 | slug: /examples 6 | --- 7 | 8 | ![example](https://github.com/meienberger/react-native-picasso/raw/master/demo/example.png) 9 | 10 | Creating the following layout would be as easy as those 40 lines of code : 11 | 12 | ```jsx 13 | import React from 'react' 14 | import { Image } from 'react-native' 15 | import { 16 | View, 17 | Text, 18 | SafeAreaView, 19 | createPicassoComponent, 20 | } from 'react-native-picasso' 21 | 22 | const PicassoImage = createPicassoComponent(Image) 23 | 24 | const App = () => { 25 | return ( 26 | 27 | 28 | 33 | 34 | 35 | Nicolas Meienberger 36 | 37 | 38 | Geneva, Switzerland 39 | 40 | 41 | 42 | 43 | 44 | Welcome to Picasso 45 | 46 | 47 | 48 | ) 49 | } 50 | 51 | export default App 52 | ``` 53 | 54 | Be aware that the component you are passing to `createPicassoComponent` should accept a `style` property initially. 55 | -------------------------------------------------------------------------------- /docs/docs/mdx.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: mdx 3 | title: Powered by MDX 4 | --- 5 | 6 | You can write JSX and use React components within your Markdown thanks to [MDX](https://mdxjs.com/). 7 | 8 | export const Highlight = ({children, color}) => ( {children} ); 14 | 15 | Docusaurus green and Facebook blue are my favorite colors. 16 | 17 | I can write **Markdown** alongside my _JSX_! 18 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'React Native Picasso', 3 | tagline: 'Design System for React Native', 4 | url: 'https://meienberger.github.io', 5 | baseUrl: '/react-native-picasso/', 6 | onBrokenLinks: 'throw', 7 | favicon: 'img/favicon.ico', 8 | organizationName: 'meienberger', // Usually your GitHub org/user name. 9 | projectName: 'react-native-picasso', // Usually your repo name. 10 | themeConfig: { 11 | navbar: { 12 | title: 'React Native Picasso', 13 | logo: { 14 | alt: 'My Site Logo', 15 | src: 'img/logo.svg', 16 | }, 17 | items: [ 18 | { 19 | to: 'docs/', 20 | activeBasePath: 'docs', 21 | label: 'Docs', 22 | position: 'left', 23 | }, 24 | // { to: 'blog', label: 'Blog', position: 'left' }, 25 | { 26 | href: 'https://github.com/meienberger/react-native-picasso', 27 | label: 'GitHub', 28 | position: 'right', 29 | }, 30 | ], 31 | }, 32 | footer: { 33 | style: 'dark', 34 | links: [ 35 | { 36 | title: 'Docs', 37 | items: [ 38 | { 39 | label: 'Introduction', 40 | to: 'docs/', 41 | }, 42 | { 43 | label: 'Getting started', 44 | to: 'docs/getting-started', 45 | }, 46 | { 47 | label: 'Customization', 48 | to: 'docs/customization', 49 | }, 50 | { 51 | label: 'Custom Component', 52 | to: 'docs/custom-component', 53 | }, 54 | ], 55 | }, 56 | // { 57 | // title: 'Community', 58 | // items: [ 59 | // { 60 | // label: 'Stack Overflow', 61 | // href: 'https://stackoverflow.com/questions/tagged/docusaurus', 62 | // }, 63 | // { 64 | // label: 'Discord', 65 | // href: 'https://discordapp.com/invite/docusaurus', 66 | // }, 67 | // { 68 | // label: 'Twitter', 69 | // href: 'https://twitter.com/docusaurus', 70 | // }, 71 | // ], 72 | // }, 73 | // { 74 | // title: 'More', 75 | // items: [ 76 | // { 77 | // label: 'Blog', 78 | // to: 'blog', 79 | // }, 80 | // { 81 | // label: 'GitHub', 82 | // href: 'https://github.com/facebook/docusaurus', 83 | // }, 84 | // ], 85 | // }, 86 | ], 87 | copyright: `Copyright © ${new Date().getFullYear()} React Native Picasso. Built with Docusaurus.`, 88 | }, 89 | }, 90 | presets: [ 91 | [ 92 | '@docusaurus/preset-classic', 93 | { 94 | docs: { 95 | sidebarPath: require.resolve('./sidebars.js'), 96 | // Please change this to your repo. 97 | editUrl: 98 | 'https://github.com/facebook/docusaurus/edit/master/website/', 99 | }, 100 | blog: { 101 | showReadingTime: true, 102 | // Please change this to your repo. 103 | editUrl: 104 | 'https://github.com/facebook/docusaurus/edit/master/website/blog/', 105 | }, 106 | theme: { 107 | customCss: require.resolve('./src/css/custom.css'), 108 | }, 109 | }, 110 | ], 111 | ], 112 | } 113 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "documentation", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "serve": "docusaurus serve", 12 | "pages:deploy": "GIT_USER=meienberger USE_SSH=true yarn deploy" 13 | }, 14 | "dependencies": { 15 | "@docusaurus/core": "2.0.0-alpha.63", 16 | "@docusaurus/preset-classic": "2.0.0-alpha.63", 17 | "@mdx-js/react": "^1.5.8", 18 | "clsx": "^1.1.1", 19 | "react": "^16.8.4", 20 | "react-dom": "^16.8.4" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">0.2%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | someSidebar: { 3 | Fundamentals: [ 4 | 'introduction', 5 | 'getting-started', 6 | 'customization', 7 | 'custom-components', 8 | 'examples', 9 | ], 10 | // Showcases: ['mdx'], 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-color-primary: #25c2a0; 11 | --ifm-color-primary-dark: rgb(33, 175, 144); 12 | --ifm-color-primary-darker: rgb(31, 165, 136); 13 | --ifm-color-primary-darkest: rgb(26, 136, 112); 14 | --ifm-color-primary-light: rgb(70, 203, 174); 15 | --ifm-color-primary-lighter: rgb(102, 212, 189); 16 | --ifm-color-primary-lightest: rgb(146, 224, 208); 17 | --ifm-code-font-size: 95%; 18 | } 19 | 20 | .docusaurus-highlight-code-line { 21 | background-color: rgb(72, 77, 91); 22 | display: block; 23 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 24 | padding: 0 var(--ifm-pre-padding); 25 | } 26 | -------------------------------------------------------------------------------- /docs/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import clsx from 'clsx' 3 | import Layout from '@theme/Layout' 4 | import Link from '@docusaurus/Link' 5 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext' 6 | import useBaseUrl from '@docusaurus/useBaseUrl' 7 | import styles from './styles.module.css' 8 | 9 | const features = [ 10 | { 11 | title: 'Focus on What Matters', 12 | // imageUrl: 'img/undraw_docusaurus_tree.svg', 13 | description: ( 14 | <> 15 | Picasso lets you focus on your business logic, you define your design 16 | system once and use it everywhere. 17 | 18 | ), 19 | }, 20 | { 21 | title: 'Easy to Use', 22 | // imageUrl: 'img/undraw_docusaurus_mountain.svg', 23 | description: ( 24 | <>Just one install command, JavaScript only, no dependencies ! 25 | ), 26 | }, 27 | 28 | { 29 | title: 'Fully compatible and flexible', 30 | // imageUrl: 'img/undraw_docusaurus_react.svg', 31 | description: ( 32 | <> 33 | Picasso is highly pluggable. You can start by using the tools at some 34 | places and integrate at your own pace. 35 | 36 | ), 37 | }, 38 | ] 39 | 40 | function Feature({ imageUrl, title, description }) { 41 | const imgUrl = useBaseUrl(imageUrl) 42 | 43 | return ( 44 |
45 | {imgUrl && ( 46 |
47 | {title} 48 |
49 | )} 50 |

{title}

51 |

{description}

52 |
53 | ) 54 | } 55 | 56 | function Home() { 57 | const context = useDocusaurusContext() 58 | const { siteConfig = {} } = context 59 | 60 | return ( 61 | 65 |
66 |
67 |

{siteConfig.title}

68 |

{siteConfig.tagline}

69 |

You should not think about your design once it's defined

70 |
71 | 78 | Get Started 79 | 80 |
81 |
82 |
83 |
84 | {features && features.length > 0 && ( 85 |
86 |
87 |
88 | {features.map((props, idx) => ( 89 | 90 | ))} 91 |
92 |
93 |
94 | )} 95 |
96 |
97 | ) 98 | } 99 | 100 | export default Home 101 | -------------------------------------------------------------------------------- /docs/src/pages/styles.module.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | 3 | /** 4 | * CSS files with the .module.css suffix will be treated as CSS modules 5 | * and scoped locally. 6 | */ 7 | 8 | .heroBanner { 9 | padding: 4rem 0; 10 | text-align: center; 11 | position: relative; 12 | overflow: hidden; 13 | } 14 | 15 | @media screen and (max-width: 966px) { 16 | .heroBanner { 17 | padding: 2rem; 18 | } 19 | } 20 | 21 | .buttons { 22 | display: flex; 23 | align-items: center; 24 | justify-content: center; 25 | } 26 | 27 | .features { 28 | display: flex; 29 | align-items: center; 30 | padding: 2rem 0; 31 | width: 100%; 32 | } 33 | 34 | .featureImage { 35 | height: 200px; 36 | width: 200px; 37 | } 38 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/img/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/static/img/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/img/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/static/img/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/img/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/static/img/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #ffffff 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/static/img/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/img/favicon-16x16.png -------------------------------------------------------------------------------- /docs/static/img/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/img/favicon-32x32.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | 12 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/static/img/logo_old.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/static/img/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/img/mstile-144x144.png -------------------------------------------------------------------------------- /docs/static/img/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/img/mstile-150x150.png -------------------------------------------------------------------------------- /docs/static/img/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/img/mstile-310x150.png -------------------------------------------------------------------------------- /docs/static/img/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/img/mstile-310x310.png -------------------------------------------------------------------------------- /docs/static/img/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicotsx/react-native-picasso/e86054633908e6dab33519978a10781e1ee7bd99/docs/static/img/mstile-70x70.png -------------------------------------------------------------------------------- /docs/static/img/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/static/img/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/img/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/img/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /docs/static/img/undraw_docusaurus_mountain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /docs/static/img/undraw_docusaurus_react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /docs/static/img/undraw_docusaurus_tree.svg: -------------------------------------------------------------------------------- 1 | docu_tree -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-picasso", 3 | "version": "1.1.12", 4 | "description": "", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "run:linter": "eslint 'src/**/*.js'", 9 | "build": "rm -rf build && mkdir build && tsc" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/meienberger/react-native-picasso.git" 14 | }, 15 | "author": "Nicolas Meienberger", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/meienberger/react-native-picasso/issues" 19 | }, 20 | "homepage": "https://github.com/meienberger/react-native-picasso#readme", 21 | "devDependencies": { 22 | "@babel/cli": "^7.11.6", 23 | "@babel/core": "^7.11.6", 24 | "@babel/plugin-transform-runtime": "^7.11.5", 25 | "@babel/preset-env": "^7.11.5", 26 | "@babel/preset-react": "^7.10.4", 27 | "@types/prop-types": "^15.7.3", 28 | "@types/react": "^16.9.56", 29 | "@types/react-native": "^0.63.34", 30 | "@typescript-eslint/eslint-plugin": "^4.7.0", 31 | "@typescript-eslint/parser": "^4.7.0", 32 | "babel-eslint": "^10.1.0", 33 | "eslint": "^7.8.1", 34 | "eslint-config-airbnb": "^18.2.0", 35 | "eslint-config-prettier": "^6.11.0", 36 | "eslint-plugin-import": "^2.22.0", 37 | "eslint-plugin-no-loops": "^0.3.0", 38 | "eslint-plugin-prettier": "^3.1.4", 39 | "eslint-plugin-react": "^7.20.6", 40 | "eslint-plugin-react-hooks": "^4.1.0", 41 | "eslint-plugin-react-native": "^3.9.1", 42 | "metro-react-native-babel-preset": "^0.63.0", 43 | "prettier": "^2.1.1", 44 | "prop-types": "^15.7.2", 45 | "react": "^16.13.1", 46 | "react-native": "^0.63.2", 47 | "typescript": "^4.0.5" 48 | }, 49 | "peerDependencies": { 50 | "react": ">= 16", 51 | "react-native": ">= 0.63" 52 | }, 53 | "files": [ 54 | "build" 55 | ], 56 | "dependencies": { 57 | "@types/hoist-non-react-statics": "^3.3.1", 58 | "deepmerge": "^4.2.2", 59 | "hoist-non-react-statics": "^3.3.2" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/SafeAreaView.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { SafeAreaView, ViewProps } from 'react-native' 3 | import { buildStyleSheet } from '../util/style-helpers' 4 | import { ThemeContext } from '../core/theming' 5 | import { Theme } from '../styles/defaultTheme' 6 | 7 | interface CProps extends ViewProps { 8 | className?: string 9 | children?: React.ReactNode 10 | } 11 | 12 | const PicassoSafeView = React.forwardRef((props, ref) => { 13 | const { children, className = '', style, ...others } = props 14 | 15 | return ( 16 | 17 | {(theme: Theme) => { 18 | const picassoStyle = buildStyleSheet(className, theme, 'view') 19 | 20 | return ( 21 | 26 | {children} 27 | 28 | ) 29 | }} 30 | 31 | ) 32 | }) 33 | 34 | PicassoSafeView.displayName = 'PicassoSafeView' 35 | 36 | export default PicassoSafeView 37 | -------------------------------------------------------------------------------- /src/components/Text.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Text, StyleSheet, TextProps } from 'react-native' 3 | import { buildStyleSheet } from '../util/style-helpers' 4 | import { ThemeContext } from '../core/theming' 5 | import { Theme } from '../styles/defaultTheme' 6 | 7 | interface CProps extends TextProps { 8 | className?: string 9 | children?: React.ReactNode 10 | } 11 | 12 | const PicassoText = React.forwardRef((props, ref) => { 13 | const { children, className = '', style, ...others } = props 14 | 15 | return ( 16 | 17 | {(theme: Theme) => { 18 | const picassoStyle = buildStyleSheet(className, theme, 'text') 19 | 20 | return ( 21 | 33 | {children} 34 | 35 | ) 36 | }} 37 | 38 | ) 39 | }) 40 | 41 | PicassoText.displayName = 'PicassoText' 42 | 43 | export default PicassoText 44 | -------------------------------------------------------------------------------- /src/components/TextInput.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { StyleSheet, TextInputProps, TextInput } from 'react-native' 3 | import { buildStyleSheet } from '../util/style-helpers' 4 | import { ThemeContext } from '../core/theming' 5 | import { Theme } from '../styles/defaultTheme' 6 | 7 | interface CProps extends TextInputProps { 8 | className?: string 9 | children?: React.ReactNode 10 | } 11 | 12 | const PicassoText = React.forwardRef((props, ref) => { 13 | const { children, className = '', style, ...others } = props 14 | 15 | return ( 16 | 17 | {(theme: Theme) => { 18 | const picassoStyle = buildStyleSheet(className, theme, 'custom') 19 | 20 | return ( 21 | 33 | {children} 34 | 35 | ) 36 | }} 37 | 38 | ) 39 | }) 40 | 41 | PicassoText.displayName = 'PicassoText' 42 | 43 | export default PicassoText 44 | -------------------------------------------------------------------------------- /src/components/TouchableOpacity.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { TouchableOpacity, TouchableOpacityProps } from 'react-native' 3 | import { buildStyleSheet } from '../util/style-helpers' 4 | import { ThemeContext } from '../core/theming' 5 | import { Theme } from '../styles/defaultTheme' 6 | 7 | interface CProps extends TouchableOpacityProps { 8 | className?: string 9 | children?: React.ReactNode 10 | } 11 | 12 | const PicassoTouchableOpacity = React.forwardRef( 13 | (props, ref) => { 14 | const { children, className = '', style, ...others } = props 15 | 16 | return ( 17 | 18 | {(theme: Theme) => { 19 | const picassoStyle = buildStyleSheet(className, theme, 'view') 20 | 21 | return ( 22 | 31 | {children} 32 | 33 | ) 34 | }} 35 | 36 | ) 37 | }, 38 | ) 39 | 40 | PicassoTouchableOpacity.displayName = 'PicassoTouchableOpacity' 41 | 42 | export default PicassoTouchableOpacity 43 | -------------------------------------------------------------------------------- /src/components/View.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { View, ViewProps } from 'react-native' 3 | import { buildStyleSheet } from '../util/style-helpers' 4 | import { ThemeContext } from '../core/theming' 5 | import { Theme } from '../styles/defaultTheme' 6 | 7 | interface CProps extends ViewProps { 8 | className?: string 9 | children?: React.ReactNode 10 | } 11 | 12 | const PicassoView = React.forwardRef((props, ref) => { 13 | const { children, className = '', style, ...others } = props 14 | 15 | return ( 16 | 17 | {(theme: Theme) => { 18 | const picassoStyle = buildStyleSheet(className, theme, 'view') 19 | 20 | return ( 21 | 26 | {children} 27 | 28 | ) 29 | }} 30 | 31 | ) 32 | }) 33 | 34 | PicassoView.displayName = 'PicassoView' 35 | 36 | export default PicassoView 37 | -------------------------------------------------------------------------------- /src/core/createThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import deepmerge from 'deepmerge' 3 | import { Theme } from '../styles/defaultTheme' 4 | 5 | interface Props { 6 | theme: Theme 7 | } 8 | 9 | function createThemeProvider( 10 | defaultTheme: Theme, 11 | ThemeContext: React.Context, 12 | ) { 13 | const ThemeProvider: React.FC = (props) => { 14 | return ( 15 | 16 | {props.children} 17 | 18 | ) 19 | } 20 | 21 | return ThemeProvider 22 | } 23 | 24 | export default createThemeProvider 25 | -------------------------------------------------------------------------------- /src/core/createTheming.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import deepmerge from 'deepmerge' 3 | import createThemeProvider from './createThemeProvider' 4 | import createWithTheme from './createWithTheme' 5 | import { Theme } from '../styles/defaultTheme' 6 | 7 | export default function createTheming( 8 | defaultTheme: Theme, 9 | ): { 10 | ThemeContext: React.Context 11 | ThemeProvider: React.FC<{ 12 | theme: Theme 13 | }> 14 | withTheme: (Comp: React.FC) => any 15 | useTheme: (overrides?: Theme) => Theme 16 | } { 17 | const ThemeContext = React.createContext(defaultTheme) 18 | 19 | const ThemeProvider = createThemeProvider(defaultTheme, ThemeContext) 20 | 21 | const withTheme = createWithTheme(ThemeContext) 22 | 23 | const useTheme = (overrides?: Theme): Theme => { 24 | const theme = React.useContext(ThemeContext) 25 | const result = React.useMemo( 26 | () => 27 | theme && overrides ? deepmerge(theme, overrides) : theme || overrides, 28 | [theme, overrides], 29 | ) 30 | 31 | return result 32 | } 33 | 34 | return { 35 | ThemeContext, 36 | ThemeProvider, 37 | withTheme, 38 | useTheme, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/core/createWithTheme.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import deepmerge from 'deepmerge' 3 | import hoistNonReactStatics from 'hoist-non-react-statics' 4 | import { Theme } from '../styles/defaultTheme' 5 | import { TextProps, TouchableOpacityProps, ViewProps } from 'react-native' 6 | 7 | interface CProps extends TouchableOpacityProps { 8 | className?: string 9 | theme: Theme 10 | ref: React.ForwardedRef 11 | } 12 | 13 | const createWithTheme = (ThemeContext: React.Context) => { 14 | const withTheme = (WrappedComponent: React.FC) => { 15 | const Enhance: React.FC = (props) => { 16 | let _previous: any 17 | 18 | const _merge = (a: object, b: Theme) => { 19 | const previous = _previous 20 | 21 | if (previous && previous.a === a && previous.b === b) { 22 | return previous.result 23 | } 24 | 25 | const result = a && b && a !== b ? deepmerge(a, b) : a || b 26 | 27 | _previous = { a, b, result } 28 | 29 | return result 30 | } 31 | 32 | const { ref, ...rest } = props 33 | 34 | return ( 35 | 36 | {(theme: Theme) => ( 37 | 42 | )} 43 | 44 | ) 45 | } 46 | 47 | const ResultComponent = React.forwardRef( 48 | (props: CProps, ref: React.ForwardedRef) => ( 49 | 50 | ), 51 | ) 52 | 53 | ResultComponent.displayName = `withTheme(${ 54 | WrappedComponent.displayName || WrappedComponent.name 55 | })` 56 | 57 | const FinalComponent = hoistNonReactStatics( 58 | ResultComponent, 59 | WrappedComponent, 60 | ) 61 | 62 | return FinalComponent 63 | } 64 | 65 | return withTheme 66 | } 67 | 68 | export default createWithTheme 69 | -------------------------------------------------------------------------------- /src/core/theming.ts: -------------------------------------------------------------------------------- 1 | import createTheming from './createTheming' 2 | import { defaultTheme } from '../styles/defaultTheme' 3 | 4 | export const { 5 | ThemeProvider, 6 | withTheme, 7 | useTheme, 8 | ThemeContext, 9 | } = createTheming(defaultTheme) 10 | -------------------------------------------------------------------------------- /src/hoc/createPicassoComponent.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/display-name */ 2 | import * as React from 'react' 3 | import { StyleSheet, ViewProps } from 'react-native' 4 | import hoistNonReactStatics from 'hoist-non-react-statics' 5 | import { ThemeContext } from '../core/theming' 6 | import { buildStyleSheet } from '../util/style-helpers' 7 | import { Theme } from '../styles/defaultTheme' 8 | 9 | interface CProps extends ViewProps { 10 | className?: string 11 | children?: React.ReactNode 12 | } 13 | 14 | const createPicassoComponent =

( 15 | WrappedComponent: React.ComponentType

, 16 | ) => { 17 | const EnhancedComponent = React.forwardRef( 18 | (props, ref) => { 19 | const { children, className = '', style, ...others } = props 20 | 21 | return ( 22 | 23 | {(theme: Theme) => { 24 | const picassoStyle = buildStyleSheet(className, theme, 'custom') 25 | 26 | return ( 27 | 36 | {children} 37 | 38 | ) 39 | }} 40 | 41 | ) 42 | }, 43 | ) 44 | 45 | const FinalComponent = hoistNonReactStatics( 46 | EnhancedComponent, 47 | WrappedComponent, 48 | ) 49 | 50 | return FinalComponent 51 | } 52 | 53 | export default createPicassoComponent 54 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { ThemeProvider, useTheme, withTheme } from './core/theming' 2 | 3 | export { default as View } from './components/View' 4 | export { default as Text } from './components/Text' 5 | export { default as TextInput } from './components/TextInput' 6 | export { default as SafeAreaView } from './components/SafeAreaView' 7 | export { default as TouchableOpacity } from './components/TouchableOpacity' 8 | export { default as createPicassoComponent } from './hoc/createPicassoComponent' 9 | export { defaultTheme } from './styles/defaultTheme' 10 | -------------------------------------------------------------------------------- /src/styles/defaultTheme.ts: -------------------------------------------------------------------------------- 1 | interface BackgroundColors { 2 | background?: string 3 | primary?: string 4 | secondary?: string 5 | border?: string 6 | [x: string]: any 7 | } 8 | 9 | interface TextColors { 10 | primary?: string 11 | secondary?: string 12 | white?: string 13 | [x: string]: any 14 | } 15 | 16 | interface Sizes { 17 | sm?: number 18 | md?: number 19 | lg?: number 20 | xl?: number 21 | xxl?: number 22 | [x: string]: any 23 | } 24 | 25 | interface Weights { 26 | light?: string 27 | normal?: string 28 | bold?: string 29 | extrabold?: string 30 | [x: string]: any 31 | } 32 | 33 | interface Font { 34 | family?: string 35 | sizes?: Sizes 36 | weights?: Weights 37 | } 38 | 39 | interface Radius extends Sizes { 40 | round?: number 41 | [x: string]: any 42 | } 43 | 44 | export interface Theme { 45 | colors?: BackgroundColors 46 | textColors?: TextColors 47 | font?: Font 48 | elevated?: object 49 | radius?: Radius 50 | spacing?: Sizes 51 | [x: string]: any 52 | } 53 | 54 | export const defaultTheme: Theme = { 55 | colors: { 56 | background: '#eeeeee', 57 | primary: '#00B386', 58 | secondary: '#dedede', 59 | border: '#d3d3d3', 60 | }, 61 | textColors: { 62 | primary: '#333333', 63 | secondary: '#666666', 64 | white: '#ffffff', 65 | }, 66 | font: { 67 | family: 'Helvetica', 68 | sizes: { 69 | sm: 12, 70 | md: 16, 71 | lg: 24, 72 | xl: 32, 73 | xxl: 40, 74 | }, 75 | weights: { 76 | light: '100', 77 | normal: 'normal', 78 | bold: 'bold', 79 | extrabold: '800', 80 | }, 81 | }, 82 | elevated: { 83 | shadowColor: '#000000', 84 | shadowOffset: { width: 0, height: 5 }, 85 | shadowOpacity: 0.2, 86 | shadowRadius: 3, 87 | elevation: 5, 88 | }, 89 | radius: { 90 | sm: 5, 91 | md: 10, 92 | lg: 20, 93 | xl: 40, 94 | xxl: 80, 95 | round: 1000, 96 | }, 97 | spacing: { 98 | sm: 8, 99 | md: 16, 100 | lg: 24, 101 | xl: 32, 102 | xxl: 40, 103 | }, 104 | } 105 | -------------------------------------------------------------------------------- /src/test.tsx: -------------------------------------------------------------------------------- 1 | import { Text, TouchableOpacity, ViewBase } from 'react-native' 2 | import { createPicassoComponent } from '.' 3 | 4 | const MyView: React.FC = (props) => { 5 | const { children } = props 6 | 7 | return {children} 8 | } 9 | 10 | const MyViewPicasso = createPicassoComponent(TouchableOpacity) 11 | 12 | const App = () => { 13 | return ( 14 | 15 | Hello 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/util/classname-helpers.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from '../styles/defaultTheme' 2 | import { validPropertiesBase, validPropertiesForType } from './constants' 3 | 4 | export interface PropertyValue { 5 | property: string 6 | value: string 7 | } 8 | 9 | /** 10 | * Splits the classname and return an array of 11 | * valid picasso class types 12 | * @param {String} classname 13 | */ 14 | export const splitAndValidate = ( 15 | classname: string, 16 | componentType: 'view' | 'text' | 'custom', 17 | theme: Theme, 18 | ): PropertyValue[] => { 19 | const validProperties: string[] = [ 20 | ...validPropertiesBase, 21 | ...validPropertiesForType[componentType], 22 | ...Object.keys(theme), 23 | ] 24 | 25 | const split = classname.split(' ') 26 | 27 | return split 28 | .map((declaration) => { 29 | const [property, value] = declaration.split('-') 30 | 31 | if (validProperties.includes(property)) { 32 | const final: PropertyValue = { property, value } 33 | return final 34 | } 35 | 36 | return { property: '', value: '' } 37 | }) 38 | .filter(Boolean) 39 | } 40 | -------------------------------------------------------------------------------- /src/util/constants.ts: -------------------------------------------------------------------------------- 1 | export const spacingProperties = [ 2 | 'm', 3 | 'mr', 4 | 'ml', 5 | 'mt', 6 | 'mb', 7 | 'mx', 8 | 'my', 9 | 'p', 10 | 'pr', 11 | 'pl', 12 | 'pt', 13 | 'pb', 14 | 'px', 15 | 'py', 16 | ] 17 | 18 | export const borderWidthProperties = ['b', 'bl', 'br', 'bt', 'bb'] 19 | 20 | export const alignValues: Record = { 21 | center: 'center', 22 | left: 'left', 23 | right: 'right', 24 | justify: 'justify', 25 | c: 'center', 26 | l: 'left', 27 | r: 'right', 28 | j: 'justify', 29 | } 30 | 31 | export const alignItemsValues: Record = { 32 | center: 'center', 33 | start: 'flex-start', 34 | end: 'flex-end', 35 | stretch: 'stretch', 36 | baseline: 'baseline', 37 | between: 'space-between', 38 | around: 'space-around', 39 | evenly: 'space-evenly', 40 | c: 'center', 41 | fs: 'flex-start', 42 | fe: 'flex-end', 43 | ba: 'baseline', 44 | sb: 'space-between', 45 | sa: 'space-around', 46 | se: 'space-evenly', 47 | } 48 | 49 | export const fontStyleValues: Record = { 50 | normal: 'normal', 51 | italic: 'italic', 52 | n: 'normal', 53 | i: 'italic', 54 | } 55 | 56 | export const flexValues: Record = { 57 | row: 'row', 58 | column: 'column', 59 | r: 'row', 60 | c: 'column', 61 | } 62 | 63 | export const validPropertiesBase = [ 64 | ...spacingProperties, 65 | 'flex', 66 | 'f', 67 | 'alignself', 68 | 'as', 69 | ] 70 | const validPropertiesView = [ 71 | 'elevated', 72 | 'radius', 73 | 'r', 74 | 'radiustl', 75 | 'rtl', 76 | 'radiustr', 77 | 'rtr', 78 | 'radiusbl', 79 | 'rbl', 80 | 'radiusbr', 81 | 'rbr', 82 | 'bg', 83 | 'alignitems', 84 | 'ai', 85 | 'justifycontent', 86 | 'jc', 87 | 'bordercolor', 88 | 'bc', 89 | ...borderWidthProperties, 90 | ] 91 | const validPropertiesText = [ 92 | 'weight', 93 | 'w', 94 | 'align', 95 | 'a', 96 | 'color', 97 | 'c', 98 | 'size', 99 | 's', 100 | 'fontstyle', 101 | 'fs', 102 | ] 103 | 104 | export const validPropertiesForType = { 105 | view: validPropertiesView, 106 | text: validPropertiesText, 107 | custom: [...validPropertiesView, ...validPropertiesText], 108 | } 109 | 110 | export const PROPERTIES = { 111 | BACKGROUND: 'bg', 112 | COLOR: 'color', 113 | COLOR_SHORT: 'c', 114 | FONT_SIZE: 'size', 115 | FONT_SIZE_SHORT: 's', 116 | FONT_WEIGHT: 'weight', 117 | FONT_WEIGHT_SHORT: 'w', 118 | FONT_STYLE: 'fontstyle', 119 | FONT_STYLE_SHORT: 'fs', 120 | ALIGN_ITEMS: 'alignitems', 121 | ALIGN_ITEMS_SHORT: 'ai', 122 | JUSTIFY_CONTENT: 'justifycontent', 123 | JUSTIFY_CONTENT_SHORT: 'jc', 124 | ALIGN_SELF: 'alignself', 125 | ALIGN_SELF_SHORT: 'as', 126 | TEXT_ALIGN: 'align', 127 | TEXT_ALIGN_SHORT: 'a', 128 | FLEX: 'flex', 129 | FLEX_SHORT: 'f', 130 | BORDER_RADIUS: 'radius', 131 | BORDER_RADIUS_SHORT: 'r', 132 | BORDER_RADIUS_TOP_LEFT: 'radiustl', 133 | BORDER_RADIUS_TOP_LEFT_SHORT: 'rtl', 134 | BORDER_RADIUS_TOP_RIGHT: 'radiustr', 135 | BORDER_RADIUS_TOP_RIGHT_SHORT: 'rtr', 136 | BORDER_RADIUS_BOTTOM_LEFT: 'radiusbl', 137 | BORDER_RADIUS_BOTTOM_LEFT_SHORT: 'rbl', 138 | BORDER_RADIUS_BOTTOM_RIGHT: 'radiusbr', 139 | BORDER_RADIUS_BOTTOM_RIGHT_SHORT: 'rbr', 140 | BORDER_COLOR: 'bordercolor', 141 | BORDER_COLOR_SHORT: 'bc', 142 | } 143 | -------------------------------------------------------------------------------- /src/util/style-helpers.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable newline-after-var */ 2 | import { StyleSheet } from 'react-native' 3 | import { Theme } from '../styles/defaultTheme' 4 | import { splitAndValidate } from './classname-helpers' 5 | import { 6 | alignValues, 7 | flexValues, 8 | spacingProperties, 9 | alignItemsValues, 10 | PROPERTIES, 11 | borderWidthProperties, 12 | fontStyleValues, 13 | } from './constants' 14 | 15 | interface PropertyValue { 16 | property: string 17 | value: string 18 | } 19 | 20 | const getSide = (value: string) => { 21 | let side = '' 22 | 23 | if (value === 't') side = 'Top' 24 | else if (value === 'b') side = 'Bottom' 25 | else if (value === 'l') side = 'Left' 26 | else if (value === 'r') side = 'Right' 27 | else if (value === 'x') side = 'Horizontal' 28 | else if (value === 'y') side = 'Vertical' 29 | 30 | return side 31 | } 32 | 33 | const classesToStyle = (array: PropertyValue[], theme: Record) => { 34 | let styles: Record = {} 35 | 36 | array.forEach(({ property, value }) => { 37 | switch (property.toLowerCase()) { 38 | case PROPERTIES.BACKGROUND: 39 | styles.backgroundColor = theme.colors[value] 40 | break 41 | case PROPERTIES.COLOR: 42 | case PROPERTIES.COLOR_SHORT: 43 | styles.color = theme.textColors[value] 44 | break 45 | case PROPERTIES.FONT_SIZE: 46 | case PROPERTIES.FONT_SIZE_SHORT: 47 | styles.fontSize = theme.font?.sizes?.[value] 48 | break 49 | case PROPERTIES.FONT_WEIGHT: 50 | case PROPERTIES.FONT_WEIGHT_SHORT: 51 | styles.fontWeight = theme.font?.weights?.[value] 52 | break 53 | case PROPERTIES.ALIGN_ITEMS: 54 | case PROPERTIES.ALIGN_ITEMS_SHORT: 55 | styles.alignItems = alignItemsValues[value] 56 | break 57 | case PROPERTIES.JUSTIFY_CONTENT: 58 | case PROPERTIES.JUSTIFY_CONTENT_SHORT: 59 | styles.justifyContent = alignItemsValues[value] 60 | break 61 | case PROPERTIES.ALIGN_SELF: 62 | case PROPERTIES.ALIGN_SELF_SHORT: 63 | styles.alignSelf = alignItemsValues[value] 64 | break 65 | case PROPERTIES.TEXT_ALIGN: 66 | case PROPERTIES.TEXT_ALIGN_SHORT: 67 | styles.textAlign = alignValues[value] 68 | break 69 | case PROPERTIES.FLEX: 70 | case PROPERTIES.FLEX_SHORT: 71 | if (flexValues[value]) { 72 | styles.flexDirection = flexValues[value] 73 | } else if (!Number.isNaN(Number(value))) { 74 | styles.flex = Number(value) 75 | } 76 | break 77 | case PROPERTIES.BORDER_RADIUS: 78 | case PROPERTIES.BORDER_RADIUS_SHORT: 79 | styles.borderRadius = theme.radius?.[value] 80 | break 81 | case PROPERTIES.BORDER_RADIUS_BOTTOM_LEFT: 82 | case PROPERTIES.BORDER_RADIUS_BOTTOM_LEFT_SHORT: 83 | styles.borderBottomLeftRadius = theme.radius?.[value] 84 | break 85 | case PROPERTIES.BORDER_RADIUS_BOTTOM_RIGHT: 86 | case PROPERTIES.BORDER_RADIUS_BOTTOM_RIGHT_SHORT: 87 | styles.borderBottomRightRadius = theme.radius?.[value] 88 | break 89 | case PROPERTIES.BORDER_RADIUS_TOP_LEFT: 90 | case PROPERTIES.BORDER_RADIUS_TOP_LEFT_SHORT: 91 | styles.borderTopLeftRadius = theme.radius?.[value] 92 | break 93 | case PROPERTIES.BORDER_RADIUS_TOP_RIGHT: 94 | case PROPERTIES.BORDER_RADIUS_TOP_RIGHT_SHORT: 95 | styles.borderTopRightRadius = theme.radius?.[value] 96 | break 97 | case PROPERTIES.BORDER_COLOR: 98 | case PROPERTIES.BORDER_COLOR_SHORT: 99 | styles.borderColor = theme.colors?.[value] 100 | break 101 | case PROPERTIES.FONT_STYLE: 102 | case PROPERTIES.FONT_STYLE_SHORT: 103 | styles.fontStyle = fontStyleValues[value] 104 | break 105 | default: 106 | if (spacingProperties.includes(property.toLowerCase())) { 107 | const values = theme.spacing 108 | let main = '' 109 | const side = getSide(property[1]) 110 | 111 | if (property[0] === 'm') main = 'margin' 112 | else if (property[0] === 'p') main = 'padding' 113 | 114 | styles[`${main}${side}`] = values[value] 115 | } else if (borderWidthProperties.includes(property.toLowerCase())) { 116 | const prop: string = property[1] 117 | const side = getSide(prop) 118 | 119 | if (!Number.isNaN(Number(value))) { 120 | styles[`border${side}Width`] = Number(value) 121 | } 122 | } 123 | // Custom root values like elevated 124 | else { 125 | styles = { 126 | ...styles, 127 | ...theme[property], 128 | } 129 | } 130 | } 131 | }) 132 | 133 | return styles 134 | } 135 | 136 | export const buildStyleSheet = ( 137 | className: string, 138 | theme: Theme, 139 | componentType: 'custom' | 'view' | 'text' = 'custom', 140 | ) => { 141 | const arrayClasses = splitAndValidate(className, componentType, theme) 142 | return StyleSheet.flatten(classesToStyle(arrayClasses, theme)) 143 | } 144 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-native", 4 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 5 | 6 | /* Basic Options */ 7 | // "incremental": true, /* Enable incremental compilation */ 8 | "target": "ES2016" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 9 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 10 | // "lib": [], /* Specify library files to be included in the compilation. */ 11 | // "allowJs": true, /* Allow javascript files to be compiled. */ 12 | // "checkJs": true, /* Report errors in .js files. */ 13 | "declaration": true /* Generates corresponding '.d.ts' file. */, 14 | // "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, 15 | // "sourceMap": true /* Generates corresponding '.map' file. */, 16 | // "outFile": "./build" /* Concatenate and emit output to single file. */, 17 | "outDir": "./build" /* Redirect output structure to the directory. */, 18 | "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true /* Enable all strict type-checking options. */, 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 51 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | 65 | /* Advanced Options */ 66 | "skipLibCheck": true /* Skip type checking of declaration files. */, 67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 68 | } 69 | } 70 | --------------------------------------------------------------------------------