",
25 | "license": "MIT",
26 | "dependencies": {
27 | "@fortawesome/fontawesome-svg-core": "^1.2.28",
28 | "@fortawesome/free-solid-svg-icons": "^5.13.0",
29 | "@fortawesome/react-fontawesome": "^0.1.9",
30 | "@material-ui/core": "^4.11.3",
31 | "@material-ui/icons": "^4.11.2",
32 | "@trendmicro/react-sidenav": "^0.5.0",
33 | "bootstrap": "^4.5.0",
34 | "file-loader": "^6.0.0",
35 | "node-sass": "^4.14.1",
36 | "ramda": "^0.26.1",
37 | "react-bootstrap": "^1.0.1",
38 | "react-grid-layout": "^1.2.0",
39 | "react-jss": "^10.1.1",
40 | "react-slick": "^0.26.1",
41 | "sass-loader": "^8.0.2",
42 | "slick-carousel": "^1.8.1",
43 | "styled-components": "^5.1.0"
44 | },
45 | "devDependencies": {
46 | "@babel/core": "^7.5.4",
47 | "@babel/plugin-proposal-object-rest-spread": "^7.5.4",
48 | "@babel/preset-env": "^7.5.4",
49 | "@babel/preset-react": "^7.0.0",
50 | "babel-eslint": "^10.0.2",
51 | "babel-loader": "^8.0.6",
52 | "copyfiles": "^2.1.1",
53 | "css-loader": "^3.5.3",
54 | "eslint": "^6.0.1",
55 | "eslint-config-prettier": "^6.0.0",
56 | "eslint-plugin-import": "^2.18.0",
57 | "eslint-plugin-react": "^7.14.2",
58 | "npm": "^6.14.6",
59 | "prop-types": "^15.7.2",
60 | "react": "^16.13.1",
61 | "react-docgen": "^4.1.1",
62 | "react-dom": "^16.8.6",
63 | "style-loader": "^0.23.1",
64 | "styled-jsx": "^3.2.1",
65 | "webpack": "4.36.1",
66 | "webpack-cli": "3.3.6",
67 | "webpack-serve": "3.1.0"
68 | },
69 | "engines": {
70 | "node": ">=8.11.0",
71 | "npm": ">=6.1.0"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/pytest.ini:
--------------------------------------------------------------------------------
1 | [pytest]
2 | testpaths = tests/
3 | addopts = -rsxX -vv
4 | log_format = %(asctime)s | %(levelname)s | %(name)s:%(lineno)d | %(message)s
5 | log_cli_level = ERROR
6 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | appdirs==1.4.4
2 | attrs==19.3.0
3 | autopep8==1.5.2
4 | black==19.10b0
5 | bleach==3.3.0
6 | Brotli==1.0.7
7 | certifi==2020.4.5.1
8 | cffi==1.14.0
9 | chardet==3.0.4
10 | click==7.1.2
11 | coloredlogs==14.0
12 | cryptography==3.3.2
13 | dash==1.12.0
14 | dash-core-components==1.10.0
15 | dash-dangerously-set-inner-html==0.0.2
16 | dash-flow-example==0.0.5
17 | dash-html-components==1.0.3
18 | dash-renderer==1.4.1
19 | dash-table==4.7.0
20 | docutils==0.16
21 | entrypoints==0.3
22 | fire==0.2.1
23 | flake8==3.7.9
24 | Flask==1.1.2
25 | Flask-Compress==1.5.0
26 | future==0.18.2
27 | humanfriendly==8.2
28 | idna==2.9
29 | itsdangerous==1.1.0
30 | jeepney==0.4.3
31 | Jinja2==2.11.2
32 | keyring==21.2.1
33 | MarkupSafe==1.1.1
34 | mccabe==0.6.1
35 | mock==4.0.1
36 | numpy==1.18.4
37 | packaging==20.3
38 | pandas==1.0.3
39 | pathspec==0.8.0
40 | pkginfo==1.5.0.1
41 | plotly==4.7.1
42 | pycodestyle==2.5.0
43 | pycparser==2.20
44 | pyflakes==2.1.1
45 | Pygments==2.6.1
46 | pyparsing==2.4.7
47 | python-dateutil==2.8.1
48 | pytz==2020.1
49 | PyYAML==5.3
50 | readme-renderer==26.0
51 | regex==2020.5.14
52 | requests==2.23.0
53 | requests-toolbelt==0.9.1
54 | retrying==1.3.3
55 | SecretStorage==3.1.2
56 | six==1.14.0
57 | termcolor==1.1.0
58 | toml==0.10.1
59 | tqdm==4.46.0
60 | twine==3.1.1
61 | typed-ast==1.4.1
62 | urllib3==1.25.9
63 | webencodings==0.5.1
64 | Werkzeug==1.0.1
65 |
--------------------------------------------------------------------------------
/review_checklist.md:
--------------------------------------------------------------------------------
1 | # Code Review Checklist
2 |
3 | ## Code quality & design
4 |
5 | - Is your code clear? If you had to go back to it in a month, would you be happy to? If someone else had to contribute to it, would they be able to?
6 |
7 | A few suggestions:
8 |
9 | - Make your variable names descriptive and use the same naming conventions throughout the code.
10 |
11 | - For more complex pieces of logic, consider putting a comment, and maybe an example.
12 |
13 | - In the comments, focus on describing _why_ the code does what it does, rather than describing _what_ it does. The reader can most likely read the code, but not necessarily understand why it was necessary.
14 |
15 | - Don't overdo it in the comments. The code should be clear enough to speak for itself. Stale comments that no longer reflect the intent of the code can hurt code comprehension.
16 |
17 | * Don't repeat yourself. Any time you see that the same piece of logic can be applied in multiple places, factor it out into a function, or variable, and reuse that code.
18 | * Scan your code for expensive operations (large computations, DOM queries, React re-renders). Have you done your possible to limit their impact? If not, it is going to slow your app down.
19 | * Can you think of cases where your current code will break? How are you handling errors? Should the user see them as notifications? Should your app try to auto-correct them for them?
20 |
21 | ## Component API
22 |
23 | - Have you tested your component on the Python side by creating an app in `usage.py` ?
24 |
25 | Do all of your component's props work when set from the back-end?
26 |
27 | Should all of them be settable from the back-end or are some only relevant to user interactions in the front-end?
28 |
29 | - Have you provided some basic documentation about your component? The Dash community uses [react docstrings](https://github.com/plotly/dash-docs/blob/master/tutorial/plugins.py#L45) to provide basic information about dash components. Take a look at this [Checklist component example](https://github.com/plotly/dash-core-components/blob/master/src/components/Checklist.react.js) and others from the dash-core-components repository.
30 |
31 | At a minimum, you should describe what your component does, and describe its props and the features they enable.
32 |
33 | Be careful to use the correct formatting for your docstrings for them to be properly recognized.
34 |
35 | ## Tests
36 |
37 | - The Dash team uses integration tests extensively, and we highly encourage you to write tests for the main functionality of your component. In the `tests` folder of the boilerplate, you can see a sample integration test. By launching it, you will run a sample Dash app in a browser. You can run the test with:
38 | ```
39 | python -m tests.test_render
40 | ```
41 | [Browse the Dash component code on GitHub for more examples of testing.](https://github.com/plotly/dash-core-components)
42 |
43 | ## Ready to publish? Final scan
44 |
45 | - Take a last look at the external resources that your component is using. Are all the external resources used [referenced in `MANIFEST.in`](https://github.com/plotly/dash-docs/blob/0b2fd8f892db720a7f3dc1c404b4cff464b5f8d4/tutorial/plugins.py#L55)?
46 |
47 | - [You're ready to publish!](https://github.com/plotly/dash-component-boilerplate/blob/master/%7B%7Bcookiecutter.project_shortname%7D%7D/README.md#create-a-production-build-and-publish)
48 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | from setuptools import setup
4 |
5 | # read the contents of your README file
6 | from os import path
7 | this_directory = path.abspath(path.dirname(__file__))
8 | with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
9 | long_description = f.read()
10 |
11 | with open('package.json') as f:
12 | package = json.load(f)
13 |
14 | package_name = package["name"].replace(" ", "_").replace("-", "_")
15 |
16 | setup(
17 | name=package_name,
18 | version=package["version"],
19 | author=package['author'],
20 | packages=[package_name],
21 | include_package_data=True,
22 | license=package['license'],
23 | description=package.get('description', package_name),
24 | long_description=long_description,
25 | long_description_content_type='text/markdown',
26 | install_requires=[],
27 | classifiers = [
28 | 'Framework :: Dash',
29 | ],
30 | )
31 |
--------------------------------------------------------------------------------
/src/demo/App.js:
--------------------------------------------------------------------------------
1 | /* eslint no-magic-numbers: 0 */
2 | import React, { Component } from 'react';
3 |
4 | // import { Carousel } from '../lib';
5 | // import GridLayoutComponent from '../lib/components/GridLayoutComponent/GridLayoutComponent.react';
6 | // import TrichResponsiveGridLayout from '../lib/components/GridLayoutComponent/ResponsiveGridLayout.react';
7 | // import RatingComp from '../lib/components/Rating/Rating.react';
8 | import ResponsiveGrid from '../lib/components/ResponsiveGrid/ResponsiveGrid.react';
9 |
10 | class App extends Component {
11 |
12 |
13 | render() {
14 | const layouts = {xl:[{ i: 'a', x: 2, y: 5, w: 3, h: 10, minH: 3, maxH: 15 },
15 | { i: 'b', x: 3, y: 0, w: 3, h: 4, minH: 3, maxH: 10, minW: 2, maxW: 4 },
16 | { i: 'c', x: 7, y: 0, w: 6, h: 2, minH: 3, maxH: 20},
17 | { i: 'd', x: 2, y: 4, w: 6, h: 2, minH: 3, maxH: 20}],
18 | lg: [{ i: 'a', x: 0, y: 0, w: 3, h: 7, minH: 3, maxH: 15 },
19 | { i: 'b', x: 3, y: 0, w: 3, h: 4, minH: 3, maxH: 10, minW: 2, maxW: 4 },
20 | { i: 'c', x: 7, y: 0, w: 6, h: 10, minH: 3, maxH: 20},
21 | { i: 'd', x: 2, y: 4, w: 6, h: 10, minH: 3, maxH: 20}],
22 | md: [{ i: 'a', x: 0, y: 0, w: 5, h: 4 },
23 | { i: 'b', x: 1, y: 2, w: 5, h: 2, minW: 2, maxW: 4 },
24 | { i: 'c', x: 2, y: 0, w: 3, h: 7 },
25 | { i: 'd', x: 3, y: 4, w: 6, h: 4, minH: 3, maxH: 15}],
26 |
27 | sm: [{ i: 'a', x: 0, y: 0, w: 4, h: 4 },
28 | { i: 'b', x: 1, y: 0, w: 3, h: 3, minW: 2, maxW: 5 },
29 | { i: 'c', x: 2, y: 0, w: 5, h: 5 },
30 | { i: 'd', x: 4, y: 3, w: 3, h: 7, minH: 3, maxH: 8}]}
31 |
32 |
33 | return (
34 |
35 |
39 |
41 |
42 |
Draggable Area
44 |
ni hao
45 |
46 |
47 | Bon jour
49 | olá
51 | HI
53 |
54 |
55 | )
56 | }
57 | }
58 |
59 | export default App;
60 |
--------------------------------------------------------------------------------
/src/demo/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(, document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/src/lib/components/Card/Card.react.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import Badge from 'react-bootstrap/Badge'
4 | import 'bootstrap/dist/css/bootstrap.min.css';
5 | import './Card.scss'
6 |
7 | /**
8 | * A simple card with an image, title, description,
9 | * badges and link for github with icon, all this arguments are optional.
10 | */
11 |
12 | export default class Card extends Component {
13 |
14 | render() {
15 |
16 | const {
17 | id,
18 | style,
19 | className,
20 | link,
21 | openNewTab,
22 | image,
23 | title,
24 | description,
25 | badges,
26 | git,
27 | dark
28 | } = this.props
29 |
30 | return (
31 |
32 |
33 | {image &&
34 |
35 |
36 |

37 |
38 |
39 | }
40 |
41 | {title &&
42 |
{title}
43 | }
44 | {description &&
45 |
{description}
46 | }
47 |
48 |
49 |
50 | {badges && badges.map((item, i) => (
51 |
52 | {item}
53 |
54 |
55 | ))}
56 |
57 | {git &&
58 |
63 | }
64 |
65 |
66 |
67 | );
68 | }
69 |
70 | }
71 |
72 | Card.defaultProps = {
73 | id: null,
74 | style: null,
75 | className: '',
76 | link: null,
77 | image: null,
78 | title: null,
79 | description: null,
80 | badges: null,
81 | git: null,
82 | dark: false,
83 | openNewTab: true
84 | };
85 |
86 | Card.propTypes = {
87 | /**
88 | * Id of the element
89 | */
90 | id: PropTypes.string,
91 |
92 | /**
93 | * Style class of the component.
94 | */
95 | className: PropTypes.string,
96 |
97 | /**
98 | * Inline style of the component.
99 | */
100 | style: PropTypes.object,
101 |
102 | /**
103 | * link to redirect when the user clicks on the image
104 | */
105 | link: PropTypes.string,
106 |
107 | /**
108 | * image that will display on card
109 | */
110 | image: PropTypes.string,
111 |
112 | /**
113 | * title of the card
114 | */
115 | title: PropTypes.string,
116 |
117 | /**
118 | * description of the card
119 | */
120 | description: PropTypes.string,
121 |
122 | /**
123 | * list of strings to display in badges, to work porperly put up to 4 or 5
124 | */
125 | badges: PropTypes.array,
126 |
127 | /**
128 | * github URL, is not required, only if you want to
129 | */
130 | git: PropTypes.string,
131 |
132 | /**
133 | * theme color of the card, that for default is light
134 | */
135 | dark: PropTypes.bool,
136 |
137 | /**
138 | * Open card link in a new tab
139 | */
140 | openNewTab: PropTypes.bool,
141 | };
142 |
--------------------------------------------------------------------------------
/src/lib/components/Card/Card.scss:
--------------------------------------------------------------------------------
1 | .trich_card {
2 |
3 | .card {
4 | height: 100%;
5 | border: none;
6 |
7 | &:focus {
8 | outline: none;
9 | }
10 |
11 | .badge {
12 | text-transform: capitalize;
13 | }
14 | }
15 |
16 | .padding16 {
17 | padding: 16px;
18 | }
19 |
20 | .radius8 {
21 | border-radius: 8px;
22 | }
23 |
24 | .bottom16 {
25 | margin-bottom: 16px;
26 | }
27 |
28 | .font-sm {
29 | font-size: 16px;
30 | }
31 | .font-xs {
32 | font-size: 14px;
33 | }
34 |
35 | .bold {
36 | font-weight: 700;
37 | }
38 |
39 | .uppercase {
40 | text-transform: uppercase;
41 | }
42 |
43 | .flex_row_btw {
44 | display: flex;
45 | flex-direction: row;
46 | justify-content: space-between;
47 | }
48 |
49 | .inline-block {
50 | display: inline-block;
51 | }
52 |
53 | .self_center {
54 | align-self: center;
55 | }
56 |
57 | &.light {
58 | .card {
59 | background-color: #f5f5f5;
60 | color: #333333;
61 |
62 | .badge {
63 | background-color: #2b7178;
64 | color: #fff;
65 | }
66 |
67 | .git {
68 | color: #c75146;
69 | }
70 | }
71 | }
72 |
73 | &.dark {
74 | .card {
75 | background-color: #333333;
76 | color: #fff;
77 |
78 | .badge {
79 | background-color: #77aeb2;
80 | color: #333333;
81 | }
82 |
83 | .git {
84 | color: #dc7d75;
85 | }
86 | }
87 | }
88 | }
89 |
90 | /* media queries */
91 |
92 | @media only screen and (min-width: 576px) {
93 |
94 | .trich_card_text {
95 | height: calc(100% - 190px);
96 | }
97 | }
98 |
99 | @media only screen and (min-width: 768px) {
100 |
101 | .trich_card {
102 | .card {
103 | min-width: 332px;
104 | max-width: 332px;
105 | height: 100%;
106 |
107 | .trich_card_img {
108 | height: 200px;
109 |
110 | img {
111 | width: 100%;
112 |
113 | &:hover {
114 | cursor: pointer;
115 | }
116 | }
117 | }
118 |
119 | .trich_card_text {
120 | height: calc(100% - 252px);
121 | }
122 |
123 | .trich_card_footer {
124 | height: 24px;
125 | }
126 |
127 | }
128 |
129 | &.light {
130 | .card:hover {
131 | filter: drop-shadow(2px 4px 6px rgb(143, 143, 143));
132 | }
133 | }
134 |
135 | &.dark {
136 | .card:hover {
137 | filter: drop-shadow(2px 4px 6px black);
138 | }
139 | }
140 | }
141 |
142 |
143 | }
144 |
145 | @media screen and (max-width: 767px) {
146 |
147 | .trich_card {
148 |
149 | .trich_card_footer {
150 | height: 24px;
151 | }
152 |
153 | .trich_card_img img {
154 | width: 100%;
155 | }
156 | }
157 |
158 |
159 | }
--------------------------------------------------------------------------------
/src/lib/components/Carousel/Carousel.react.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import Slider from "react-slick";
4 | import './Carousel.scss'
5 |
6 | /**
7 | * Carousel is an carousel component.
8 | * It takes some setting properties
9 | * You put how many blocks of divs you want inside it,
10 | * and this div turn into slides
11 | */
12 |
13 | export default class Carousel extends Component {
14 | render() {
15 |
16 | const {
17 | dots,
18 | arrows,
19 | infinite,
20 | autoplay,
21 | speed,
22 | slides_to_show,
23 | slides_to_scroll,
24 | center_mode,
25 | center_padding,
26 | swipe_to_slide,
27 | variable_width,
28 | responsive,
29 | vertical,
30 | className,
31 | style,
32 | id,
33 | children } = this.props
34 |
35 | const settings = {
36 | dots: dots || false,
37 | arrows: arrows || true,
38 | infinite: infinite || true,
39 | autoplay: autoplay || false,
40 | speed: speed || 1000,
41 | slidesToShow: slides_to_show || 3,
42 | slidesToScroll: slides_to_scroll || 1,
43 | centerMode: center_mode || false,
44 | centerPadding: center_padding || '50px',
45 | swipeToSlide: swipe_to_slide || true,
46 | variableWidth: variable_width || false,
47 | responsive: responsive || null,
48 | vertical: vertical || false,
49 |
50 | };
51 |
52 | return (
53 |
54 |
55 | {children}
56 |
57 |
58 | );
59 | }
60 | }
61 |
62 | Carousel.defaultProps = {
63 |
64 | };
65 |
66 | Carousel.propTypes = {
67 | /**
68 | * Dots under carousel
69 | */
70 | dots: PropTypes.bool,
71 |
72 | /**
73 | * Arrows to control carousel
74 | */
75 | arrows: PropTypes.bool,
76 |
77 | /**
78 | * If the carousel content will repeat in a loop
79 | */
80 | infinite: PropTypes.bool,
81 |
82 | /**
83 | * If the carousel will start to play automatically
84 | */
85 | autoplay: PropTypes.bool,
86 |
87 | /**
88 | * Speed of autoplay
89 | */
90 | speed: PropTypes.number,
91 |
92 | /**
93 | * How many slides you want to show
94 | */
95 | slides_to_show: PropTypes.number,
96 |
97 | /**
98 | * How many slides will scroll when you swipe the carousel
99 | */
100 | slides_to_scroll: PropTypes.number,
101 |
102 | /**
103 | * To centralize the carousel
104 | */
105 | center_mode: PropTypes.bool,
106 |
107 | /**
108 | * Padding on the sides
109 | */
110 | center_padding: PropTypes.string,
111 |
112 | /**
113 | * If you can slide to scroll the slides
114 | */
115 | swipe_to_slide: PropTypes.bool,
116 |
117 | /**
118 | * The slides width varies according to the screen size
119 | */
120 | variable_width: PropTypes.bool,
121 |
122 | /**
123 | * Settings of breakpoints
124 | */
125 | responsive: PropTypes.array,
126 |
127 | /**
128 | * If your carousel is vertical
129 | */
130 | vertical: PropTypes.bool,
131 |
132 | /**
133 | * Your carousel is vertical
134 | */
135 | children: PropTypes.node,
136 |
137 | /**
138 | * Inline style of the component.
139 | */
140 | style: PropTypes.object,
141 |
142 | /**
143 | * Style class of the component.
144 | */
145 | className: PropTypes.string,
146 |
147 | /**
148 | * Id of the element
149 | */
150 | id: PropTypes.string
151 |
152 | };
153 |
--------------------------------------------------------------------------------
/src/lib/components/Carousel/Carousel.scss:
--------------------------------------------------------------------------------
1 | $slick-font-path: "~slick-carousel/slick/fonts/";
2 | $slick-loader-path: "~slick-carousel/slick/";
3 |
4 | @import '~slick-carousel/slick/slick', '~slick-carousel/slick/slick-theme';
5 |
6 | .trich_carousel {
7 | .slick-prev {
8 | &:before {
9 | font-family: "Font Awesome 5 Free";
10 | font-weight: 900;
11 | content: '\f053';
12 | color: #232323;
13 | }
14 | }
15 | .slick-next {
16 | &:before {
17 | font-family: "Font Awesome 5 Free";
18 | font-weight: 900;
19 | content: '\f054';
20 | color: #232323;
21 | }
22 | }
23 |
24 | .slick-dots {
25 | button {
26 | color: unset;
27 | &:before {
28 | font-size: 28px;
29 | color: #232323;
30 | }
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/src/lib/components/GridLayoutComponent/GridLayout.scss:
--------------------------------------------------------------------------------
1 | .gridlayout
2 | {
3 | background:lightgrey;
4 | }
--------------------------------------------------------------------------------
/src/lib/components/GridLayoutComponent/GridLayoutComponent.react.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import 'react-grid-layout/css/styles.css'
4 | import 'react-resizable/css/styles.css'
5 | import GridLayout from 'react-grid-layout';
6 | import './GridLayout.scss'
7 |
8 | /**
9 | * GridLayout is an GridLayout component.
10 | * It takes some setting properties
11 | * You put how many blocks of divs you want inside it,
12 | * and this div turn into slides
13 | */
14 |
15 | export default class GridLayoutComponent extends Component {
16 |
17 | render() {
18 |
19 | const { style, className, children, id, layout, total_cols, row_height, width, background_color } = this.props;
20 |
21 | const styles = {
22 | maxWidth: width || '100px',
23 | backgroundColor: background_color || 'lightgray',
24 | margin: '0 auto'
25 | }
26 |
27 | return (
28 |
29 |
30 | {children.map((child, i) => (
31 |
32 | {child}
33 |
34 | ))
35 | }
36 |
37 |
38 | )
39 | }
40 | }
41 |
42 | GridLayoutComponent.defaultProps = {
43 |
44 | };
45 |
46 | GridLayoutComponent.propTypes = {
47 | /**
48 | * Your GridLayout is vertical
49 | */
50 | children: PropTypes.node,
51 |
52 | /**
53 | * Inline style of the component.
54 | */
55 | style: PropTypes.object,
56 |
57 | /**
58 | * Style class of the component.
59 | */
60 | className: PropTypes.string,
61 |
62 | /**
63 | * Id of the element
64 | */
65 | id: PropTypes.string,
66 |
67 | /**
68 | * Initial layout
69 | */
70 | layout: PropTypes.array,
71 |
72 | };
73 |
--------------------------------------------------------------------------------
/src/lib/components/ResponsiveGrid/GridItem.react.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import ReactDOM from "react-dom"
3 | import PropTypes from 'prop-types';
4 |
5 | /**
6 | * A class for displaying an item in a grid
7 | * Designed to be wrapped in a function, similar to a higher-order component. Otherwise
8 | * the layout will render incorrectly
9 | */
10 | class GridItem extends Component {
11 | constructor(props) {
12 | super(props);
13 | this.relayout = this.relayout.bind(this);
14 | this.relayoutChildren = this.relayoutChildren.bind(this);
15 | }
16 |
17 | componentDidUpdate(prevProps) {
18 | let updated = false;
19 | // Ensure that the layout has in fact changed before calling relayout
20 | const keys = ['w','h','x','y'];
21 |
22 | for (let key of keys) {
23 | if(this.props.layout[key] !== prevProps.layout[key]) {
24 | updated = true;
25 | }
26 | }
27 |
28 | if (updated) {
29 | this.relayoutChildren();
30 | }
31 | }
32 |
33 | componentDidMount() {
34 | this.relayoutChildren();
35 | window.addEventListener("resize", this.relayoutChildren);
36 | }
37 |
38 | componentWillUnmount() {
39 | window.removeEventListener("resize", this.relayoutChildren);
40 | }
41 |
42 | /**
43 | * Iterate over children and trigger a relayout event
44 | */
45 | relayoutChildren() {
46 |
47 | function flattenReactChildrenToArray(nodeChildren, accumulated = []) {
48 | React.Children.forEach(nodeChildren, (childNode) => {
49 | accumulated.push(childNode);
50 | if (childNode && childNode.props && childNode.props.children) {
51 | flattenReactChildrenToArray(childNode.props.children, accumulated);
52 | }
53 | });
54 | return accumulated;
55 | }
56 |
57 | // Relayout after a time period so that the rest of the layout can render properly
58 | window.setTimeout(() => {
59 | let flat = flattenReactChildrenToArray(this.props.children);
60 | flat.forEach(this.relayout);
61 | }, 50);
62 | }
63 |
64 | /**
65 | * Relayout the Plotly objects; modify their sizes to fit inside the columns
66 | */
67 | relayout(child) {
68 |
69 | const rootNode = ReactDOM.findDOMNode(this).parentElement; //gets the grid item node so we can calculate the correct plot height
70 |
71 | function outerHeight(el) {
72 | var height = el.offsetHeight;
73 | var style = getComputedStyle(el);
74 |
75 | height += parseInt(style.marginTop) + parseInt(style.marginBottom);
76 | return height;
77 | }
78 |
79 | function innerHeight(el) {
80 | var height = el.clientHeight;
81 | var style = getComputedStyle(el);
82 |
83 | height -= (parseInt(style.paddingTop) + parseInt(style.paddingBottom) + parseInt(style.borderTopWidth) + parseInt(style.borderBottomWidth));
84 | return height;
85 | }
86 |
87 | function innerWidth(el) {
88 | var width = el.clientWidth;
89 | var style = getComputedStyle(el);
90 |
91 | width -= (parseInt(style.paddingLeft) + parseInt(style.paddingRight) + parseInt(style.borderLeftWidth) + parseInt(style.borderRightWidth));
92 | return width;
93 | }
94 |
95 | if( (child.props && child.props.id) || (child.props._dashprivate_layout && child.props._dashprivate_layout.props.id)) {
96 | const id = child.props.id || child.props._dashprivate_layout.props.id;
97 | const elem = document.getElementById(id);
98 |
99 | // filter only plotly charts
100 | // TODO height calculation only works if plot is at the immediate level
101 |
102 | function updateElem(elem) {
103 | if(elem && elem.className && typeof elem.className === 'string' && elem.className.includes('js-plotly-plot')) {
104 | const parent = elem.parentElement; //div
105 | let height = innerHeight(rootNode);
106 | let width = innerWidth(rootNode);
107 |
108 | // If there are other elements in the GridItem, don't allow the
109 | // Plotly chart to completely fill the space
110 | for (let c of parent.children) {
111 | if(c !== elem) {
112 | height = height - outerHeight(c);
113 | }
114 | }
115 |
116 | const update = {
117 | width: width - 15,
118 | height: height - 15, //TODO does not entirely fit.
119 | };
120 |
121 | try {
122 | window.Plotly.relayout(elem, update);
123 | } catch(e) {
124 | // Log the error
125 | window.console.log(e);
126 | }
127 | } else if(child.props.onResize && typeof v === 'function') {
128 | child.props.onResize(); //TODO not really useful unless size is passed
129 | }
130 | }
131 |
132 | function df(node) {
133 | updateElem(node);
134 | if(node.children) {
135 | for(var i = 0; i < node.children.length; i++)
136 | df(node.children[i]);
137 | }
138 | }
139 |
140 | df(elem);
141 | }
142 | }
143 |
144 | render() {
145 | return this.props.children;
146 | }
147 | }
148 |
149 | GridItem.propTypes = {
150 | /**
151 | * An identifier for the component.
152 | * Synonymous with `key`, but `key` cannot be specified as
153 | * a PropType without causing errors. A caveat to this is that when using
154 | * the component in pure React (as opposed to via Dash), both `i` and `key`
155 | * must be specified
156 | */
157 | i: PropTypes.string.isRequired,
158 |
159 | /**
160 | * A list of child elements to place inside the grid ite,
161 | */
162 | children: PropTypes.node,
163 |
164 | /**
165 | * An object describing the layout of the element
166 | */
167 | layout: PropTypes.shape({
168 | /**
169 | * The x-value of the element location, in grid units
170 | */
171 | x: PropTypes.number.isRequired,
172 |
173 | /**
174 | * The y-value of the element location, in grid units
175 | */
176 | y: PropTypes.number.isRequired,
177 |
178 | /**
179 | * The width of the element, in grid units
180 | */
181 | w: PropTypes.number.isRequired,
182 |
183 | /**
184 | * The height of the element, in grid units
185 | */
186 | h: PropTypes.number.isRequired,
187 |
188 | /**
189 | * The minimum width of the element, in grid units
190 | */
191 | minW: PropTypes.number,
192 |
193 | /**
194 | * The maximum width of the element, in grid units
195 | */
196 | maxW: PropTypes.number,
197 |
198 | /**
199 | * The minimum height of the element, in grid units
200 | */
201 | minH: PropTypes.number,
202 |
203 | /**
204 | * The maximum height of the element, in grid units
205 | */
206 | maxH: PropTypes.number,
207 |
208 | /**
209 | * Is static: if true, the element is not resizable or draggable
210 | */
211 | static: PropTypes.bool,
212 |
213 | /**
214 | * If false, element can not be dragged
215 | */
216 | isDraggable: PropTypes.bool,
217 |
218 | /**
219 | * If false, the element can not be resized
220 | */
221 | isResizable: PropTypes.bool
222 | }),
223 |
224 | /**
225 | * Dash-assigned callback that should be called whenever any of the properties change
226 | */
227 | setProps: PropTypes.func
228 | };
229 |
230 | export default GridItem;
231 |
--------------------------------------------------------------------------------
/src/lib/components/ResponsiveGrid/GridLayoutResponsive.scss:
--------------------------------------------------------------------------------
1 | img {
2 | max-width: 100%;
3 | max-height: 100%;
4 | }
5 |
6 | .react-grid-layout {
7 | position: relative;
8 | transition: height 200ms ease;
9 | }
10 | .react-grid-item {
11 | transition: all 200ms ease;
12 | transition-property: left, top;
13 | }
14 | .react-grid-item.cssTransforms {
15 | transition-property: transform;
16 | }
17 | .react-grid-item.resizing {
18 | z-index: 1;
19 | will-change: width, height;
20 | }
21 |
22 | .react-grid-item.react-draggable-dragging {
23 | transition: none;
24 | z-index: 3;
25 | will-change: transform;
26 | }
27 |
28 | .react-grid-item.react-grid-placeholder {
29 | background: red;
30 | opacity: 0.2;
31 | transition-duration: 100ms;
32 | z-index: 2;
33 | -webkit-user-select: none;
34 | -moz-user-select: none;
35 | -ms-user-select: none;
36 | -o-user-select: none;
37 | user-select: none;
38 | }
39 |
40 | .react-grid-item > .react-resizable-handle {
41 | position: absolute;
42 | width: 20px;
43 | height: 20px;
44 | bottom: 0;
45 | right: 0;
46 | background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/Pg08IS0tIEdlbmVyYXRvcjogQWRvYmUgRmlyZXdvcmtzIENTNiwgRXhwb3J0IFNWRyBFeHRlbnNpb24gYnkgQWFyb24gQmVhbGwgKGh0dHA6Ly9maXJld29ya3MuYWJlYWxsLmNvbSkgLiBWZXJzaW9uOiAwLjYuMSAgLS0+DTwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DTxzdmcgaWQ9IlVudGl0bGVkLVBhZ2UlMjAxIiB2aWV3Qm94PSIwIDAgNiA2IiBzdHlsZT0iYmFja2dyb3VuZC1jb2xvcjojZmZmZmZmMDAiIHZlcnNpb249IjEuMSINCXhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiDQl4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjZweCIgaGVpZ2h0PSI2cHgiDT4NCTxnIG9wYWNpdHk9IjAuMzAyIj4NCQk8cGF0aCBkPSJNIDYgNiBMIDAgNiBMIDAgNC4yIEwgNCA0LjIgTCA0LjIgNC4yIEwgNC4yIDAgTCA2IDAgTCA2IDYgTCA2IDYgWiIgZmlsbD0iIzAwMDAwMCIvPg0JPC9nPg08L3N2Zz4=');
47 | background-position: bottom right;
48 | padding: 0 3px 3px 0;
49 | background-repeat: no-repeat;
50 | background-origin: content-box;
51 | box-sizing: border-box;
52 | cursor: se-resize;
53 | }
54 |
55 | .react-resizable {
56 | position: relative;
57 | }
58 |
59 | .react-resizable-handle {
60 | position: absolute;
61 | width: 20px;
62 | height: 20px;
63 | bottom: 0;
64 | right: 0;
65 | background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI2cHgiIGhlaWdodD0iNnB4Ij48ZyBvcGFjaXR5PSIwLjMwMiI+PHBhdGggZD0iTSA2IDYgTCAwIDYgTCAwIDQuMiBMIDQgNC4yIEwgNC4yIDQuMiBMIDQuMiAwIEwgNiAwIEwgNiA2IEwgNiA2IFoiIGZpbGw9IiMwMDAwMDAiLz48L2c+PC9zdmc+');
66 | background-position: bottom right;
67 | padding: 0 3px 3px 0;
68 | background-repeat: no-repeat;
69 | background-origin: content-box;
70 | box-sizing: border-box;
71 | cursor: se-resize;
72 | }
73 |
74 | testing {
75 | background:black;
76 | }
--------------------------------------------------------------------------------
/src/lib/components/ResponsiveGrid/ResponsiveGrid.react.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Responsive, WidthProvider, utils } from "react-grid-layout";
4 | import { unpackChildren, wrapChildren} from '../../utils';
5 |
6 | import './GridLayoutResponsive.scss';
7 |
8 | const type = obj => Object.prototype.toString.call(obj);
9 | const ResponsiveReactGridLayout = WidthProvider(Responsive);
10 |
11 |
12 | /**
13 | * This component is an update of an another component build by Alexander Cabello
14 | *
15 | * The Link to the original component is: https://github.com/AlgorithmHub/dash-responsive-grid-layout
16 | *
17 | * It allows the user to drag, resize and modify the components inside of it.
18 | * To be able to run it, you need to add a layout(one definition to each breakpoint) and a div with the ID (key) that will be mapped as the box inside of the Draggable Component.
19 | *
20 | */
21 |
22 |
23 | export default class ResponsiveGrid extends Component {
24 | constructor(props) {
25 | super(props);
26 |
27 | // Bind `this` to functions so that they can be
28 | // passed as callbacks without scoping issues
29 | this.onLayoutChange = this.onLayoutChange.bind(this);
30 | this.onBreakpointChange = this.onBreakpointChange.bind(this);
31 |
32 | this.state = {
33 | layout: props.layout,
34 | layouts: props.layouts,
35 | breakpoint: props.breakpoint
36 | };
37 |
38 | if(this.props.setProps) {
39 | this.props.setProps({
40 | layout: props.layout,
41 | layouts: props.layouts,
42 | breakpoint: props.breakpoint
43 | });
44 | }
45 | }
46 |
47 | /**
48 | * Callback for the onLayoutChange
49 | */
50 | onLayoutChange(layout, layouts) {
51 | if(this.props.setProps) {
52 | this.props.setProps({
53 | layout: layout,
54 | layouts: layouts
55 | });
56 | } else {
57 | this.setState({
58 | layout: layout,
59 | layouts: layouts
60 | });
61 | }
62 | }
63 |
64 | onBreakpointChange(breakpoint, cols) {
65 | if(this.props.setProps) {
66 | this.props.setProps({breakpoint: breakpoint});
67 | } else {
68 | this.setState({
69 | breakpoint: this.props.breakpoint
70 | });
71 | }
72 | }
73 |
74 | render() {
75 |
76 | let layout, layouts, breakpoint;
77 | if(this.props.setProps) { //note setProps is not set if there are no dash input or state callbacks
78 | layout = this.props.layout;
79 | layouts = this.props.layouts;
80 | breakpoint = this.props.breakpoint;
81 | } else {
82 | layout = this.state.layout;
83 | layouts = this.state.layouts;
84 | breakpoint = this.state.breakpoint;
85 | }
86 |
87 |
88 | const unpackedChildren = unpackChildren(this.props.children);
89 | const wrappedChildren = wrapChildren(unpackedChildren, layouts[breakpoint]);
90 |
91 |
92 | return (
93 |
123 | { wrappedChildren }
124 |
125 | );
126 | }
127 | }
128 |
129 | ResponsiveGrid.propTypes = {
130 |
131 | /**
132 | * The ID used to identify the component in Dash callbacks
133 | */
134 | id: PropTypes.string,
135 |
136 | /**
137 | * A list of renderable child elements, that will be placed inside the grid
138 | */
139 | children: PropTypes.node,
140 |
141 | /**
142 | * Dash-assigned callback that should be called whenever any of the properties change
143 | */
144 | setProps: PropTypes.func,
145 |
146 | //
147 | // Basic props
148 | //
149 |
150 | // Optional, but if you are managing width yourself you may want to set the breakpoint
151 | // yourself as well.
152 | breakpoint: PropTypes.string,
153 |
154 | // {name: pxVal}, e.g. {lg: 1200, md: 996, sm: 768, xs: 480}
155 | breakpoints: PropTypes.object,
156 |
157 | // # of cols. This is a breakpoint -> cols map
158 | cols: PropTypes.object,
159 |
160 | // layouts is an object mapping breakpoints to layouts.
161 | // e.g. {lg: Layout, md: Layout, ...}
162 | layouts: PropTypes.object,
163 |
164 | // The width of this component.
165 | // Required in this propTypes stanza because generateInitialState() will fail without it.
166 | width: PropTypes.number,
167 |
168 | //
169 | // Callbacks
170 | //
171 |
172 | // Calls back with breakpoint and new # cols
173 | onBreakpointChange: PropTypes.func,
174 |
175 | // Callback so you can save the layout.
176 | // Calls back with (currentLayout, allLayouts). allLayouts are keyed by breakpoint.
177 | onLayoutChange: PropTypes.func,
178 |
179 | // Calls back with (containerWidth, margin, cols, containerPadding)
180 | onWidthChange: PropTypes.func,
181 |
182 |
183 | // From GridLayout
184 | /**
185 | * The height, in pixels of a row in the grid
186 | */
187 | rowHeight: PropTypes.number,
188 |
189 | /**
190 | * Total Rows that can the board have based on the cumulated sum of all rows
191 | */
192 | maxRows: PropTypes.number,
193 |
194 | /**
195 | * If true, containers will automatically resize to fit the content
196 | */
197 | autoSize: PropTypes.bool,
198 |
199 | /**
200 | * CSS selector for tags that will not be draggable. Requires a leading '.'
201 | */
202 | draggableCancel: PropTypes.string,
203 |
204 | /**
205 | * CSS selector for tags that will act as the draggable handle. Requires a leading '.'
206 | */
207 | draggableHandle: PropTypes.string,
208 |
209 | /**
210 | * If true, the layout will compact vertically
211 | */
212 | verticalCompact: PropTypes.bool,
213 |
214 | /**
215 | * Compaction type.
216 | * One of 'vertical' and 'horizontal'
217 | */
218 | compactType: PropTypes.oneOf(['vertical', 'horizontal']),
219 |
220 | /**
221 | * Margin between items [x, y] in px
222 | */
223 | margin: PropTypes.arrayOf(PropTypes.number),
224 |
225 | /**
226 | * Padding inside the container [x, y] in px
227 | */
228 | containerPadding: PropTypes.arrayOf(PropTypes.number),
229 |
230 | /**
231 | * Elements can be dragged
232 | */
233 | isDraggable: PropTypes.bool,
234 |
235 | /**
236 | * Elements can be resized
237 | */
238 | isResizable: PropTypes.bool,
239 |
240 | /**
241 | * Use CSS transforms instead of Position. Improves performance if switched on
242 | */
243 | useCSSTransforms: PropTypes.bool,
244 |
245 | /**
246 | * If true, grid items won't change position when being
247 | * dragged over
248 | */
249 | preventCollision: PropTypes.bool,
250 |
251 |
252 | /**
253 | * Callback when dragging is started
254 | */
255 | onDragStart: PropTypes.func,
256 |
257 | /**
258 | * Callback upon each drag movement
259 | */
260 | onDrag: PropTypes.func,
261 |
262 | /**
263 | * Callback upon drag completion
264 | */
265 | onDragStop: PropTypes.func,
266 |
267 | /**
268 | * Calls when resize starts
269 | */
270 | onResizeStart: PropTypes.func,
271 |
272 | /**
273 | * Calls when resize movement happens
274 | */
275 | onResize: PropTypes.func,
276 |
277 | /**
278 | * Calls when resize is complete
279 | */
280 | onResizeStop: PropTypes.func,
281 |
282 | /**
283 | * Set the ClassName to the component
284 | */
285 | className: PropTypes.string,
286 |
287 | /**
288 | * Set the ClassName to the component
289 | * available options are:
290 | * ?Array<'s' | 'w' | 'e' | 'n' | 'sw' | 'nw' | 'se' | 'ne'>
291 | * default: ["se"]
292 | */
293 | // resizeHandles: PropTypes.arrayOf(PropTypes.string),
294 |
295 | };
296 |
297 | ResponsiveGrid.defaultProps = ResponsiveReactGridLayout.defaultProps;
298 |
299 | ResponsiveGrid.defaultProps = {
300 | breakpoint: 'lg',
301 | breakpoints: {lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0},
302 | cols: {lg: 12, md: 10, sm: 6, xs: 4, xxs: 2},
303 | layouts: {lg: [], md: [], sm: [], xs: [], xxs: []},
304 | onBreakpointChange: () => {},
305 | onLayoutChange: () => {},
306 | onWidthChange: () => {}
307 | };
308 |
--------------------------------------------------------------------------------
/src/lib/components/SideBar/SideBar.react.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Children } from 'react';
2 | import PropTypes from 'prop-types';
3 | import SideNav from '@trendmicro/react-sidenav';
4 | import './SideBar.scss'
5 | import '@trendmicro/react-sidenav/dist/react-sidenav.css';
6 |
7 | /**
8 | * A collapsible side bar for your dashboard,
9 | * with icons from Font Awesome
10 | */
11 |
12 | export default class SideBar extends Component {
13 |
14 | render() {
15 |
16 | const {
17 | id,
18 | className,
19 | bg_color,
20 | text_color,
21 | children
22 | } = this.props
23 |
24 | return (
25 |
32 |
33 |
34 | {children}
35 |
36 |
37 | );
38 | }
39 |
40 | }
41 |
42 | SideBar.defaultProps = {
43 | id: null,
44 | className: '',
45 | bg_color: '#2b7279',
46 | text_color: '#ffffff'
47 | };
48 |
49 | SideBar.propTypes = {
50 | /**
51 | * Id of the element
52 | */
53 | id: PropTypes.string,
54 |
55 | /**
56 | * Style class of the component.
57 | */
58 | className: PropTypes.string,
59 |
60 | /**
61 | * Color of sidebar background
62 | */
63 | bg_color: PropTypes.string,
64 |
65 | /**
66 | * Color of sidebar text
67 | */
68 | text_color: PropTypes.string,
69 |
70 | /**
71 | * Your carousel is vertical
72 | */
73 | children: PropTypes.node
74 | };
75 |
--------------------------------------------------------------------------------
/src/lib/components/SideBar/SideBar.scss:
--------------------------------------------------------------------------------
1 | .trich_sidebar {
2 | i {
3 | vertical-align: middle;
4 | }
5 | }
6 |
7 | @media screen and (min-width: 600px) {
8 | .sidenav---expanded---1KdUL ~ #page_content {
9 | margin-left: 240px;
10 | transition: 0.5s;
11 | }
12 |
13 | .sidenav---collapsed---LQDEv ~ #page_content {
14 | margin-left: 64px;
15 | transition: 0.5s;
16 | }
17 | }
18 |
19 | @media screen and (max-width: 599px) {
20 | .sidenav---collapsed---LQDEv {
21 | background-color: transparent !important;
22 | }
23 |
24 | .sidenav---collapsed---LQDEv .sidenav---sidenav-nav---3tvij {
25 | display: none;
26 | }
27 | }
--------------------------------------------------------------------------------
/src/lib/components/SideBarItem/SideBarItem.react.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { NavItem, NavIcon, NavText } from '@trendmicro/react-sidenav';
4 | import '@trendmicro/react-sidenav/dist/react-sidenav.css';
5 |
6 | /**
7 | * Side bar items, to be used inside of sidebar component
8 | */
9 |
10 | export default class SideBarItem extends Component {
11 |
12 | constructor(props) {
13 | super(props);
14 |
15 | this.incrementClicks = this.incrementClicks.bind(this);
16 | }
17 |
18 | incrementClicks() {
19 | if (!this.props.disabled && this.props.setProps) {
20 | this.props.setProps({
21 | n_clicks: this.props.n_clicks + 1,
22 | n_clicks_timestamp: Date.now()
23 | });
24 | }
25 | }
26 |
27 | render() {
28 |
29 | const {
30 | id,
31 | className,
32 | label,
33 | icon,
34 | setProps,
35 | ...otherProps
36 | } = this.props
37 |
38 | return (
39 |
45 |
46 |
47 |
48 |
49 | {label}
50 |
51 |
52 | );
53 | }
54 |
55 | }
56 |
57 | SideBarItem.defaultProps = {
58 | id: null,
59 | className: '',
60 | label: 'Label',
61 | icon: 'fas fa-circle',
62 | disabled: false,
63 | n_clicks: 0,
64 | n_clicks_timestamp: -1
65 | };
66 |
67 | SideBarItem.propTypes = {
68 | /**
69 | * Id of the element
70 | */
71 | id: PropTypes.string,
72 |
73 | /**
74 | * Style class of the component.
75 | */
76 | className: PropTypes.string,
77 |
78 | /**
79 | * Text of menu item on sidebar
80 | */
81 | label: PropTypes.string,
82 |
83 | /**
84 | * Icon of menu item on sidebar, pass the icon class from font awesome
85 | */
86 | icon: PropTypes.string,
87 |
88 | /**
89 | * Disable the link. Default: False.
90 | */
91 | disabled: PropTypes.bool,
92 |
93 | /**
94 | * An integer that represents the number of times
95 | * that this element has been clicked on.
96 | */
97 | n_clicks: PropTypes.number,
98 |
99 | /**
100 | * An integer that represents the time (in ms since 1970)
101 | * at which n_clicks changed. This can be used to tell
102 | * which button was changed most recently.
103 | */
104 | n_clicks_timestamp: PropTypes.number,
105 |
106 | /**
107 | * Dash-assigned callback that should be called whenever any of the
108 | * properties change
109 | */
110 | setProps: PropTypes.func
111 | };
112 |
--------------------------------------------------------------------------------
/src/lib/components/ThemeToggle/ThemeToggle.react.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import './ThemeToggle.scss'
4 | import styled from 'styled-components'
5 | import 'bootstrap/dist/css/bootstrap.min.css';
6 | import { OverlayTrigger, Tooltip } from 'react-bootstrap';
7 |
8 | /**
9 | * Dark/Light theme toggle switch for your Dash project.
10 | */
11 |
12 | export default class ThemeToggle extends Component {
13 |
14 | constructor(props) {
15 | super(props);
16 | this.state = { checked: false };
17 |
18 | this.onChange = this.onChange.bind(this);
19 | }
20 |
21 | onChange(e) {
22 | if (e.target.checked) {
23 | document.documentElement.setAttribute('data-theme', 'dark');
24 | } else {
25 | document.documentElement.setAttribute('data-theme', 'light');
26 | }
27 | this.setState({ checked: e.target.checked });
28 | };
29 | render() {
30 | const {
31 | bg_color_dark,
32 | icon_color_dark,
33 | bg_color_light,
34 | icon_color_light,
35 | tooltip_text,
36 | className,
37 | id,
38 | style
39 | } = this.props
40 |
41 | const { checked } = this.state
42 |
43 | let background = checked ? bg_color_dark : bg_color_light
44 | let color = checked ? icon_color_dark : icon_color_light
45 |
46 | const Label = styled.label`
47 | background: ${background};
48 | color: ${color};
49 | `
50 |
51 | return (
52 |
53 |
55 | {tooltip_text}
56 |
57 | }>
58 |
62 |
63 |
64 |
65 | );
66 | }
67 | }
68 |
69 | ThemeToggle.defaultProps = {
70 | bg_color_dark: '#232323',
71 | icon_color_dark: '#EDC575',
72 | bg_color_light: '#07484E',
73 | icon_color_light: '#c8dbdc',
74 | tooltip_text: 'Toggle light/dark theme',
75 | className: '',
76 | style: null,
77 | id: null
78 | };
79 |
80 | ThemeToggle.propTypes = {
81 | /**
82 | * Background color of toggle switch when dark theme
83 | */
84 | bg_color_dark: PropTypes.string,
85 |
86 | /**
87 | * Sun icon color of toggle switch when dark theme
88 | */
89 | icon_color_dark: PropTypes.string,
90 |
91 | /**
92 | * Background color of toggle switch when light theme
93 | */
94 | bg_color_light: PropTypes.string,
95 |
96 | /**
97 | * Moon icon color of toggle switch when dark theme
98 | */
99 | icon_color_light: PropTypes.string,
100 |
101 | /**
102 | * Text that will appear in tooltip (only desktop)
103 | */
104 | tooltip_text: PropTypes.string,
105 |
106 | /**
107 | * Inline style of the component.
108 | */
109 | style: PropTypes.object,
110 | /**
111 | * Style class of the component.
112 | */
113 | className: PropTypes.string,
114 |
115 | /**
116 | * Id of the element
117 | */
118 | id: PropTypes.string
119 | };
120 |
--------------------------------------------------------------------------------
/src/lib/components/ThemeToggle/ThemeToggle.scss:
--------------------------------------------------------------------------------
1 | .themeToggle {
2 | input[type=checkbox] {
3 | height: 0;
4 | width: 0;
5 | visibility: hidden;
6 | }
7 |
8 | label {
9 | cursor: pointer;
10 | width: 55px;
11 | height: 30px;
12 | margin: 0 auto;
13 | display: flex;
14 | justify-content: center;
15 | align-items: center;
16 | -webkit-border-radius: 100px;
17 | -moz-border-radius: 100px;
18 | border-radius: 100px;
19 | position: relative;
20 | margin-top: -8px;
21 |
22 | &:after {
23 | font-family: "Font Awesome 5 Free";
24 | font-weight: 900;
25 | content: "\f186";
26 | font-size: 21px;
27 | position: absolute;
28 | top: 0;
29 | left: 4px;
30 | -webkit-transition: ease-in-out 200ms;
31 | -moz-transition: ease-in-out 200ms;
32 | -ms-transition: ease-in-out 200ms;
33 | -o-transition: ease-in-out 200ms;
34 | transition: ease-in-out 200ms;
35 | }
36 |
37 | &:hover:after {
38 | font-size: 23px;
39 | top: -2px;
40 | }
41 | }
42 |
43 | @media sreen and (max-width: 991px) {
44 | #tooltip-bottom {
45 | display: none;
46 | }
47 | }
48 |
49 | }
50 | html[data-theme='dark'] {
51 |
52 | .themeToggle {
53 | label {
54 | &:after {
55 | font-family: "Font Awesome 5 Free";
56 | font-weight: 900;
57 | content: "\f185";
58 | left: calc(100% - 5px);
59 | -webkit-transform: translateX(-100%);
60 | -moz-transform: translateX(-100%);
61 | -ms-transform: translateX(-100%);
62 | -o-transform: translateX(-100%);
63 | transform: translateX(-100%);
64 | }
65 | &:hover:after {
66 | font-size: 23px;
67 | top: -2px
68 | }
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/src/lib/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 | import Carousel from './components/Carousel/Carousel.react';
3 | import ThemeToggle from './components/ThemeToggle/ThemeToggle.react';
4 | import SideBar from './components/SideBar/SideBar.react';
5 | import SideBarItem from './components/SideBarItem/SideBarItem.react';
6 | import Card from './components/Card/Card.react';
7 | import GridLayoutComponent from './components/GridLayoutComponent/GridLayoutComponent.react';
8 | import ResponsiveGrid from './components/ResponsiveGrid/ResponsiveGrid.react';
9 | import GridItem from './components/ResponsiveGrid/ResponsiveGrid.react';
10 |
11 | export {
12 | Carousel,
13 | ThemeToggle,
14 | SideBar,
15 | SideBarItem,
16 | Card,
17 | GridLayoutComponent,
18 | ResponsiveGrid,
19 | GridItem
20 | };
21 |
--------------------------------------------------------------------------------
/src/lib/utils/GridItemWrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import GridItem from '../components/ResponsiveGrid/GridItem.react';
3 |
4 | /**
5 | * A wrapper function for GridItem
6 | * Because React Grid Layout works on the props of the
7 | * children passed to the root element, we can't just rely on
8 | * injecting the correct props at render time. Instead, we intercept
9 | * the GridItem pre-render, and replace it with a different component with the correct layout.
10 | *
11 | * In the future, this plain div will be replaced with a GridItemProxy component, that will
12 | * contain the necessary event handlers
13 | */
14 | export const gridItemWrapper = (gridItem, newLayout) => {
15 |
16 |
17 | newLayout = newLayout || new Array();
18 |
19 | let i, layout, gridItemNew;
20 | if(gridItem.type !== GridItem) {
21 | i = gridItem.key || gridItem.props.id;
22 | layout = gridItem.props.dataGrid || {x: 0, y: 0, w: 4, h: 4, maxWidth:12, maxHeight:8, minWidth:2, minHeight:3};
23 | gridItemNew = {gridItem}
24 | } else {
25 | i = gridItem.props.i;
26 | layout = gridItem.props.layout;
27 | gridItemNew = gridItem;
28 | }
29 |
30 | let updatedLayout = Object.assign({}, (newLayout.find(x => x.i === i) || layout));
31 | updatedLayout.i = i;
32 |
33 | return (
34 |
35 | { React.cloneElement(gridItemNew, { layout: updatedLayout }) }
36 |
37 | );
38 | }
39 |
40 |
41 | /**
42 | * By default, Dash wraps children in an element, obfuscating the key.
43 | * If a child has no key, this function npacks the children so that `key`
44 | * is exposed and the layout can be used
45 | */
46 | export const unpackChildren = (children) => {
47 | if(children) {
48 | if(Array.isArray(children)) {
49 | return children.map(child => child.key === null ? (child.props.children || child) : child);
50 | } else if(!children.key && children.props) {
51 | return (children.props.children || children);
52 | }
53 | }
54 |
55 | return children;
56 | }
57 |
58 | /**
59 | * Wrap children inside a wrapper that conforms to react-grid-layout
60 | */
61 | export const wrapChildren = (children, layout) => {
62 | if(children) {
63 | if(Array.isArray(children)) {
64 | return children.map(child => gridItemWrapper(child, layout));
65 | } else {
66 | return gridItemWrapper(children, layout);
67 | }
68 | }
69 |
70 | return children;
71 | }
--------------------------------------------------------------------------------
/src/lib/utils/index.js:
--------------------------------------------------------------------------------
1 | import {gridItemWrapper, unpackChildren, wrapChildren} from './GridItemWrapper';
2 |
3 | export {
4 | gridItemWrapper,
5 | unpackChildren,
6 | wrapChildren,
7 | }
8 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/romanonatacha/dash_trich_components/cad90d50377b1e8f4f3694a3575f1548447c51dd/tests/__init__.py
--------------------------------------------------------------------------------
/tests/requirements.txt:
--------------------------------------------------------------------------------
1 | # Packages needed to run the tests.
2 | # Switch into a virtual environment
3 | # pip install -r requirements.txt
4 |
5 | dash[dev,testing]>=1.3.1
6 |
--------------------------------------------------------------------------------
/tests/test_usage.py:
--------------------------------------------------------------------------------
1 | from dash.testing.application_runners import import_app
2 |
3 |
4 | # Basic test for the component rendering.
5 | # The dash_duo pytest fixture is installed with dash (v1.0+)
6 | def test_render_component(dash_duo):
7 | # Start a dash app contained as the variable `app` in `usage.py`
8 | app = import_app('usage')
9 | dash_duo.start_server(app)
10 |
11 | # Get the generated component input with selenium
12 | # The html input will be a children of the #input dash component
13 | my_component = dash_duo.find_element('#input > input')
14 |
15 | assert 'my-value' == my_component.get_attribute('value')
16 |
17 | # Clear the input
18 | dash_duo.clear_input(my_component)
19 |
20 | # Send keys to the custom input.
21 | my_component.send_keys('Hello dash')
22 |
23 | # Wait for the text to equal, if after the timeout (default 10 seconds)
24 | # the text is not equal it will fail the test.
25 | dash_duo.wait_for_text_to_equal('#output', 'You have entered Hello dash')
26 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const packagejson = require('./package.json');
3 |
4 | const dashLibraryName = packagejson.name.replace(/-/g, '_');
5 |
6 | module.exports = (env, argv) => {
7 |
8 | let mode;
9 |
10 | const overrides = module.exports || {};
11 |
12 | // if user specified mode flag take that value
13 | if (argv && argv.mode) {
14 | mode = argv.mode;
15 | }
16 |
17 | // else if configuration object is already set (module.exports) use that value
18 | else if (overrides.mode) {
19 | mode = overrides.mode;
20 | }
21 |
22 | // else take webpack default (production)
23 | else {
24 | mode = 'production';
25 | }
26 |
27 | let filename = (overrides.output || {}).filename;
28 | if (!filename) {
29 | const modeSuffix = mode === 'development' ? 'dev' : 'min';
30 | filename = `${dashLibraryName}.${modeSuffix}.js`;
31 | }
32 |
33 | const entry = overrides.entry || { main: './src/lib/index.js' };
34 |
35 | const devtool = overrides.devtool || 'source-map';
36 |
37 | const externals = ('externals' in overrides) ? overrides.externals : ({
38 | react: 'React',
39 | 'react-dom': 'ReactDOM',
40 | 'plotly.js': 'Plotly',
41 | 'prop-types': 'PropTypes',
42 | });
43 |
44 | return {
45 | mode,
46 | entry,
47 | output: {
48 | path: path.resolve(__dirname, dashLibraryName),
49 | filename,
50 | library: dashLibraryName,
51 | libraryTarget: 'window',
52 | },
53 | devtool,
54 | externals,
55 | module: {
56 | rules: [
57 | {
58 | test: /\.jsx?$/,
59 | exclude: /node_modules/,
60 | use: {
61 | loader: 'babel-loader',
62 | },
63 | },
64 | {
65 | test: /\.css$/,
66 | use: [
67 | {
68 | loader: 'style-loader',
69 | options: {
70 | insertAt: 'top'
71 | }
72 | },
73 | {
74 | loader: 'css-loader',
75 | },
76 | ],
77 | },
78 | {
79 | test: /\.s[ac]ss$/i,
80 | use: [
81 | // Creates `style` nodes from JS strings
82 | 'style-loader',
83 | // Translates CSS into CommonJS
84 | 'css-loader',
85 | // Compiles Sass to CSS
86 | 'sass-loader',
87 | ],
88 | },
89 | {
90 | test: /\.(woff(2)?|ttf|otf|eot|svg|gif)(\?v=\d+\.\d+\.\d+)?$/,
91 | use: [{
92 | loader: 'file-loader',
93 | options: {
94 | name: '[name].[ext]',
95 | outputPath: 'fonts/'
96 | }
97 | }]
98 | }
99 | ],
100 | },
101 | }
102 | };
103 |
--------------------------------------------------------------------------------
/webpack.serve.config.js:
--------------------------------------------------------------------------------
1 | const config = require('./webpack.config.js');
2 | const path = require('path');
3 |
4 | config.entry = {main: './src/demo/index.js'};
5 | config.output = {
6 | filename: './output.js',
7 | path: path.resolve(__dirname),
8 | };
9 | config.mode = 'development';
10 | config.externals = undefined; // eslint-disable-line
11 | config.devtool = 'inline-source-map';
12 | module.exports = config;
13 |
--------------------------------------------------------------------------------