├── .npmignore ├── .gitignore ├── src ├── components │ ├── tabs │ │ ├── Tab.js │ │ ├── Tabs.js │ │ ├── RoutedTabs.js │ │ └── ScrollableRoutedTabs.js │ ├── animatedIcons │ │ ├── index.js │ │ └── MenuIcon.js │ ├── utils │ │ ├── Icon.js │ │ ├── Error.js │ │ └── Loading.js │ ├── content │ │ ├── ContentHeaderActions.js │ │ ├── ContentBody.js │ │ ├── ContentHeader.js │ │ └── Content.js │ ├── sidebar │ │ ├── SidebarMenu.js │ │ ├── SidebarToggler.js │ │ ├── SidebarHeader.js │ │ ├── Sidebar.js │ │ └── SidebarItem.js │ ├── Responsive.js │ ├── card │ │ ├── CardHeader.js │ │ ├── Card.js │ │ └── CardFooter.js │ ├── Theme.js │ ├── ThemeProvider.js │ └── header │ │ ├── HeaderBrand.js │ │ └── Header.js ├── reducers │ └── index.js ├── index.js └── ducks │ └── Sidebar.js ├── .babelrc ├── .flowconfig ├── storybook ├── head.html ├── reducers.js ├── store.js ├── stories │ ├── Snackbar.story.js │ ├── Utils.story.js │ ├── Content.story.js │ ├── Card.story.js │ ├── Header.story.js │ ├── AnimatedIcons.story.js │ ├── Template.story.js │ ├── Sidebar.story.js │ └── Tabs.story.js └── config.js ├── README.md ├── .eslintrc ├── LICENSE.md └── package.json /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | .idea 5 | storybook-static 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | .idea 5 | storybook-static 6 | lib 7 | -------------------------------------------------------------------------------- /src/components/tabs/Tab.js: -------------------------------------------------------------------------------- 1 | import { Tab } from 'material-ui/Tabs'; 2 | 3 | export default Tab; 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["flow", "es2015", "react", "stage-0"], 3 | "plugins": ["material-ui"], 4 | } 5 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [options] 8 | sharedmemory.hash_table_pow=21 9 | -------------------------------------------------------------------------------- /src/components/animatedIcons/index.js: -------------------------------------------------------------------------------- 1 | import MenuIcon from './MenuIcon'; 2 | 3 | export default { 4 | Menu: MenuIcon, 5 | }; 6 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import sidebarReducer from '../ducks/Sidebar'; 2 | 3 | export default { 4 | sidebar: sidebarReducer, 5 | }; 6 | -------------------------------------------------------------------------------- /storybook/head.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /storybook/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { reducers } from '../src'; 4 | 5 | export default combineReducers(reducers); 6 | -------------------------------------------------------------------------------- /src/components/utils/Icon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import FontIcon from 'material-ui/FontIcon'; 3 | 4 | const Icon = ({ children, ...props }) => 5 | ; 6 | 7 | export default Icon; 8 | -------------------------------------------------------------------------------- /storybook/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | 3 | import reducers from './reducers'; 4 | 5 | const store = createStore(reducers); 6 | 7 | if (module.hot) { 8 | module.hot.accept(() => { 9 | const nextRootReducer = require('./reducers').default; 10 | store.replaceReducer(nextRootReducer); 11 | }); 12 | } 13 | 14 | export default store; 15 | -------------------------------------------------------------------------------- /src/components/content/ContentHeaderActions.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const ContentHeaderActions = ({ children }) => 4 |
5 | {children} 6 |
; 7 | 8 | const styles = () => ({ 9 | wrapper: { 10 | display: 'flex', 11 | alignItems: 'center', 12 | }, 13 | }); 14 | 15 | export default ContentHeaderActions; 16 | -------------------------------------------------------------------------------- /src/components/utils/Error.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Error = ({ error }) => 4 |
5 |

Error

6 |

{error.message}

