├── .babelrc
├── .gitignore
├── .npmignore
├── .storybook
├── addons.js
└── config.js
├── LICENSE.md
├── README.md
├── package-lock.json
├── package.json
├── preview.jpg
├── src
├── components
│ ├── List.jsx
│ ├── Node.jsx
│ ├── Panel.jsx
│ ├── PrimitiveValue.jsx
│ └── index.js
├── getValueExcerpt.js
├── index.jsx
├── renderData.js
└── themes
│ ├── dark.js
│ ├── index.js
│ └── light.js
├── stories
├── Example.jsx
├── index.js
└── styles.css
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["last 2 versions"]
7 | }
8 | }],
9 | "react"
10 | ],
11 | "plugins": [
12 | "transform-export-extensions",
13 | "transform-class-properties"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .storybook
2 | stories
3 | src
4 | docs
5 | stories
6 | webpack.*
7 |
--------------------------------------------------------------------------------
/.storybook/addons.js:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-actions/register';
2 | import '@storybook/addon-links/register';
3 |
--------------------------------------------------------------------------------
/.storybook/config.js:
--------------------------------------------------------------------------------
1 | import { configure } from '@storybook/react';
2 |
3 | function loadStories() {
4 | require('../stories');
5 | }
6 |
7 | configure(loadStories, module);
8 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Artem Zakharchenko
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 | # react-data-preview
2 |
3 | Preview any JavaScript data in a fancy interactive way. Heavily inspired by React DevTools props inspector.
4 |
5 |
6 |
7 |
8 |
9 | ## Getting started
10 | ### Install
11 | ```bash
12 | npm install react-data-preview
13 | ```
14 |
15 | ### Use
16 | ```jsx
17 | import React from 'react';
18 | import Preview from 'react-data-preview';
19 |
20 | const data = {
21 | firstName: 'John',
22 | lastName: 'Maverick',
23 | isAuthenticated: true
24 | image: class Image {},
25 | logout() {},
26 | orders: [{}, {}],
27 | settings: {
28 | nestedKey: 'foo'
29 | }
30 | };
31 |
32 | export default class App extends React.Component {
33 | render() {
34 | return (
35 |
36 | );
37 | }
38 | }
39 | ```
40 |
41 | ## License
42 | MIT
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-data-preview",
3 | "version": "1.0.0",
4 | "description": "Fancy preview for JavaScript data.",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "storybook": "start-storybook -p 6006",
8 | "build": "NODE_ENV=production webpack",
9 | "prepublishOnly": "npm run build"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/kettanaito/react-data-preview.git"
14 | },
15 | "keywords": [
16 | "javascript",
17 | "data",
18 | "preview",
19 | "react",
20 | "react-data-preview"
21 | ],
22 | "author": "Artem Zakharchenko",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/kettanaito/react-data-preview/issues"
26 | },
27 | "homepage": "https://github.com/kettanaito/react-data-preview#readme",
28 | "dependencies": {
29 | "react": "^16.2.0",
30 | "styled-components": "^3.2.3"
31 | },
32 | "devDependencies": {
33 | "@storybook/addon-actions": "^3.3.15",
34 | "@storybook/addon-links": "^3.3.15",
35 | "@storybook/react": "^3.3.15",
36 | "babel-core": "^6.26.0",
37 | "babel-loader": "^7.1.4",
38 | "babel-minify-webpack-plugin": "^0.3.1",
39 | "babel-plugin-transform-class-properties": "^6.24.1",
40 | "babel-plugin-transform-export-extensions": "^6.22.0",
41 | "babel-preset-env": "^1.6.1",
42 | "babel-preset-react": "^6.24.1",
43 | "prop-types": "^15.6.1",
44 | "react-dom": "^16.2.0",
45 | "storybook": "^1.0.0",
46 | "webpack": "^4.2.0",
47 | "webpack-cli": "^2.0.12"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kettanaito/react-data-preview/2e66005f8e119f818f5a910f16a524c4852baeda/preview.jpg
--------------------------------------------------------------------------------
/src/components/List.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const List = styled.ul`
5 | margin: 0;
6 | padding: 0;
7 |
8 | list-style:none;
9 | `;
10 |
11 | export default List;
12 |
--------------------------------------------------------------------------------
/src/components/Node.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import List from './List';
5 | import renderData from '../renderData';
6 | import getValueExcerpt from '../getValueExcerpt';
7 |
8 | const NodeContainer = styled.li`
9 | position: relative;
10 | padding-left: .9rem;
11 |
12 | list-style: none;
13 |
14 | &:not(:last-child) {
15 | margin-bottom: .25rem;
16 | }
17 | `;
18 |
19 | const NodeArrow = styled.span`
20 | position: absolute;
21 | top: 6px;
22 | left: 3px;
23 | display: inline-block;
24 | margin: auto .3rem auto 0;
25 | height: 0;
26 |
27 | border-style: solid;
28 | border-width: 5px;
29 | border-color: ${({ theme }) => theme.node.arrowBackground} transparent transparent;
30 |
31 | transform: rotate(${({ isExpanded }) => isExpanded ? 0 : -90}deg);
32 | transform-origin: center;
33 |
34 | ${({ isExpanded}) => isExpanded && `
35 | top: 7px;
36 | left: 0;
37 | `}
38 | `;
39 |
40 | const NodeName = styled.span`
41 | background-color: ${({ theme }) => theme.node.name.background};
42 | color: ${({ theme }) => theme.node.name.foreground};
43 | cursor: pointer;
44 | `;
45 |
46 | export default class Node extends React.PureComponent {
47 | static propTypes = {
48 | name: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
49 | value: PropTypes.any.isRequired
50 | }
51 |
52 | state = {
53 | isExpanded: false
54 | }
55 |
56 | handleClick = (event) => {
57 | event.stopPropagation();
58 | this.setState(({ isExpanded }) => ({ isExpanded: !isExpanded }));
59 | }
60 |
61 | renderChildren = () => {
62 | const { children, value } = this.props;
63 | if (children) return children;
64 |
65 | return (
66 |
67 | { renderData(value) }
68 |
69 | );
70 | }
71 |
72 | render() {
73 | const { isExpanded } = this.state;
74 | const { children, name, value } = this.props;
75 | const isExpandable = (Array.isArray(value)) || (value instanceof Object);
76 | const hasBlueValue = Array.isArray(value) || (typeof value === 'function');
77 |
78 | return (
79 |
80 | { isExpandable && () }
81 | { name }:
82 | { getValueExcerpt(value) }
83 | { isExpanded && this.renderChildren() }
84 |
85 | );
86 | }
87 | }
--------------------------------------------------------------------------------
/src/components/Panel.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const Panel = styled.ul`
5 | margin: 1rem;
6 | padding: 1rem;
7 |
8 | background-color: ${({ theme }) => theme.panel.background};
9 | border-radius: 3px;
10 | box-shadow:
11 | 0 2px 12px rgba(0, 0, 0, .2),
12 | inset 0 0 0 1px ${({ theme }) => theme.panel.borderColor},
13 | inset 0 0 0 2px rgba(255, 255, 255, .2);
14 |
15 | color: ${({ theme }) => theme.panel.foreground};
16 | line-height: 1.4;
17 | `;
18 |
19 | export default Panel;
20 |
--------------------------------------------------------------------------------
/src/components/PrimitiveValue.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export default styled.span`
4 | background-color: ${({ name, theme }) => theme.primitives[name].background};
5 | color: ${({ name, theme }) => theme.primitives[name].foreground};
6 | `;
7 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export Panel from './Panel';
2 | export List from './List';
3 | export Node from './Node';
4 | export PrimitiveValue from './PrimitiveValue';
5 |
--------------------------------------------------------------------------------
/src/getValueExcerpt.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PrimitiveValue } from './components';
3 |
4 | export default function getValueExcerpt(value) {
5 | if (typeof value === 'string') {
6 | return "{value}"
7 | }
8 |
9 | if (typeof value === 'boolean') {
10 | return {value.toString()};
11 | }
12 |
13 | if (typeof value === 'function') {
14 | return {value.name || 'fn'}();
15 | }
16 |
17 | if (Array.isArray(value)) {
18 | return Array[{value.length}];
19 | }
20 |
21 | if (value instanceof Object) {
22 | return {…};
23 | }
24 |
25 | if (!isNaN(value)) {
26 | return {value};
27 | }
28 |
29 |
30 | return 'null';
31 | }
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { ThemeProvider } from 'styled-components';
4 | import * as themes from './themes';
5 | import { Panel, Node } from './components';
6 | import renderData from './renderData';
7 |
8 | export default class Preview extends React.Component {
9 | static propTypes = {
10 | data: PropTypes.any.isRequired,
11 | theme: PropTypes.string
12 | }
13 |
14 | static defaultProps = {
15 | theme: 'dark'
16 | }
17 |
18 | render() {
19 | const { data } = this.props;
20 | console.log(data);
21 |
22 | return (
23 |
24 |
25 | { renderData(data) }
26 |
27 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/renderData.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { List, Node } from './components';
3 |
4 | export default function renderData(data) {
5 | if (Array.isArray(data)) {
6 | return renderArray(data);
7 | }
8 |
9 | if (typeof data === 'function') {
10 | return renderFunction(data);
11 | }
12 |
13 | if (data instanceof Object) {
14 | return renderObject(data);
15 | }
16 | }
17 |
18 | function renderArray(data) {
19 | if (data.length === 0) {
20 | return (Empty array
);
21 | }
22 |
23 | return data.map((value, index) => {
24 | return (
25 |
29 | );
30 | });
31 | }
32 |
33 | function renderObject(data) {
34 | return Object.keys(data).map((keyName, index) => {
35 | return (
36 |
40 | );
41 | });
42 | }
43 |
44 | function renderFunction(data) {
45 | const { prototype } = data;
46 |
47 | return (
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | }
60 |
--------------------------------------------------------------------------------
/src/themes/dark.js:
--------------------------------------------------------------------------------
1 | export default {
2 | panel: {
3 | foreground: '#AFCCD5',
4 | background: '#243239',
5 | borderColor: '#243239'
6 | },
7 | primitives: {
8 | string: {
9 | foreground: '#FF8B6E'
10 | },
11 | boolean: {
12 | foreground: '#FF8B6E'
13 | },
14 | number: {
15 | foreground: '#FF8B6E'
16 | },
17 | array: {
18 | foregroud: '#79ABFA'
19 | },
20 | object: {
21 | foreground: '#FF8B6E'
22 | },
23 | function: {
24 | foreground: '#79ABFA'
25 | }
26 | },
27 | node: {
28 | arrowBackground: 'rgba(255, 255, 255, .2)',
29 | name: {
30 | foreground: '#7AD9ED'
31 | }
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/src/themes/index.js:
--------------------------------------------------------------------------------
1 | export dark from './dark';
2 | export light from './light';
3 |
--------------------------------------------------------------------------------
/src/themes/light.js:
--------------------------------------------------------------------------------
1 | export default {
2 | panel: {
3 | foreground: '#888',
4 | background: '#f9f9f9',
5 | borderColor: '#ddd',
6 | },
7 | primitives: {
8 | string: {
9 | foreground: '#F44C4F'
10 | },
11 | boolean: {
12 | foreground: '#F44C4F'
13 | },
14 | number: {
15 | foreground: '#F44C4F'
16 | },
17 | array: {
18 | foreground: '#636DDC'
19 | },
20 | object: {
21 | foreground: '#F44C4F'
22 | },
23 | function: {
24 | foreground: '#636DDC'
25 | }
26 | },
27 | node: {
28 | arrowBackground: 'rgba(0, 0, 0, .2)',
29 | name: {
30 | foreground: '#444'
31 | }
32 | }
33 | };
34 |
35 |
--------------------------------------------------------------------------------
/stories/Example.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Preview from '../src';
3 |
4 | class UserImage {}
5 |
6 | const data = {
7 | firstName: 'John',
8 | lastName: 'Maverick',
9 | isAuthenticated: true,
10 | image: UserImage,
11 | logout() {},
12 | orders: [
13 | {
14 | id: 1,
15 | price: 1230,
16 | status: 'NEW',
17 | reorder: function() {}
18 | },
19 | {
20 | id: 2,
21 | price: 3400,
22 | status: 'CLOSED',
23 | reorder: function() {}
24 | }
25 | ],
26 | settings: {
27 | isBusiness: false
28 | }
29 | };
30 |
31 | export default class Example extends React.Component {
32 | render() {
33 | return (
34 |
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/stories/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { storiesOf } from '@storybook/react';
3 | import './styles.css';
4 |
5 | import Example from './Example';
6 |
7 | storiesOf('Welcome', module)
8 | .add('Example', () => );
9 |
--------------------------------------------------------------------------------
/stories/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-size: 14px;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
4 | }
5 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 |
4 | const PRODUCTION = (process.env.NODE_ENV === 'production');
5 |
6 | module.exports = {
7 | mode: process.env.NODE_ENV,
8 | target: 'web',
9 | entry: {
10 | index: path.resolve(__dirname, 'src/index.jsx')
11 | },
12 | externals: {
13 | react: 'umd react'
14 | },
15 | output: {
16 | path: path.resolve(__dirname, 'lib'),
17 | filename: '[name].js',
18 | library: 'reactDataPreview',
19 | libraryTarget: 'umd',
20 | umdNamedDefine: true
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.jsx?$/,
26 | exclude: /node_module/,
27 | use: ['babel-loader']
28 | }
29 | ]
30 | },
31 | resolve: {
32 | extensions: ['.js', '.jsx']
33 | }
34 | };
35 |
--------------------------------------------------------------------------------