├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── blank.yml │ └── publish.yml ├── .gitignore ├── .npmrc ├── LICENSE ├── README.md ├── jest.config.js ├── lib └── index.tsx ├── package-lock.json ├── package.json ├── scripts └── setupEnzyme.ts ├── tests └── index.test.tsx └── tsconfig.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/blank.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | test: 10 | name: Run package tests 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - uses: actions/setup-node@v1 16 | - name: npm install, build and test 17 | env: 18 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 19 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 20 | run: | 21 | npm install 22 | npm run build 23 | npm run coveralls 24 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | create: 5 | types: [tag] 6 | 7 | jobs: 8 | test: 9 | name: Run package tests 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v1 14 | - uses: actions/setup-node@v1 15 | - name: npm install, build and test 16 | env: 17 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 18 | run: | 19 | npm install 20 | npm run build 21 | npm t 22 | - name: Publish package 23 | env: 24 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 25 | run: npm publish 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.DS_Store 2 | /dist 3 | /.rts2_cache_cjs/ 4 | /node_modules 5 | /coverage 6 | .rts2_cache_es/ 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN} 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Felix Wostal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://i.ibb.co/j6CkJV4/react-robohash.png) 2 | 3 | [![npm version](https://badge.fury.io/js/react-robohash.svg)](https://badge.fury.io/js/react-robohash) 4 | [![Build Status](https://action-badges.now.sh/fel1xw/react-robohash?action=CI)](https://github.com/fel1xw/react-robohash/actions) 5 | [![Blazing Fast](https://badgen.now.sh/badge/speed/blazing%20%F0%9F%94%A5/green)](https://npm.im/react-robohash) 6 | [![Coverage Status](https://coveralls.io/repos/github/fel1xw/react-robohash/badge.svg?branch=master)](https://coveralls.io/github/fel1xw/react-robohash?branch=master) 7 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 8 | [![gzip size](http://img.badgesize.io/https://unpkg.com/react-robohash@latest/dist/index.js?compression=gzip)](https://unpkg.com/react-robohash@latest/dist/index.js) 9 | [![Known Vulnerabilities](https://snyk.io/test/github/fel1xw/react-robohash/badge.svg)](https://snyk.io/test/github/fel1xw/react-robohash) 10 | 11 | ## Install 12 | ```sh 13 | npm install react-robohash --save 14 | # or 15 | yarn add react-robohash 16 | ``` 17 | 18 | ## Playground 19 | You can play around with the module on [codesandbox](https://codesandbox.io/s/zrpz3jpzp?fontsize=14) 20 | 21 | ## Props 22 | | Name | Type | Required | Default | Description | 23 | |-------------|-----------------|----------|--------------------|-----------------------------------------------------------------------------| 24 | | name | string | true | | name, email, whatever identifier you want | 25 | | alt | string | false | `Robohash-${name}` | alt property for image | 26 | | className | string | false | Robohash | pass custom class | 27 | | size | string | number | false | | specify image size like 200 or 100x50 (square works best) | 28 | | type | string | number | false | robot | type - "robot", "alien", "head", "cat", 1, 2, 3, 4 | 29 | | background | string | false | 0 | custom background set (1, 2) | 30 | | fileType | string | false | png | image return type, png, svg, jpg (see homepage) | 31 | | gravatar | boolean | false | false | checks if gravatar is available otherwise fallbacks to robot | 32 | | children | function | false | | custom render prop function which gets only the url passed in (see example) | 33 | 34 | ## Usage 35 | ``` 36 | import Robohash from 'react-robohash'; 37 | 38 | 42 | ``` 43 | 44 | or with custom element as render prop function 45 | ``` 46 | import Robohash from 'react-robohash'; 47 | 48 | 49 | {url => {url}} 50 | 51 | ``` 52 | 53 | ## Author 54 | * Felix Wostal [@felixwostal](https://twitter.com/felixwostal) 55 | 56 | ## Support 57 | If you like the project and want to support my work, you can think about buy me a coffee :) 58 | 59 | [![paypal](https://img.shields.io/badge/donate-paypal-blue.svg)](https://paypal.me/felixwostal/1) 60 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "roots": [ 3 | "" 4 | ], 5 | "transform": { 6 | "^.+\\.tsx?$": "ts-jest" 7 | }, 8 | "testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.tsx?$", 9 | "moduleFileExtensions": [ 10 | "ts", 11 | "tsx", 12 | "js", 13 | "jsx", 14 | ], 15 | "setupFiles": ["/scripts/setupEnzyme.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /lib/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | type Props = { 4 | name: string, 5 | alt?: string, 6 | className?: string, 7 | size?: string | number, 8 | type?: string | number, 9 | fileType?: string, 10 | background?: number, 11 | gravatar?: boolean, 12 | children?: (url: string) => React.ReactElement 13 | } 14 | 15 | export const generateURL = ({ name, fileType = 'png', ...rest }: Props) => { 16 | const url = new URL(`https://robohash.org/${name}.${fileType}`) 17 | Object.entries(rest) 18 | .map(transformKeyAndValue) 19 | .forEach(([ key, value ]) => { 20 | url.searchParams.set(key, value) 21 | }) 22 | 23 | return url.href 24 | } 25 | 26 | const getSetForType = (type: string) => { 27 | switch (type.toLowerCase()) { 28 | case 'robot': 29 | return 'set1' 30 | case 'alien': 31 | return 'set2' 32 | case 'head': 33 | return 'set3' 34 | case 'cat': 35 | return 'set4' 36 | default: 37 | return 'set1' 38 | } 39 | } 40 | const transformKeyAndValue = ([ key, value ]: any[]) => { 41 | switch (key) { 42 | case 'type': { 43 | const val = typeof value === 'number' ? `set${value}` : getSetForType(value) 44 | return ['set', val] 45 | } 46 | case 'background': 47 | return ['bgset', `bg${value}`] 48 | case 'gravatar': 49 | return value ? ['gravatar', 'yes'] : [] 50 | case 'size': 51 | const val = typeof value === 'number' ? `${value}x${value}` : value 52 | return ['size', val] 53 | default: 54 | return [] 55 | } 56 | } 57 | 58 | const ReactRoboHash = ({ alt, children, className = 'Robohash', ...rest }: Props & React.HTMLAttributes): React.ReactElement => { 59 | const url = generateURL(rest) 60 | 61 | return children 62 | ? children(url) 63 | : {alt 64 | } 65 | 66 | export default ReactRoboHash; 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-robohash", 3 | "version": "1.0.9", 4 | "description": "A react component for robohash.org", 5 | "files": [ 6 | "dist" 7 | ], 8 | "types": "dist/index.d.ts", 9 | "main": "dist/index.js", 10 | "scripts": { 11 | "prebuild": "rimraf dist", 12 | "build": "tsc", 13 | "dev": "tsc --watch", 14 | "test": "jest", 15 | "coveralls": "jest --coverage && cat ./coverage/lcov.info | coveralls", 16 | "prepublishOnly": "npm t && npm run build" 17 | }, 18 | "keywords": [ 19 | "react", 20 | "robohash", 21 | "react-robohash" 22 | ], 23 | "author": { 24 | "name": "Felix Wostal", 25 | "url": "https://felixwostal.de" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/fel1xw/react-robohash/issues" 29 | }, 30 | "homepage": "https://github.com/fel1xw/react-robohash#readme", 31 | "license": "MIT", 32 | "devDependencies": { 33 | "@types/enzyme": "^3.9.0", 34 | "@types/enzyme-adapter-react-16": "^1.0.5", 35 | "@types/jest": "^24.0.6", 36 | "@types/node": "^11.9.5", 37 | "@types/react": "^16.8.5", 38 | "@types/react-dom": "^16.8.2", 39 | "coveralls": "^3.0.3", 40 | "enzyme": "^3.9.0", 41 | "enzyme-adapter-react-16": "^1.10.0", 42 | "jest": "^24.1.0", 43 | "react": "^16.8.3", 44 | "react-dom": "^16.8.3", 45 | "rimraf": "^2.6.3", 46 | "ts-jest": "^24.0.0", 47 | "typescript": "^3.3.3333" 48 | }, 49 | "peerDependencies": { 50 | "react": "^16.8.3", 51 | "react-dom": "^16.8.3" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scripts/setupEnzyme.ts: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme' 2 | import Adapter from 'enzyme-adapter-react-16' 3 | 4 | Enzyme.configure({ adapter: new Adapter() }) 5 | -------------------------------------------------------------------------------- /tests/index.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { shallow } from 'enzyme' 3 | import { parse } from 'url' 4 | 5 | import Robohash from '../lib' 6 | 7 | const baseProps = { 8 | name: 'react-robohash', 9 | alt: 'custom-alt', 10 | className: 'custom-class', 11 | size: 200, 12 | type: 'robot', 13 | fileType: 'png', 14 | background: 0, 15 | gravatar: true, 16 | } 17 | 18 | const parseToQueryObject = (query: string) => JSON.parse('{"' + decodeURI(query.replace(/&/g, "\",\"").replace(/=/g, "\":\"")) + '"}') 19 | 20 | describe('', () => { 21 | it('renders with baseProps', () => { 22 | const wrapper = shallow() 23 | const img = wrapper.find('img') 24 | expect(img).toBeTruthy() 25 | expect(img.prop('src')).toContain('.png') 26 | expect(img.prop('alt')).toBe(baseProps.alt) 27 | expect(img.hasClass(baseProps.className)).toBe(true) 28 | }) 29 | 30 | it('render with function as children', () => { 31 | const props = { 32 | name: 'robo', 33 | children: (url: string) => {url} 34 | } 35 | const wrapper = shallow() 36 | expect(wrapper.text().length).toBeGreaterThan(0) 37 | }) 38 | 39 | it('properly formats the url', () => { 40 | const wrapper = shallow() 41 | const query = parseToQueryObject(parse(wrapper.find('img').prop('src')!).query!) 42 | expect(query.bgset).toBe('bg0') 43 | expect(query.gravatar).toBe('yes') 44 | expect(query.set).toBe('set1') 45 | expect(query.size).toBe('200x200') 46 | }) 47 | 48 | it('accepts size as string', () => { 49 | const wrapper = shallow() 50 | const query = parseToQueryObject(parse(wrapper.find('img').prop('src')!).query!) 51 | expect(query.size).toBe('100x100') 52 | }) 53 | 54 | it('passes no when gravatar is false', () => { 55 | const wrapper = shallow() 56 | const query = parseToQueryObject(parse(wrapper.find('img').prop('src')!).query!) 57 | expect(query.gravatar).not.toBeDefined() 58 | }) 59 | 60 | it('has default class and alt value', () => { 61 | const wrapper = shallow() 62 | expect(wrapper.find('img').hasClass('Robohash')).toBe(true) 63 | expect(wrapper.find('img').prop('alt')).toBe('Robohash-robo') 64 | }) 65 | 66 | it('ignores custom props in query', () => { 67 | const wrapper = shallow() 68 | expect(wrapper.find('img').prop('src')).not.toContain('onClick') 69 | }) 70 | 71 | it('accepts string or number for type', () => { 72 | const cases = [ 73 | ['robot', 'set1'], 74 | ['alien', 'set2'], 75 | ['head', 'set3'], 76 | ['cat', 'set4'], 77 | ['xyz', 'set1'], 78 | [1, 'set1'], 79 | [2, 'set2'], 80 | [3, 'set3'], 81 | [4, 'set4'], 82 | ] 83 | 84 | cases.forEach(([ type, expected ]) => { 85 | const wrapper = shallow() 86 | expect(wrapper.find('img').prop('src')).toContain(expected) 87 | }) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "lib": [ 7 | "dom", 8 | "es6", 9 | "es7", 10 | "es2017" 11 | ], /* Specify library files to be included in the compilation. */ 12 | // "allowJs": true, /* Allow javascript files to be compiled. */ 13 | // "checkJs": true, /* Report errors in .js files. */ 14 | "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 15 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 16 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 17 | "sourceMap": true, /* Generates corresponding '.map' file. */ 18 | // "outFile": "./", /* Concatenate and emit output to single file. */ 19 | "outDir": "dist", /* Redirect output structure to the directory. */ 20 | "rootDir": "lib", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 21 | // "composite": true, /* Enable project compilation */ 22 | "removeComments": true, /* Do not emit comments to output. */ 23 | // "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 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 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 33 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 34 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 35 | /* Additional Checks */ 36 | "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | /* Module Resolution Options */ 41 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 42 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 43 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 44 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 45 | // "typeRoots": [], /* List of folders to include type definitions from. */ 46 | // "types": [], /* Type declaration files to be included in compilation. */ 47 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 48 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 49 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 50 | /* Source Map Options */ 51 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | /* Experimental Options */ 56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | }, 59 | "exclude": [ 60 | "node_modules", 61 | "**/*.test.", 62 | "dist", 63 | "scripts", 64 | "tests" 65 | ] 66 | } 67 | --------------------------------------------------------------------------------