7 |
; 8 | 9 | const styles = () => ({ 10 | wrapper: { 11 | display: 'flex', 12 | flexDirection: 'column', 13 | width: '100%', 14 | height: '100%', 15 | alignItems: 'center', 16 | justifyContent: 'center', 17 | }, 18 | }); 19 | 20 | export default Error; 21 | -------------------------------------------------------------------------------- /src/components/sidebar/SidebarMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { List, makeSelectable } from 'material-ui/List'; 3 | 4 | const SelectableList = makeSelectable(List); 5 | 6 | const SidebarMenu = ({ children }) => 7 |
8 | 9 | {children} 10 | 11 |
; 12 | 13 | const styles = () => ({ 14 | wrapper: { 15 | marginTop: 20, 16 | }, 17 | }); 18 | 19 | export default SidebarMenu; 20 | -------------------------------------------------------------------------------- /src/components/Responsive.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { getTheme } from './Theme'; 3 | 4 | export const VIEWPORT: Object = { 5 | SMALL: getTheme().responsive.small || 480, 6 | MEDIUM: getTheme().responsive.medium || 768, 7 | }; 8 | 9 | export const isSmall = (): boolean => window.innerWidth <= VIEWPORT.SMALL; 10 | 11 | export const isMedium = (): boolean => !isSmall() && window.innerWidth <= VIEWPORT.MEDIUM; 12 | 13 | export const isLarge = (): boolean => !isMedium() && window.innerWidth > VIEWPORT.MEDIUM; 14 | -------------------------------------------------------------------------------- /src/components/sidebar/SidebarToggler.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { connect } from 'react-redux'; 4 | import { toggleSidebar } from '../../ducks/Sidebar'; 5 | 6 | const SidebarToggler = ({ actions, children }) => 7 | actions.toggleSidebar()}> 8 | {children} 9 | ; 10 | 11 | const mapDispatchToProps = dispatch => ({ 12 | actions: { 13 | toggleSidebar: () => dispatch(toggleSidebar()), 14 | }, 15 | }); 16 | 17 | export default connect(null, mapDispatchToProps)(SidebarToggler); 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Entria Components 2 | 3 | ## Install 4 | 5 | ``` 6 | npm i @entria/components --save 7 | yarn add @entria/components 8 | ``` 9 | 10 | ## Config 11 | 12 | The Redux store should know how to handle actions coming from the components: 13 | 14 | ```js 15 | import { createStore, combineReducers } from 'redux'; 16 | import { reducers as entriaComponentsReducers } from '@entria/components'; 17 | 18 | const rootReducer = combineReducers({ 19 | // ...your other reducers here 20 | ...entriaComponentsReducers, 21 | }); 22 | 23 | const store = createStore(rootReducer); 24 | ``` 25 | -------------------------------------------------------------------------------- /src/components/sidebar/SidebarHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { getTheme } from '../Theme'; 4 | 5 | const SidebarHeader = ({ children }) => 6 |
7 | {children} 8 |
; 9 | 10 | const styles = () => ({ 11 | wrapper: { 12 | display: 'flex', 13 | alignItems: 'center', 14 | justifyContent: 'center', 15 | width: '100%', 16 | height: 100, 17 | padding: 20, 18 | backgroundColor: getTheme().palette.primary1Color, 19 | color: 'white', 20 | boxSizing: 'border-box', 21 | }, 22 | }); 23 | 24 | export default SidebarHeader; 25 | -------------------------------------------------------------------------------- /src/components/content/ContentBody.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | 5 | import { VIEWPORT } from '../Responsive'; 6 | 7 | const Wrapper = styled.div` 8 | padding: 20px; 9 | 10 | @media(min-width: ${VIEWPORT.MEDIUM}px) { 11 | padding: 40px; 12 | } 13 | `; 14 | 15 | const ContentBody = ({ style, children }) => 16 | 17 | {children} 18 | ; 19 | 20 | ContentBody.defaultProps = { 21 | style: {}, 22 | }; 23 | 24 | ContentBody.propTypes = { 25 | style: PropTypes.object, 26 | }; 27 | 28 | export default ContentBody; 29 | -------------------------------------------------------------------------------- /src/components/card/CardHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const CardHeader = ({ style, children }) => 5 |
6 | {children} 7 |
; 8 | 9 | const styles = () => ({ 10 | wrapper: { 11 | padding: '15px 40px', 12 | marginTop: -20, 13 | marginRight: -40, 14 | marginLeft: -40, 15 | marginBottom: 20, 16 | borderBottom: '1px solid #e5e5e5', 17 | }, 18 | }); 19 | 20 | CardHeader.defaultProps = { 21 | style: {}, 22 | }; 23 | 24 | CardHeader.propTypes = { 25 | style: PropTypes.object, 26 | }; 27 | 28 | export default CardHeader; 29 | -------------------------------------------------------------------------------- /storybook/stories/Snackbar.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { storiesOf } from '@kadira/storybook'; 4 | import loremIpsum from 'lorem-ipsum'; 5 | 6 | import FlatButton from 'material-ui/FlatButton'; 7 | import { Content } from '../../src'; 8 | 9 | const stories = storiesOf('Snackbar', module); 10 | 11 | const ComponentWithSnackbar = (props, context) => ( 12 | context.showSnackbar({ message: loremIpsum() })} 15 | /> 16 | ); 17 | 18 | ComponentWithSnackbar.contextTypes = { 19 | showSnackbar: PropTypes.func, 20 | }; 21 | 22 | stories.add('default', () => 23 | 24 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /storybook/stories/Utils.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@kadira/storybook'; 3 | 4 | import { Error, Icon, Loading } from '../../src'; 5 | 6 | const stories = storiesOf('Utils', module); 7 | 8 | stories.add('Error', () => ); 9 | 10 | stories.add('Icon', () => 11 |
12 | 13 | 14 | 15 | 16 |
17 | ); 18 | 19 | stories.add('Loading', () => ); 20 | 21 | const styles = { 22 | iconsWrapper: { 23 | padding: 20, 24 | display: 'flex', 25 | alignItems: 'center', 26 | justifyContent: 'center', 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/card/Card.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Card as MUICard } from 'material-ui/Card'; 4 | 5 | import CardFooter from './CardFooter'; 6 | import CardHeader from './CardHeader'; 7 | 8 | const Card = ({ style, children }) => 9 | 10 | {React.isValidElement(children) ? children :
{children}
} 11 |
; 12 | 13 | const styles = () => ({ 14 | wrapper: { 15 | position: 'relative', 16 | padding: '20px 40px', 17 | width: '100%', 18 | height: '100%', 19 | }, 20 | }); 21 | 22 | Card.Footer = CardFooter; 23 | Card.Header = CardHeader; 24 | 25 | Card.defaultProps = { 26 | style: {}, 27 | }; 28 | 29 | Card.propTypes = { 30 | style: PropTypes.object, 31 | }; 32 | 33 | export default Card; 34 | -------------------------------------------------------------------------------- /src/components/card/CardFooter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { CardActions } from 'material-ui/Card'; 4 | 5 | const CardFooter = ({ style, children }) => 6 | 7 | {React.isValidElement(children) ? children :
{children}
} 8 |
; 9 | 10 | const styles = () => ({ 11 | wrapper: { 12 | display: 'flex', 13 | justifyContent: 'flex-end', 14 | padding: '15px 40px', 15 | marginBottom: -20, 16 | marginRight: -40, 17 | marginLeft: -40, 18 | marginTop: 20, 19 | borderTop: '1px solid #e5e5e5', 20 | }, 21 | }); 22 | 23 | CardFooter.defaultProps = { 24 | style: {}, 25 | }; 26 | 27 | CardFooter.propTypes = { 28 | style: PropTypes.object, 29 | }; 30 | 31 | export default CardFooter; 32 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export AnimatedIcons from './components/animatedIcons'; 2 | export Card from './components/card/Card'; 3 | export Content from './components/content/Content'; 4 | export Header from './components/header/Header'; 5 | export Sidebar from './components/sidebar/Sidebar'; 6 | 7 | export RoutedTabs from './components/tabs/RoutedTabs'; 8 | export ScrollableRoutedTabs from './components/tabs/ScrollableRoutedTabs'; 9 | export Tab from './components/tabs/Tab'; 10 | export Tabs from './components/tabs/Tabs'; 11 | 12 | export Error from './components/utils/Error'; 13 | export Icon from './components/utils/Icon'; 14 | export Loading from './components/utils/Loading'; 15 | 16 | export * as Responsive from './components/Responsive'; 17 | export { createTheme, getTheme } from './components/Theme'; 18 | export ThemeProvider from './components/ThemeProvider'; 19 | 20 | export reducers from './reducers'; 21 | -------------------------------------------------------------------------------- /src/components/tabs/Tabs.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { Tabs } from 'material-ui/Tabs'; 4 | 5 | import { VIEWPORT } from '../Responsive'; 6 | 7 | const Wrapper = styled.div` 8 | > div { 9 | > div:nth-child(2) > div { 10 | transition: all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms !important; 11 | } 12 | 13 | @media(max-width: ${VIEWPORT.MEDIUM}px) { 14 | > div:nth-child(1) { 15 | flex-direction: column; 16 | 17 | button { 18 | width: 100% !important; 19 | } 20 | } 21 | 22 | > div:nth-child(2) > div { 23 | left: 0 !important; 24 | width: 100% !important; 25 | top: -${props => (props.tabsCount - props.selectedIndex - 1) * 48}px 26 | } 27 | } 28 | } 29 | `; 30 | 31 | const CustomTabs = props => 32 | 33 | 34 | ; 35 | 36 | export default CustomTabs; 37 | -------------------------------------------------------------------------------- /src/ducks/Sidebar.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { isLarge } from '../components/Responsive'; 3 | 4 | const LOCAL_STORAGE_SIDEBAR = 'entria-components-v1-sidebarVisible'; 5 | 6 | // Actions 7 | const TOGGLE: string = 'entria-components/Sidebar/TOGGLE'; 8 | 9 | // Reducer 10 | type State = { 11 | visible: boolean, 12 | }; 13 | 14 | type Action = { 15 | type: string, 16 | }; 17 | 18 | const initialState: State = { 19 | visible: isLarge() ? localStorage.getItem(LOCAL_STORAGE_SIDEBAR) !== 'false' : false, 20 | }; 21 | 22 | export default function reducer(state: State = initialState, action: Action) { 23 | switch (action.type) { 24 | case TOGGLE: { 25 | const visible = !state.visible; 26 | 27 | localStorage.setItem(LOCAL_STORAGE_SIDEBAR, visible); 28 | 29 | return { 30 | ...state, 31 | visible, 32 | }; 33 | } 34 | default: 35 | return state; 36 | } 37 | } 38 | 39 | // Action Creators 40 | export function toggleSidebar(): Action { 41 | return { type: TOGGLE }; 42 | } 43 | -------------------------------------------------------------------------------- /storybook/config.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { addDecorator, configure } from '@kadira/storybook'; 3 | import { Provider } from 'react-redux'; 4 | import { MemoryRouter } from 'react-router'; 5 | import injectTapEventPlugin from 'react-tap-event-plugin'; 6 | 7 | import { ThemeProvider, createTheme } from '../src'; 8 | import store from './store'; 9 | 10 | injectTapEventPlugin(); 11 | 12 | const theme = createTheme({ 13 | palette: { 14 | primary1Color: '#8b2756', 15 | accent1Color: '#661f42', 16 | accent2Color: '#3d192f', 17 | }, 18 | }); 19 | 20 | addDecorator(story => 21 | 22 | 23 | 24 | {story()} 25 | 26 | 27 | , 28 | ); 29 | 30 | const req = require.context('./stories', true, /\.story\.js$/); 31 | 32 | function loadStories() { 33 | req.keys().forEach(filename => req(filename)); 34 | } 35 | 36 | configure(loadStories, module); 37 | -------------------------------------------------------------------------------- /src/components/tabs/RoutedTabs.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { withRouter } from 'react-router-dom'; 4 | import Tabs from './Tabs'; 5 | import Tab from './Tab'; 6 | 7 | const RoutedTabs = ({ tabs, location, history }) => { 8 | let initialSelectedIndex = 0; 9 | tabs.forEach((tab, tabIndex) => { 10 | if (tab.route === location.pathname) { 11 | initialSelectedIndex = tabIndex; 12 | } 13 | }); 14 | 15 | return ( 16 | 17 | {tabs.map((tab, tabIndex) => 18 | history.replace(tab.route)}> 19 | {tabIndex === initialSelectedIndex ? tab.component : null} 20 | , 21 | )} 22 | 23 | ); 24 | }; 25 | 26 | RoutedTabs.propTypes = { 27 | tabs: PropTypes.arrayOf( 28 | PropTypes.shape({ 29 | route: PropTypes.string, 30 | label: PropTypes.string, 31 | component: PropTypes.node, 32 | }), 33 | ).isRequired, 34 | }; 35 | 36 | export default withRouter(RoutedTabs); 37 | -------------------------------------------------------------------------------- /src/components/utils/Loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { CircularProgress } from 'material-ui'; 4 | 5 | const Loading = ({ visible, background, zIndex, size, thickness }) => { 6 | const styles = { 7 | wrapper: { 8 | position: 'absolute', 9 | top: 0, 10 | right: 0, 11 | bottom: 0, 12 | left: 0, 13 | zIndex: visible ? zIndex : -1, 14 | opacity: visible ? 1 : 0, 15 | transition: 'all 0.2s', 16 | background, 17 | display: 'flex', 18 | width: '100%', 19 | height: '100%', 20 | alignItems: 'center', 21 | justifyContent: 'center', 22 | }, 23 | }; 24 | 25 | return ( 26 |
27 | 28 |
29 | ); 30 | }; 31 | 32 | Loading.defaultProps = { 33 | visible: true, 34 | background: '#FFFFFF', 35 | zIndex: 9999, 36 | }; 37 | 38 | Loading.propTypes = { 39 | visible: PropTypes.bool, 40 | background: PropTypes.string, 41 | zIndex: PropTypes.number, 42 | }; 43 | 44 | export default Loading; 45 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "jest": true 7 | }, 8 | "settings": { 9 | "import/resolver": "webpack" 10 | }, 11 | "rules": { 12 | "arrow-parens": 0, 13 | "react/jsx-filename-extension": 0, 14 | "import/extensions": 0, 15 | "max-len": [1, 120, 4, {"ignoreRegExpLiterals": true}], 16 | "jsx-a11y/no-static-element-interactions": 0, 17 | "react/no-unescaped-entities": 0, 18 | "react/forbid-prop-types": 0, 19 | "react/jsx-wrap-multilines": 0, 20 | "no-use-before-define": 0, 21 | "react/prop-types": 0, 22 | "react/jsx-boolean-value": 0, 23 | "default-case": 0, 24 | "react/no-children-prop": 0, 25 | "jsx-a11y/img-has-alt": 0, 26 | "comma-dangle": 0, 27 | "no-return-assign": 0, 28 | "consistent-return": 0, 29 | "class-methods-use-this": 0, 30 | "import/prefer-default-export": 0, 31 | "react/no-did-mount-set-state": 0, 32 | "prefer-const": [ 2, { "destructuring": "all" }], 33 | "react/prefer-stateless-function": [2, { "ignorePureComponents": true }] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Entria Tech Team 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 | -------------------------------------------------------------------------------- /src/components/tabs/ScrollableRoutedTabs.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { withRouter } from 'react-router-dom'; 4 | import { Tabs, Tab } from 'material-ui-scrollable-tabs/Tabs'; 5 | 6 | const ScrollableRoutedTabs = ({ tabs, location, history }) => { 7 | let initialSelectedIndex = 0; 8 | tabs.forEach((tab, tabIndex) => { 9 | if (tab.route === location.pathname) { 10 | initialSelectedIndex = tabIndex; 11 | } 12 | }); 13 | 14 | return ( 15 | 16 | {tabs.map((tab, tabIndex) => ( 17 | history.replace(tab.route)}> 18 | {tabIndex === initialSelectedIndex ? tab.component : null} 19 | 20 | ))} 21 | 22 | ); 23 | }; 24 | 25 | ScrollableRoutedTabs.propTypes = { 26 | tabs: PropTypes.arrayOf( 27 | PropTypes.shape({ 28 | route: PropTypes.string, 29 | label: PropTypes.string, 30 | component: PropTypes.node, 31 | }), 32 | ).isRequired, 33 | }; 34 | 35 | export default withRouter(ScrollableRoutedTabs); 36 | -------------------------------------------------------------------------------- /src/components/content/ContentHeader.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { getTheme } from '../Theme'; 5 | import ContentHeaderActions from './ContentHeaderActions'; 6 | 7 | const ContentHeader = ({ title, actions }) => 8 |
9 |

