Skip to content

Commit d6d603d

Browse files
committed
Add some generic tools to improve reusability
- A generic component for toolbars (with buttons configurations, glyphs and tooltip management) - A tooltip enhancer. To avoid to add OverlayTrigger stuff every time. - A generic empty page component and enhancer (that allows to simply render a configurable empty page on some conditions - A Sidebar tool with good defaults, to reuse.
1 parent da0e278 commit d6d603d

15 files changed

Lines changed: 481 additions & 0 deletions

File tree

docma-config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@
118118
"web/client/components/mapcontrols/annotations/Annotations.jsx",
119119
"web/client/components/mapcontrols/annotations/AnnotationsEditor.jsx",
120120
"web/client/components/mapcontrols/annotations/AnnotationsConfig.js",
121+
"web/client/components/misc/enhancers/emptyState.jsx",
122+
"web/client/components/misc/enhancers/tooltip.jsx",
123+
"web/client/components/misc/sidebar/Sidebar.jsx",
124+
"web/client/components/misc/toolbar/Toolbar.jsx",
125+
"web/client/components/misc/EmptyView.jsx",
121126

122127
"web/client/actions/index.jsdoc",
123128
"web/client/actions/controls.js",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2017, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
const React = require('react');
9+
10+
const FullWidthIcon = require('./FullWidthGlyph');
11+
12+
/**
13+
* A component to display an empty page.
14+
* It allows to display a big icon with some information centered. As an option some additiona content can be provided via props. (e.g. add buttons, tools or info)
15+
* It is very similar to material design concept for [empty state](https://material.io/guidelines/patterns/empty-states.html)
16+
*
17+
* @class EmptyView
18+
* @memberof components.misc
19+
* @param {Number} [opacity=0.45] The opactity
20+
* @param {String} [glyph="info-sign"] The icon glyph
21+
* @param {string|node} [title] The title of the advice to display
22+
* @param {string|node} [description] The description to display
23+
* @param {string|node} [content] Additional content for the empty view (e.g. buttons...)
24+
*/
25+
module.exports = ({
26+
opacity= 0.45,
27+
glyph="info-sign",
28+
title,
29+
description,
30+
content
31+
} = {}) =>
32+
(<div style={{textAlign: "center", position: "absolute", width: "100%"}} >
33+
<div key="empty-main-view" style={{opacity}}>
34+
{glyph ? <div key="glyph" style={{width: "40%", "marginLeft": "30%", textAlign: "center"}}><FullWidthIcon glyph={glyph} /></div> : null}
35+
{title ? <h1 key="title" >{title}</h1> : null}
36+
{description ? <p key="description">{description}</p> : null}
37+
</div>
38+
<div key="content">{content}</div>
39+
</div>);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2017, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
10+
const React = require('react');
11+
const {Glyphicon} = require('react-bootstrap');
12+
const ContainerDimensions = require('react-container-dimensions').default;
13+
14+
/**
15+
* An icon that fits to the width of the container
16+
* @param {string} glyph icon glyph
17+
*/
18+
module.exports = ({glyph = "info-sign"}) => (<ContainerDimensions>
19+
{ ({ width }) => (<Glyphicon glyph={glyph} style={{fontSize: width}} />)}
20+
</ContainerDimensions>);
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2017, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
const expect = require('expect');
9+
const React = require('react');
10+
const ReactDOM = require('react-dom');
11+
const EmptyView = require('../EmptyView');
12+
13+
describe("EmptyView component", () => {
14+
beforeEach((done) => {
15+
document.body.innerHTML = '<div id="container"></div>';
16+
setTimeout(done);
17+
});
18+
afterEach((done) => {
19+
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
20+
document.body.innerHTML = '';
21+
setTimeout(done);
22+
});
23+
it('create EmptyView with defaults', () => {
24+
ReactDOM.render(<EmptyView />, document.getElementById("container"));
25+
const el = document.getElementsByClassName('glyphicon')[0];
26+
expect(el).toExist();
27+
});
28+
it('create EmptyView title, description and content', () => {
29+
ReactDOM.render(<EmptyView title="Title" description="description" content={<div id="content" >TEST</div>}/>, document.getElementById("container"));
30+
const description = document.getElementsByTagName('p')[0];
31+
expect(description).toExist();
32+
const content = document.getElementById("content");
33+
expect(content).toExist();
34+
});
35+
36+
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Copyright 2017, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
var expect = require('expect');
10+
var React = require('react');
11+
var ReactDOM = require('react-dom');
12+
var tooltip = require('../tooltip');
13+
14+
const {Button} = require('react-bootstrap');
15+
describe("tooltip enhancer", () => {
16+
beforeEach((done) => {
17+
document.body.innerHTML = '<div id="container"></div>';
18+
setTimeout(done);
19+
});
20+
21+
afterEach((done) => {
22+
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
23+
document.body.innerHTML = '';
24+
setTimeout(done);
25+
});
26+
27+
it('creates component with defaults', () => {
28+
const CMP = tooltip(({id}) => <div id={id} />);
29+
ReactDOM.render(<CMP id="text-cmp"/>, document.getElementById("container"));
30+
const el = document.getElementById("text-cmp");
31+
expect(el).toExist();
32+
});
33+
it('creates component with tooltip props', () => {
34+
const CMP = tooltip(Button);
35+
ReactDOM.render(<CMP tooltip={<div>Hello</div>} tooltipTrigger={['click', 'focus', 'hover']} id="text-cmp">TEXT</CMP>, document.getElementById("container"));
36+
const el = document.getElementById("text-cmp");
37+
expect(el).toExist();
38+
el.click();
39+
expect(el.getAttribute('aria-describedby')).toExist();
40+
});
41+
42+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2017, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
const React = require('react');
9+
const {branch} = require('recompose');
10+
const DefaultEmptyComponent = require("../EmptyView");
11+
12+
13+
/**
14+
* Empty State enhancer. Enhances an object adding a (localizable) tooltip.
15+
* It is applied only if props contains `tooltip` or `tooltipId`. It have to be applied to a React (functional) component
16+
* @type {function}
17+
* @name emptyState
18+
* @memberof components.misc.enhancers
19+
* @param {function} test The test for the properties. If it is true, use empty view
20+
* @param {object} [emptyComponentProps] parameters for the empty components. The structure must reflect the props of the EmptyComponent parameter (or its default @see [EmptyView](#components.misc.EmptyView))
21+
* @param {Component} [EmptyComponent=EmptyView] the component to use for empty view. By default components.misc.EmptyView
22+
* @example
23+
* emptyState(({data=[]}) => data.length === 0)(ComponentToEnhance);
24+
*
25+
*/
26+
module.exports = (test, emptyComponentProps, EmptyComponent = DefaultEmptyComponent) => branch(
27+
test,
28+
// TODO return proper HOC
29+
() => () => <EmptyComponent {...emptyComponentProps} />);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2017, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
const React = require('react');
9+
const {branch} = require('recompose');
10+
const { Tooltip, OverlayTrigger} = require('react-bootstrap');
11+
const Message = require('../../I18N/Message');
12+
13+
/**
14+
* Tooltip enhancer. Enhances an object adding a (localizable) tooltip.
15+
* It is applied only if props contains `tooltip` or `tooltipId`. It have to be applied to a React (functional) component
16+
* @type {function}
17+
* @name tooltip
18+
* @memberof components.misc.enhancers
19+
* @prop {string|node} [tooltip] if present will add the tooltip. This is the full tooltip content
20+
* @prop {string} [tooltipId] if present will show a localized tooltip using the tooltipId as msgId
21+
* @prop {string} [tooltipPosition="top"]
22+
* @prop {string} tooltipTrigger see react overlay trigger
23+
* @example
24+
* render() {
25+
* const Cmp = tooltip((props) =><El {...props}></El>); // or simply tooltip(El);
26+
* return <Cmp tooltipId="Hello" tooltipPosition="left" /> // => render the component with tooltip
27+
* }
28+
*
29+
*/
30+
module.exports = branch(
31+
({tooltip, tooltipId} = {}) => tooltip || tooltipId,
32+
// TODO return proper HOC
33+
(Wrapped) => ({tooltip, tooltipId, tooltipPosition = "top", tooltipTrigger, key, ...props} = {}) => (<OverlayTrigger
34+
trigger={tooltipTrigger}
35+
key={key}
36+
placement={tooltipPosition}
37+
overlay={<Tooltip id={"tooltip-" + {key}}>{tooltipId ? <Message msgId={tooltipId} /> : tooltip}</Tooltip>}><Wrapped {...props}/></OverlayTrigger>));
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2017, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
const React = require('react');
10+
const Sidebar = require('react-sidebar').default;
11+
/**
12+
* MapStore generic sidebar component with good defaults
13+
* @class Sidebar
14+
* @memberof components.misc.sidebar
15+
* @param {boolean} open Open flag
16+
* @param {Number} [width=600] Width of the sidebar
17+
* @param {node} children Content
18+
*/
19+
module.exports = ({open, width = 600, children} = {}) => (<Sidebar
20+
open={open}
21+
sidebarClassName="sidepanel-content"
22+
sidebar={children}
23+
styles={{
24+
sidebar: {
25+
zIndex: 1024,
26+
width
27+
},
28+
overlay: {
29+
zIndex: 1023,
30+
width: 0
31+
},
32+
root: {
33+
right: open ? 0 : 'auto',
34+
width: '0',
35+
overflow: 'visible'
36+
},
37+
content: {
38+
overflowY: 'auto'
39+
}
40+
}}
41+
>
42+
<div/>
43+
</Sidebar>);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
* Copyright 2017, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
const React = require('react');
9+
module.exports = ({title} = {}) => <div className="sidebar-title"><h3 className="sidebar-title ">{title}</h3></div>;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2017, GeoSolutions Sas.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
const expect = require('expect');
9+
const React = require('react');
10+
const ReactDOM = require('react-dom');
11+
const Sidebar = require('../Sidebar');
12+
const SidebarHeader = require('../SidebarHeader');
13+
14+
describe("Sidebar component", () => {
15+
beforeEach((done) => {
16+
document.body.innerHTML = '<div id="container"></div>';
17+
setTimeout(done);
18+
});
19+
afterEach((done) => {
20+
ReactDOM.unmountComponentAtNode(document.getElementById("container"));
21+
document.body.innerHTML = '';
22+
setTimeout(done);
23+
});
24+
it('create Sidebar with content', () => {
25+
ReactDOM.render(<Sidebar open>
26+
<div id="content" />
27+
</Sidebar>, document.getElementById("container"));
28+
const el = document.getElementById('content');
29+
expect(el).toExist();
30+
});
31+
it('create Sidebar wjth title and content', () => {
32+
ReactDOM.render(<Sidebar open>
33+
<SidebarHeader title={<span id="content">content</span>} />
34+
</Sidebar>, document.getElementById("container"));
35+
const titleContainer = document.getElementsByClassName('sidebar-title')[0];
36+
expect(titleContainer).toExist();
37+
const content = document.getElementById("content");
38+
expect(content).toExist();
39+
});
40+
41+
});

0 commit comments

Comments
 (0)