10 | {title} 11 |

12 | 13 | {actions &&
{actions}
} 14 |
; 15 | 16 | ContentHeader.Actions = ContentHeaderActions; 17 | 18 | const styles = () => ({ 19 | container: { 20 | display: 'flex', 21 | alignItems: 'center', 22 | paddingTop: 40, 23 | paddingLeft: 40, 24 | paddingRight: 40, 25 | boxSizing: 'border-box', 26 | }, 27 | title: { 28 | width: '100%', 29 | margin: 0, 30 | fontSize: 28, 31 | fontWeight: 400, 32 | letterSpacing: '-0.015em', 33 | color: getTheme().palette.primary1Color, 34 | }, 35 | actions: {}, 36 | }); 37 | 38 | ContentHeader.defaultProps = { 39 | title: null, 40 | actions: null, 41 | }; 42 | 43 | ContentHeader.propTypes = { 44 | title: PropTypes.string, 45 | actions: PropTypes.node, 46 | }; 47 | 48 | export default ContentHeader; 49 | -------------------------------------------------------------------------------- /storybook/stories/Content.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@kadira/storybook'; 3 | import loremIpsum from 'lorem-ipsum'; 4 | 5 | import FlatButton from 'material-ui/FlatButton'; 6 | import { Content } from '../../src'; 7 | 8 | const stories = storiesOf('Content', module); 9 | 10 | const HeaderActions = ( 11 | 12 | 13 | 14 | 15 | ); 16 | 17 | stories.add('default', () => 18 | 19 | {loremIpsum({ count: 3, units: 'paragraphs' })} 20 | , 21 | ); 22 | 23 | stories.add('with body', () => 24 | 25 | 26 | {loremIpsum({ count: 3, units: 'paragraphs' })} 27 | 28 | , 29 | ); 30 | 31 | stories.add('with header', () => 32 | 33 | 34 | , 35 | ); 36 | 37 | stories.add('with body and header', () => 38 | 39 | 40 | 41 | {loremIpsum({ count: 3, units: 'paragraphs' })} 42 | 43 | , 44 | ); 45 | -------------------------------------------------------------------------------- /storybook/stories/Card.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@kadira/storybook'; 3 | import loremIpsum from 'lorem-ipsum'; 4 | 5 | import FlatButton from 'material-ui/FlatButton'; 6 | import { Card } from '../../src'; 7 | 8 | const stories = storiesOf('Card', module); 9 | 10 | stories.addDecorator(story =>
{story()}
); 11 | 12 | stories.add('default', () => 13 | 14 | {loremIpsum({ count: 3, units: 'paragraphs' })} 15 | , 16 | ); 17 | 18 | stories.add('with header', () => 19 | 20 | {loremIpsum()} 21 | {loremIpsum({ count: 3, units: 'paragraphs' })} 22 | , 23 | ); 24 | 25 | stories.add('with footer', () => 26 | 27 | {loremIpsum({ count: 3, units: 'paragraphs' })} 28 | 29 | 30 | 31 | , 32 | ); 33 | 34 | stories.add('with header and footer', () => 35 | 36 | {loremIpsum()} 37 | {loremIpsum({ count: 3, units: 'paragraphs' })} 38 | 39 | 40 | 41 | , 42 | ); 43 | 44 | const styles = { 45 | wrapper: { 46 | padding: 20, 47 | height: 'auto', 48 | boxSizing: 'border-box', 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /storybook/stories/Header.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@kadira/storybook'; 3 | 4 | import { Header, getTheme } from '../../src'; 5 | 6 | const stories = storiesOf('Header', module); 7 | 8 | const BrandWithoutLogo = ; 9 | 10 | const BrandWihLogo = ( 11 | 16 | ); 17 | 18 | stories.add('default', () =>
); 19 | 20 | stories.add('styled', () => 21 |
Header} 23 | style={styles.styledHeader} 24 | />); 25 | 26 | stories.add('brand without logo on left', () =>
); 27 | 28 | stories.add('brand without logo on title', () =>
); 29 | 30 | stories.add('brand without logo on right', () =>
); 31 | 32 | stories.add('brand with logo on left', () =>
); 33 | 34 | stories.add('brand with logo on title', () =>
); 35 | 36 | stories.add('brand with logo on right', () =>
); 37 | 38 | const styles = { 39 | styledHeader: { 40 | boxShadow: 0, 41 | background: getTheme().palette.primary1Color, 42 | }, 43 | styledBrand: { 44 | color: '#FFF', 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /storybook/stories/AnimatedIcons.story.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { storiesOf } from '@kadira/storybook'; 3 | 4 | import IconButton from 'material-ui/IconButton'; 5 | import { AnimatedIcons } from '../../src'; 6 | 7 | const stories = storiesOf('AnimatedIcons', module); 8 | 9 | stories.addDecorator(story => 10 |
11 |
{story()}
12 |
13 | ); 14 | 15 | class StatefullButton extends Component { 16 | state = this.props.initialState; 17 | 18 | onTouchTap() { 19 | this.setState(this.props.onTouchTap(this.state)); 20 | } 21 | 22 | render() { 23 | return ( 24 |
25 | this.onTouchTap()}> 26 | 27 | 28 |
29 | ); 30 | } 31 | } 32 | 33 | stories.add('default', () => 34 | ( 40 | { opened: !state.opened } 41 | )} 42 | />, 43 | ); 44 | 45 | const styles = { 46 | wrapper: { 47 | display: 'flex', 48 | alignItems: 'center', 49 | justifyContent: 'center', 50 | padding: 20, 51 | }, 52 | icons: { 53 | borderTop: '1px solid #eee', 54 | borderRight: '1px solid #eee', 55 | }, 56 | icon: { 57 | padding: 5, 58 | borderBottom: '1px solid #eee', 59 | borderLeft: '1px solid #eee', 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /src/components/content/Content.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | import { connect } from 'react-redux'; 5 | 6 | import { getTheme } from '../Theme'; 7 | import { VIEWPORT } from '../Responsive'; 8 | import ContentBody from './ContentBody'; 9 | import ContentHeader from './ContentHeader'; 10 | 11 | const Wrapper = styled.div` 12 | box-sizing: border-box; 13 | display: flex; 14 | flex-direction: column; 15 | min-height: 100%; 16 | padding-top: ${() => `${getTheme().appBar.height}px`}; 17 | padding-left: 0px; 18 | transition-property: padding; 19 | transition-duration: 300ms; 20 | 21 | @media(min-width: ${VIEWPORT.MEDIUM}px) { 22 | padding-left: ${props => (props.sidebar.visible ? `${getTheme().drawer.width}px` : 0)}; 23 | } 24 | `; 25 | 26 | const Content = ({ sidebar, style, children }) => 27 | 28 |
29 | {children} 30 |
31 |
; 32 | 33 | Content.Body = ContentBody; 34 | Content.Header = ContentHeader; 35 | 36 | const styles = () => ({ 37 | content: { 38 | position: 'relative', 39 | boxSizing: 'border-box', 40 | width: '100%', 41 | minHeight: '100%', 42 | flexGrow: 1, 43 | }, 44 | }); 45 | 46 | Content.defaultProps = { 47 | style: {}, 48 | }; 49 | 50 | Content.propTypes = { 51 | style: PropTypes.object, 52 | }; 53 | 54 | const mapStateToProps = state => ({ 55 | sidebar: state.sidebar, 56 | }); 57 | 58 | export default connect(mapStateToProps)(Content); 59 | -------------------------------------------------------------------------------- /src/components/Theme.js: -------------------------------------------------------------------------------- 1 | import { Objects } from '@entria/utils'; 2 | import { css } from 'glamor'; 3 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 4 | import { white } from 'material-ui/styles/colors'; 5 | 6 | const LOCAL_STORAGE_CONFIG = 'entria-components-v1-theme'; 7 | 8 | const defaultConfig = { 9 | fontFamily: '"Montserrat", sans-serif', 10 | palette: { 11 | primary1Color: '#2B8CE9', 12 | primary2Color: '#FC615D', 13 | primary3Color: '#FE9958', 14 | accent1Color: '#129ACA', 15 | accent2Color: '#1598CE', 16 | accent3Color: '#999999', 17 | textColor: '#4F4F4F', 18 | }, 19 | appBar: { 20 | height: 100, 21 | color: white, 22 | textColor: '#000', 23 | padding: 0, 24 | }, 25 | drawer: { 26 | color: white, 27 | width: 300, 28 | }, 29 | 30 | // ENTRIA Custom 31 | responsive: { 32 | small: 480, 33 | medium: 768, 34 | }, 35 | }; 36 | 37 | export function createTheme(customConfig) { 38 | localStorage.setItem(LOCAL_STORAGE_CONFIG, JSON.stringify(customConfig)); 39 | 40 | const config = Objects.concat(defaultConfig, customConfig); 41 | 42 | css.global('html, body, #root, [data-reactroot]', { 43 | margin: 0, 44 | padding: 0, 45 | width: '100%', 46 | height: '100%', 47 | fontFamily: config.fontFamily, 48 | }); 49 | 50 | return getMuiTheme(config); 51 | } 52 | 53 | export function getTheme() { 54 | const customConfig = localStorage.getItem(LOCAL_STORAGE_CONFIG) 55 | ? JSON.parse(localStorage.getItem(LOCAL_STORAGE_CONFIG)) 56 | : {}; 57 | 58 | const config = Objects.concat(defaultConfig, customConfig); 59 | 60 | return getMuiTheme(config); 61 | } 62 | -------------------------------------------------------------------------------- /src/components/ThemeProvider.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 4 | import Snackbar from 'material-ui/Snackbar'; 5 | 6 | import { createTheme } from './Theme'; 7 | 8 | class ThemeProvider extends Component { 9 | static childContextTypes = { 10 | showSnackbar: PropTypes.func, 11 | }; 12 | 13 | state = { 14 | snackbar: { 15 | message: '', 16 | action: 'OK', 17 | duration: 8000, 18 | }, 19 | }; 20 | 21 | getChildContext() { 22 | return { 23 | showSnackbar: this.handleSnackbar, 24 | }; 25 | } 26 | 27 | handleSnackbar = ({ message, action = 'OK', duration = 4000 }) => { 28 | this.setState({ 29 | snackbar: { 30 | message, 31 | action, 32 | duration, 33 | }, 34 | }); 35 | }; 36 | 37 | handleSnackbarConfirm = () => { 38 | this.setState({ 39 | snackbar: { 40 | ...this.state.snackbar, 41 | message: '', 42 | }, 43 | }); 44 | }; 45 | 46 | render() { 47 | const { snackbar } = this.state; 48 | const { theme, children } = this.props; 49 | 50 | const muiTheme = theme || createTheme({}); 51 | 52 | return ( 53 | 54 |
55 | {children} 56 | 57 | 65 |
66 |
67 | ); 68 | } 69 | } 70 | 71 | export default ThemeProvider; 72 | -------------------------------------------------------------------------------- /src/components/header/HeaderBrand.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const Brand = ({ image, title, subtitle, style, to }) => ( 6 | 7 | {image ? ( 8 | 9 | ) : ( 10 | 11 | )} 12 | 13 | ); 14 | 15 | const BrandWithImage = ({ image, title }) => ( 16 |
17 | {title} 18 |
19 | ); 20 | 21 | const BrandWithoutImage = ({ title, subtitle }) => ( 22 |
23 |

{title}

24 | 25 | {subtitle} 26 |
27 | ); 28 | 29 | const styles = () => ({ 30 | link: { 31 | height: '100%', 32 | textDecoration: 'none', 33 | color: '#232323', 34 | }, 35 | withImage: { 36 | width: '100%', 37 | height: '100%', 38 | display: 'flex', 39 | textAlign: 'center', 40 | alignItems: 'center', 41 | justifyContent: 'center', 42 | }, 43 | image: { 44 | maxWidth: '100%', 45 | maxHeight: '100%', 46 | }, 47 | withoutImage: { 48 | height: '100%', 49 | display: 'flex', 50 | alignItems: 'center', 51 | flexDirection: 'column', 52 | justifyContent: 'center', 53 | }, 54 | title: { 55 | margin: 0, 56 | padding: 0, 57 | fontSize: 23, 58 | }, 59 | subtitle: { 60 | margin: 0, 61 | padding: 0, 62 | fontSize: 12.5, 63 | textTransform: 'uppercase', 64 | }, 65 | }); 66 | 67 | Brand.defaultProps = { 68 | style: {}, 69 | }; 70 | 71 | Brand.propTypes = { 72 | style: PropTypes.object, 73 | }; 74 | 75 | export default Brand; 76 | -------------------------------------------------------------------------------- /src/components/animatedIcons/MenuIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { css } from 'glamor'; 4 | 5 | const MenuIcon = ({ opened, size, spaces, color }) => { 6 | const linesHeight = size * 3; 7 | const spacesHeight = size * spaces * 3; 8 | const height = linesHeight + spacesHeight; 9 | 10 | const defaultStyle = css({ 11 | height, 12 | position: 'relative', 13 | transform: 'rotate(0deg)', 14 | transition: '.5s ease-in-out', 15 | '> span': { 16 | display: 'block', 17 | position: 'absolute', 18 | left: 0, 19 | width: '100%', 20 | height: size, 21 | borderRadius: size, 22 | background: color, 23 | opacity: 1, 24 | transform: 'rotate(0deg)', 25 | transition: '.25s ease-in-out', 26 | }, 27 | '> span:nth-child(1)': { 28 | top: 0, 29 | }, 30 | '> span:nth-child(2), > span:nth-child(3)': { 31 | top: size * spaces * 2, 32 | }, 33 | '> span:nth-child(4)': { 34 | top: size * spaces * 4, 35 | width: '80%', 36 | }, 37 | }); 38 | 39 | const openedStyle = css({ 40 | '> span:nth-child(1), > span:nth-child(4)': { 41 | top: size * spaces * 1.5, 42 | width: 0, 43 | left: '50%', 44 | }, 45 | '> span:nth-child(2)': { 46 | transform: 'rotate(45deg)', 47 | }, 48 | '> span:nth-child(3)': { 49 | transform: 'rotate(-45deg)', 50 | }, 51 | }); 52 | 53 | return ( 54 |
55 | 56 | 57 | 58 | 59 |
60 | ); 61 | }; 62 | 63 | MenuIcon.defaultProps = { 64 | opened: false, 65 | size: 2, 66 | spaces: 1.5, 67 | color: '#323232', 68 | }; 69 | 70 | MenuIcon.propTypes = { 71 | opened: PropTypes.bool, 72 | size: PropTypes.number, 73 | spaces: PropTypes.number, 74 | color: PropTypes.string, 75 | }; 76 | 77 | export default MenuIcon; 78 | -------------------------------------------------------------------------------- /storybook/stories/Template.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { storiesOf } from '@kadira/storybook'; 4 | import loremIpsum from 'lorem-ipsum'; 5 | 6 | import IconButton from 'material-ui/IconButton'; 7 | import FlatButton from 'material-ui/FlatButton'; 8 | import { Header, Sidebar, Content, AnimatedIcons } from '../../src'; 9 | 10 | const stories = storiesOf('Template', module); 11 | 12 | const CustomHeader = connect( 13 | state => ({ 14 | sidebar: state.sidebar, 15 | }), 16 | )( 17 | ({ sidebar }) => ( 18 |
21 | 22 | 23 | 24 | 25 | } 26 | title={ 27 | 28 | } 29 | /> 30 | ), 31 | ); 32 | 33 | stories.add('default', () => 34 |
35 | 36 | 37 | 38 | Entria 39 | 40 | Link 1 41 | Link 2 42 | Link 3 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | } 55 | /> 56 | 57 | 58 | {loremIpsum({ count: 3, units: 'paragraphs' })} 59 | 60 | 61 |
, 62 | ); 63 | 64 | const styles = { 65 | header: { 66 | width: '100%', 67 | display: 'flex', 68 | alignItems: 'center', 69 | }, 70 | sidebarItem: { 71 | color: '#545454', 72 | }, 73 | }; 74 | -------------------------------------------------------------------------------- /src/components/sidebar/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | import Drawer from 'material-ui/Drawer'; 5 | 6 | import { connect } from 'react-redux'; 7 | 8 | import { getTheme } from '../Theme'; 9 | import { VIEWPORT, isLarge } from '../Responsive'; 10 | import SidebarHeader from './SidebarHeader'; 11 | import SidebarItem from './SidebarItem'; 12 | import SidebarMenu from './SidebarMenu'; 13 | import SidebarToggler from './SidebarToggler'; 14 | 15 | const Overlay = styled.div` 16 | transition: all 200ms; 17 | position: fixed; 18 | top: 0px; 19 | right: 0px; 20 | bottom: 0px; 21 | left: 0px; 22 | background: rgba(0, 0, 0, .1); 23 | opacity: 0; 24 | z-index: -1; 25 | 26 | @media(max-width: ${VIEWPORT.MEDIUM}px) { 27 | opacity: ${props => (!isLarge() && props.visible ? 1 : 0)}; 28 | z-index: ${props => (!isLarge() && props.visible ? 1200 : -1)}; 29 | } 30 | `; 31 | 32 | const Sidebar = ({ visible, style, children }) => { 33 | const containerStyle = { 34 | ...styles().container, 35 | ...style, 36 | }; 37 | 38 | const top = /^\d+$/.test(containerStyle.top) ? `${containerStyle.top}px` : containerStyle.top; 39 | containerStyle.height = `calc(100% - ${top})`; 40 | 41 | return ( 42 |
43 | 44 | {children} 45 | 46 | 47 | 48 |
49 | ); 50 | }; 51 | 52 | Sidebar.Header = SidebarHeader; 53 | Sidebar.Item = SidebarItem; 54 | Sidebar.Menu = SidebarMenu; 55 | Sidebar.Toggler = SidebarToggler; 56 | 57 | const styles = () => ({ 58 | container: { 59 | top: getTheme().appBar.height, 60 | }, 61 | }); 62 | 63 | Sidebar.defaultProps = { 64 | style: {}, 65 | }; 66 | 67 | Sidebar.propTypes = { 68 | style: PropTypes.object, 69 | }; 70 | 71 | const mapStateToProps = state => ({ 72 | visible: state.sidebar.visible, 73 | }); 74 | 75 | export default connect(mapStateToProps)(Sidebar); 76 | -------------------------------------------------------------------------------- /src/components/sidebar/SidebarItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { NavLink } from 'react-router-dom'; 4 | 5 | import { ListItem } from 'material-ui/List'; 6 | 7 | import { connect } from 'react-redux'; 8 | import { toggleSidebar } from '../../ducks/Sidebar'; 9 | 10 | import { isLarge } from '../Responsive'; 11 | 12 | const SidebarItem = ({ 13 | link, 14 | style, 15 | activeStyle, 16 | exact, 17 | external, 18 | target, 19 | sidebar, 20 | actions, 21 | children, 22 | }) => { 23 | if (external) { 24 | return ( 25 | 26 | 27 | {children} 28 | 29 | 30 | ); 31 | } 32 | 33 | return ( 34 | { 40 | if (!isLarge() && sidebar.visible) { 41 | actions.toggleSidebar(); 42 | } 43 | }} 44 | > 45 | 46 | {children} 47 | 48 | 49 | ); 50 | }; 51 | 52 | const styles = () => ({ 53 | link: { 54 | transition: 'all 100ms linear', 55 | textDecoration: 'none', 56 | }, 57 | activeLink: { 58 | display: 'block', 59 | }, 60 | listItem: { 61 | color: 'inherit', 62 | }, 63 | }); 64 | 65 | SidebarItem.defaultProps = { 66 | style: {}, 67 | activeStyle: {}, 68 | exact: true, 69 | external: false, 70 | target: '_blank', 71 | }; 72 | 73 | SidebarItem.propTypes = { 74 | link: PropTypes.string.isRequired, 75 | style: PropTypes.object, 76 | activeStyle: PropTypes.object, 77 | exact: PropTypes.bool, 78 | external: PropTypes.bool, 79 | target: PropTypes.string, 80 | }; 81 | 82 | const mapStateToProps = state => ({ 83 | sidebar: state.sidebar, 84 | }); 85 | 86 | const mapDispatchToProps = dispatch => ({ 87 | actions: { 88 | toggleSidebar: () => dispatch(toggleSidebar()), 89 | }, 90 | }); 91 | 92 | export default connect(mapStateToProps, mapDispatchToProps)(SidebarItem); 93 | -------------------------------------------------------------------------------- /src/components/header/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import AppBar from 'material-ui/AppBar'; 4 | 5 | import { getTheme } from '../Theme'; 6 | import HeaderBrand from './HeaderBrand'; 7 | 8 | const Header = ({ left, title, right, style, leftStyle, titleStyle, rightStyle }) => 9 |
10 | 20 |
; 21 | 22 | Header.Brand = HeaderBrand; 23 | 24 | const styles = () => ({ 25 | wrapper: { 26 | position: 'fixed', 27 | top: 0, 28 | zIndex: 1400, 29 | boxShadow: 'rgba(0, 0, 0, 0.004) 0px 5px 10px, rgba(0, 0, 0, 0.1) 0px 8px 20px', 30 | overflow: 'hidden', 31 | }, 32 | left: { 33 | width: getTheme().drawer.width, 34 | height: getTheme().appBar.height, 35 | margin: 0, 36 | padding: 20, 37 | boxSizing: 'border-box', 38 | display: 'flex', 39 | alignItems: 'center', 40 | }, 41 | title: { 42 | height: getTheme().appBar.height, 43 | margin: 0, 44 | padding: 20, 45 | boxSizing: 'border-box', 46 | display: 'flex', 47 | alignItems: 'center', 48 | justifyContent: 'center', 49 | lineHeight: 'initial', 50 | }, 51 | right: { 52 | width: getTheme().drawer.width, 53 | height: getTheme().appBar.height, 54 | margin: 0, 55 | padding: 20, 56 | boxSizing: 'border-box', 57 | display: 'flex', 58 | alignItems: 'center', 59 | justifyContent: 'flex-end', 60 | }, 61 | }); 62 | 63 | Header.defaultProps = { 64 | style: {}, 65 | leftStyle: {}, 66 | titleStyle: {}, 67 | rightStyle: {}, 68 | left: null, 69 | title: null, 70 | right: null, 71 | }; 72 | 73 | Header.propTypes = { 74 | style: PropTypes.object, 75 | leftStyle: PropTypes.object, 76 | titleStyle: PropTypes.object, 77 | rightStyle: PropTypes.object, 78 | left: PropTypes.node, 79 | title: PropTypes.node, 80 | right: PropTypes.node, 81 | }; 82 | 83 | export default Header; 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@entria/components", 3 | "version": "0.1.9", 4 | "description": "Entria common components", 5 | "keywords": [ 6 | "react", 7 | "entria", 8 | "common", 9 | "components" 10 | ], 11 | "license": "ISC", 12 | "homepage": "https://github.com/entria/entria-components#readme", 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/entria/entria-components" 16 | }, 17 | "main": "lib/index.js", 18 | "module": "src/index.js", 19 | "jsnext:main": "src/index.js", 20 | "devDependencies": { 21 | "@entria/utils": "^0.0.3", 22 | "@kadira/storybook": "^2.35.3", 23 | "babel-cli": "^6.24.1", 24 | "babel-eslint": "^7.2.3", 25 | "babel-plugin-material-ui": "^0.3.1", 26 | "babel-preset-es2015": "^6.24.1", 27 | "babel-preset-flow": "^6.23.0", 28 | "babel-preset-react": "^6.24.1", 29 | "babel-preset-stage-0": "^6.24.1", 30 | "eslint": "^3.19.0", 31 | "eslint-config-airbnb": "^15.0.1", 32 | "eslint-import-resolver-webpack": "^0.8.1", 33 | "eslint-plugin-import": "^2.3.0", 34 | "eslint-plugin-jsx-a11y": "^5.0.3", 35 | "eslint-plugin-react": "^7.1.0", 36 | "flow-bin": "^0.48.0", 37 | "gh-pages": "^1.0.0", 38 | "glamor": "^3.0.0-2", 39 | "lint-staged": "^4.0.0", 40 | "lorem-ipsum": "^1.0.4", 41 | "material-ui": "0.19.4", 42 | "material-ui-scrollable-tabs": "^2.0.0", 43 | "pre-commit": "^1.2.2", 44 | "prettier": "^1.4.4", 45 | "prop-types": "^15.5.10", 46 | "react": "^15.6.1", 47 | "react-dom": "^15.6.1", 48 | "react-redux": "^5.0.5", 49 | "react-router-dom": "^4.1.1", 50 | "react-tap-event-plugin": "^2.0.1", 51 | "redux": "^3.7.0", 52 | "styled-components": "^2.1.1" 53 | }, 54 | "dependencies": {}, 55 | "peerDependencies": { 56 | "@entria/utils": ">=0.0.3", 57 | "glamor": ">=3.0.0-2", 58 | "material-ui": ">=0.19.4", 59 | "material-ui-scrollable-tabs": ">=2.0.0", 60 | "prop-types": ">=15.5.10", 61 | "react": ">=0.14", 62 | "react-dom": ">=0.14", 63 | "react-redux": ">=5.0.5", 64 | "react-router-dom": ">=4.1.1", 65 | "redux": ">=3.7.0", 66 | "styled-components": ">=2.1.1" 67 | }, 68 | "lint-staged": { 69 | "src/**/*.js": [ 70 | "prettier --write --single-quote true --trailing-comma all --print-width 100", 71 | "git add" 72 | ] 73 | }, 74 | "pre-commit": "lint:staged", 75 | "scripts": { 76 | "prepublish": "npm run build", 77 | "build": "babel src -d lib", 78 | "lint": "eslint --ext js src", 79 | "lint:staged": "lint-staged", 80 | "start": "start-storybook -p 6006 -c storybook", 81 | "demo:build": "build-storybook -c storybook", 82 | "demo:gh-pages": "gh-pages -d storybook-static/", 83 | "demo:publish": "npm run demo:build && npm run demo:gh-pages", 84 | "check": "npm run lint && npm run test", 85 | "release:patch": "npm run check && npm version patch && git push --follow-tags && npm publish --access public", 86 | "release:minor": "npm run check && npm version minor && git push --follow-tags && npm publish --access public", 87 | "release:major": "npm run check && npm version major && git push --follow-tags && npm publish --access public" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /storybook/stories/Sidebar.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@kadira/storybook'; 3 | 4 | import FlatButton from 'material-ui/FlatButton'; 5 | import { Sidebar, Content, getTheme } from '../../src'; 6 | 7 | const stories = storiesOf('Sidebar', module); 8 | 9 | stories.add('default', () => 10 | 11 | 12 | Link 1 13 | Link 2 14 | Link 3 15 | 16 | , 17 | ); 18 | 19 | stories.add('styled', () => 20 | 21 | 22 | Link 1 23 | Link 2 24 | Link 3 25 | 26 | , 27 | ); 28 | 29 | stories.add('with header', () => 30 | 31 | Header 32 | 33 | Link 1 34 | Link 2 35 | Link 3 36 | 37 | , 38 | ); 39 | 40 | stories.add('with many menus', () => 41 | 42 | Header 43 | 44 | Link 1 45 | Link 2 46 | Link 3 47 | 48 | 49 | Link 4 50 | Link 5 51 | Link 6 52 | 53 | , 54 | ); 55 | 56 | stories.add('with toggler', () => 57 | 58 | 59 | Header 60 | 61 | Link 1 62 | Link 2 63 | Link 3 64 | 65 | 66 | Link 4 67 | Link 5 68 | Link 6 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | , 78 | ); 79 | 80 | const styles = { 81 | content: { 82 | paddingTop: 0, 83 | }, 84 | sidebar: { 85 | top: 0, 86 | }, 87 | item: { 88 | color: '#545454', 89 | }, 90 | styledSidebar: { 91 | boxShadow: 0, 92 | background: getTheme().palette.primary1Color, 93 | }, 94 | styledSidebarItem: { 95 | color: '#FFF', 96 | }, 97 | }; 98 | -------------------------------------------------------------------------------- /storybook/stories/Tabs.story.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@kadira/storybook'; 3 | 4 | import { Tabs, Tab, RoutedTabs, ScrollableRoutedTabs } from '../../src'; 5 | 6 | const stories = storiesOf('Tabs', module); 7 | 8 | stories.add('default', () => 9 |
10 | 11 | 12 |
Component 1
13 |
14 | 15 |
Component 2
16 |
17 | 18 |
Component 3
19 |
20 |
21 |
22 | ); 23 | 24 | stories.add('RoutedTabs', () => 25 |
26 |

I change the route when you click me!

27 | 28 | Component 1
, 34 | }, 35 | { 36 | route: '/route-2', 37 | label: 'Tab 2', 38 | component:
Component 2
, 39 | }, 40 | { 41 | route: '/route-3', 42 | label: 'Tab 3', 43 | component:
Component 3
, 44 | }, 45 | ]} 46 | /> 47 | 48 | ); 49 | 50 | stories.add('ScrollableRoutedTabs', () => 51 |
52 |

I change the route when you click me!

53 | 54 | Component 1
, 60 | }, 61 | { 62 | route: '/route-2', 63 | label: 'Tab 2', 64 | component:
Component 2
, 65 | }, 66 | { 67 | route: '/route-3', 68 | label: 'Tab 3', 69 | component:
Component 3
, 70 | }, 71 | { 72 | route: '/route-4', 73 | label: 'Tab 4', 74 | component:
Component 4
, 75 | }, 76 | { 77 | route: '/route-5', 78 | label: 'Tab 5', 79 | component:
Component 5
, 80 | }, 81 | { 82 | route: '/route-6', 83 | label: 'Tab 6', 84 | component:
Component 6
, 85 | }, 86 | { 87 | route: '/route-7', 88 | label: 'Tab 7', 89 | component:
Component 7
, 90 | }, 91 | { 92 | route: '/route-8', 93 | label: 'Tab 8', 94 | component:
Component 8
, 95 | }, 96 | { 97 | route: '/route-9', 98 | label: 'Tab 9', 99 | component:
Component 9
, 100 | }, 101 | ]} 102 | /> 103 | 104 | ); 105 | 106 | const styles = { 107 | wrapper: { 108 | padding: 20, 109 | }, 110 | message: { 111 | marginBottom: 30, 112 | }, 113 | tabComponent: { 114 | padding: 20, 115 | }, 116 | }; 117 | --------------------------------------------------------------------------------