├── .npmignore ├── demo ├── js │ ├── index.js │ └── basicDemo.js ├── baseStyle.css └── index.html ├── .babelrc ├── bar-example.png ├── index.js ├── src ├── index.js ├── css │ ├── Titlebar.scss │ └── Titlebar.css └── Titlebar.js ├── .sass-cache ├── 50867431e20832840eca9e0ff0b1c682c7d7fc99 │ └── Titlebar.scssc └── e1b5660542d2358f8b0257871f9d73b126078b09 │ └── Titlebar.scssc ├── lib ├── index.js ├── css │ └── Titlebar.css └── Titlebar.js ├── webpack-demo.config.js ├── .eslintrc ├── README.md ├── webpack-umd.config.js ├── package.json └── umd ├── ReactTitlebar.min.js └── ReactTitlebar.js /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/js/index.js: -------------------------------------------------------------------------------- 1 | import './basicDemo'; 2 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-2"] 3 | } 4 | -------------------------------------------------------------------------------- /bar-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santomegonzalo/react-titlebar-macos/HEAD/bar-example.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Titlebar from './lib/Titlebar'; 2 | 3 | module.exports = { 4 | Titlebar, 5 | }; 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Titlebar from './Titlebar'; 2 | 3 | module.exports = { 4 | Titlebar, 5 | }; 6 | -------------------------------------------------------------------------------- /.sass-cache/50867431e20832840eca9e0ff0b1c682c7d7fc99/Titlebar.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santomegonzalo/react-titlebar-macos/HEAD/.sass-cache/50867431e20832840eca9e0ff0b1c682c7d7fc99/Titlebar.scssc -------------------------------------------------------------------------------- /.sass-cache/e1b5660542d2358f8b0257871f9d73b126078b09/Titlebar.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santomegonzalo/react-titlebar-macos/HEAD/.sass-cache/e1b5660542d2358f8b0257871f9d73b126078b09/Titlebar.scssc -------------------------------------------------------------------------------- /demo/baseStyle.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: #333; 3 | -webkit-box-sizing: border-box; 4 | -moz-box-sizing: border-box; 5 | box-sizing: border-box; 6 | -webkit-font-smoothing: antialiased; 7 | max-width: 600px; 8 | margin: 0 auto 200px; 9 | padding: 10px; 10 | } 11 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _Titlebar = require('./Titlebar'); 4 | 5 | var _Titlebar2 = _interopRequireDefault(_Titlebar); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 8 | 9 | module.exports = { 10 | Titlebar: _Titlebar2.default 11 | }; -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-titlebar-osx demo 6 | 7 | 8 | 9 | 10 | 11 | 12 |

react-titlebar-osx

13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /webpack-demo.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: { 5 | demo: './demo/js/index.js', 6 | }, 7 | output: { 8 | path: path.join(__dirname, 'demo'), 9 | filename: 'demo-bundle.js', 10 | }, 11 | module: { 12 | loaders: [{ 13 | test: /\.js$/, 14 | exclude: /node_modules/, 15 | loader: 'babel', 16 | }, { 17 | test: /\.scss$/, 18 | loaders: ['style', 'css', 'sass'], 19 | }, { 20 | test: /\.css/, 21 | loaders: ['style', 'css'], 22 | }], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "parser": "babel-eslint", 4 | "rules": { 5 | 'class-methods-use-this': ['error', { 6 | "exceptMethods": [ 7 | 'render', 8 | 'getInitialState', 9 | 'getDefaultProps', 10 | 'getChildContext', 11 | 'componentWillMount', 12 | 'componentDidMount', 13 | 'componentWillReceiveProps', 14 | 'shouldComponentUpdate', 15 | 'componentWillUpdate', 16 | 'componentDidUpdate', 17 | 'componentWillUnmount', 18 | ], 19 | }], 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-titlebar-osx 2 | 3 | Emulate OS X window *titlebar* using **ES6** and **React**. Extracted from [kapetan/titlebar](https://github.com/kapetan/titlebar). 4 | 5 | ## demo 6 | ``` 7 | npm run demo-dev 8 | ``` 9 | 10 | ## how looks 11 | 12 | ![Example how looks](https://github.com/santomegonzalo/react-titlebar-osx/raw/master/bar-example.png "Real example") 13 | 14 | ## how to use it 15 | 16 | ``` 17 | npm install --save react-titlebar-osx 18 | ``` 19 | 20 | ```javascript 21 | import { Titlebar } from 'react-titlebar-osx'; 22 | ``` 23 | 24 | ```javascript 25 | this.handleClose()} 29 | onMaximize={() => this.handleMaximize()} 30 | onFullscreen={() => this.handleFullscreen()} 31 | onMinimize={() => this.handleMinimize()} 32 | /> 33 | ``` 34 | 35 | ## props 36 | 37 | - `draggable` (default `false`): Enable dragging. 38 | - `transparent` (default `false`): Transparent background. 39 | - `text`: Enable centered text. 40 | - `padding`: Add more space to the top and bottom. 41 | - `onClose`: (required) called when close is clicked. 42 | - `onMinimize`: (required) called when minimized is clicked. 43 | - `onMaximize`: (required) called when maximize is clicked. 44 | - `onFullscreen`: (required) called when fullscreen is clicked. 45 | -------------------------------------------------------------------------------- /webpack-umd.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | const plugins = [new webpack.optimize.OccurenceOrderPlugin()]; 5 | 6 | if (process.env.MINIFY) { 7 | plugins.push(new webpack.optimize.UglifyJsPlugin()); 8 | } 9 | 10 | const filename = (process.env.MINIFY) ? 'ReactTitlebar.min.js' : 'ReactTitlebar.js'; 11 | 12 | module.exports = { 13 | entry: { 14 | Titlebar: './index.js', 15 | }, 16 | output: { 17 | library: 'ReactTitlebar', 18 | libraryTarget: 'umd', 19 | path: path.join(__dirname, 'umd'), 20 | filename, 21 | }, 22 | externals: [ 23 | { 24 | react: { 25 | root: 'React', 26 | commonjs2: 'react', 27 | commonjs: 'react', 28 | amd: 'react', 29 | }, 30 | }, 31 | { 32 | 'react-dom': { 33 | root: 'ReactDOM', 34 | commonjs2: 'react-dom', 35 | commonjs: 'react-dom', 36 | amd: 'react-dom', 37 | }, 38 | }, 39 | ], 40 | module: { 41 | loaders: [{ 42 | test: /\.js$/, 43 | exclude: /node_modules/, 44 | loader: 'babel', 45 | }, { 46 | test: /\.scss$/, 47 | loaders: ['style', 'css', 'sass'], 48 | }, { 49 | test: /\.css/, 50 | loaders: ['style', 'css'], 51 | }], 52 | }, 53 | node: { 54 | Buffer: false, 55 | process: false, 56 | setImmediate: false, 57 | }, 58 | plugins, 59 | }; 60 | -------------------------------------------------------------------------------- /demo/js/basicDemo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Titlebar } from '../../src'; 4 | 5 | class Demo extends React.Component { 6 | 7 | handleMinimize() { 8 | console.log('Minize button clicked'); 9 | } 10 | 11 | handleMaximize() { 12 | console.log('Maximize button clicked'); 13 | } 14 | 15 | handleFullscreen() { 16 | console.log('Fullscreen button clicked'); 17 | } 18 | 19 | handleClose() { 20 | console.log('Close button clicked'); 21 | } 22 | 23 | render() { 24 | return ( 25 |
26 |
27 | this.handleClose()} 29 | onMaximize={() => this.handleMaximize()} 30 | onFullscreen={() => this.handleFullscreen()} 31 | onMinimize={() => this.handleMinimize()} 32 | /> 33 |
34 |
39 | this.handleClose()} 41 | onMaximize={() => this.handleMaximize()} 42 | onFullscreen={() => this.handleFullscreen()} 43 | onMinimize={() => this.handleMinimize()} 44 | title="Awesome titlebar" 45 | /> 46 |
47 |
52 | this.handleClose()} 54 | onMaximize={() => this.handleMaximize()} 55 | onFullscreen={() => this.handleFullscreen()} 56 | onMinimize={() => this.handleMinimize()} 57 | padding={10} 58 | title="Awesome titlebar - With Padding" 59 | /> 60 |
61 |
66 |
Transparent Background
67 | this.handleClose()} 70 | onMaximize={() => this.handleMaximize()} 71 | onFullscreen={() => this.handleFullscreen()} 72 | onMinimize={() => this.handleMinimize()} 73 | /> 74 |
75 |
76 | ); 77 | } 78 | } 79 | 80 | ReactDOM.render( 81 | , 82 | document.getElementById('demo'), // eslint-disable-line no-undef 83 | ); 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-titlebar-osx", 3 | "version": "1.1.0", 4 | "description": "osx controls to use on electron with react", 5 | "main": "lib/index", 6 | "scripts": { 7 | "lint": "eslint 'lib/Titlebar.js'", 8 | "demo-bundle": "webpack --config ./webpack-demo.config.js", 9 | "demo-watch": "webpack --config ./webpack-demo.config.js --watch", 10 | "demo-dev": "npm run demo-watch & http-server demo", 11 | "build:css": "sass --update --sourcemap=none src/css:src/css --style expanded && sass --update --sourcemap=none src/css:lib/css --style expanded", 12 | "build:umd:unmin": "webpack --config ./webpack-umd.config.js", 13 | "build:umd:min": "MINIFY=true webpack --config ./webpack-umd.config.js", 14 | "build:umd": "npm run build:umd:unmin && npm run build:umd:min", 15 | "build:commonjs": "mkdir -p lib && babel ./src -d lib", 16 | "publish": "npm run build:css && npm run build:commonjs && npm run build:umd" 17 | }, 18 | "files": [ 19 | "*.md", 20 | "umd", 21 | "lib", 22 | "src" 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/santomegonzalo/react-titlebar-osx.git" 27 | }, 28 | "author": "Gonzalo Santome", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/santomegonzalo/react-titlebar-osx/issues" 32 | }, 33 | "homepage": "https://github.com/santomegonzalo/react-titlebar-osx#readme", 34 | "keywords": [ 35 | "reactjs", 36 | "react-component", 37 | "react", 38 | "electron", 39 | "osx", 40 | "titlebar" 41 | ], 42 | "dependencies": { 43 | "classnames": "^2.2.5", 44 | "defaultcss": "^1.1.1", 45 | "react": "^15.3.2", 46 | "react-dom": "^15.3.2" 47 | }, 48 | "peerDependencies": { 49 | "react": "15.x.x", 50 | "react-dom": "15.x.x" 51 | }, 52 | "devDependencies": { 53 | "babel-cli": "^6.18.0", 54 | "babel-core": "^6.18.2", 55 | "babel-eslint": "^7.1.1", 56 | "babel-loader": "^6.2.7", 57 | "babel-preset-es2015": "^6.18.0", 58 | "babel-preset-react": "^6.16.0", 59 | "babel-preset-stage-2": "^6.18.0", 60 | "css-loader": "^0.26.1", 61 | "eslint": "^3.12.2", 62 | "eslint-config-airbnb-base": "^11.0.0", 63 | "eslint-loader": "^1.6.1", 64 | "eslint-plugin-import": "^2.2.0", 65 | "http-server": "^0.9.0", 66 | "node-sass": "^4.1.1", 67 | "sass-loader": "^4.1.1", 68 | "style-loader": "^0.13.1", 69 | "webpack": "^1.13.3" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/css/Titlebar.scss: -------------------------------------------------------------------------------- 1 | .titlebar { 2 | padding: 0 3px; 3 | display: flex; 4 | background-color: #f6f6f6; 5 | 6 | &.transparent { 7 | background-color: transparent; 8 | } 9 | 10 | &.webkit-draggable { 11 | -webkit-app-region: drag; 12 | } 13 | 14 | &.alt { 15 | svg.fullscreen-svg { 16 | display: none; 17 | } 18 | 19 | svg.maximize-svg { 20 | display: block !important; 21 | } 22 | } 23 | 24 | &.webkit-draggable .titlebar-close, 25 | &.webkit-draggable .titlebar-minimize, 26 | &.webkit-draggable .titlebar-fullscreen { 27 | -webkit-app-region: no-drag; 28 | } 29 | } 30 | 31 | .titlebar-text { 32 | flex-grow: 1; 33 | text-align: center; 34 | font-family: 'Helvetica Neue', Helvetica; 35 | font-size: 14px; 36 | line-height: 24px; 37 | -webkit-touch-callout: none; 38 | -webkit-user-select: none; 39 | user-select: none; 40 | cursor: default; 41 | } 42 | 43 | .titlebar-stoplight { 44 | flex-grow: 0; 45 | display: flex; 46 | 47 | .titlebar-close, 48 | .titlebar-minimize, 49 | .titlebar-fullscreen { 50 | width: 10px; 51 | height: 10px; 52 | border-radius: 50%; 53 | margin: 6px 4px; 54 | line-height: 0; 55 | } 56 | 57 | .titlebar-close { 58 | border: 1px solid #e2463f; 59 | background-color: #ff5f57; 60 | margin-left: 6px; 61 | 62 | &:active { 63 | border-color: #ad3934; 64 | background-color: #bf4943; 65 | } 66 | 67 | svg { 68 | width: 6px; 69 | height: 6px; 70 | margin-top: 2px; 71 | margin-left: 2px; 72 | opacity: 0; 73 | } 74 | } 75 | 76 | .titlebar-minimize { 77 | border: 1px solid #e1a116; 78 | background-color: #ffbd2e; 79 | 80 | &:active { 81 | border-color: #ad7d15; 82 | background-color: #bf9123; 83 | } 84 | 85 | svg { 86 | width: 8px; 87 | height: 8px; 88 | margin-top: 1px; 89 | margin-left: 1px; 90 | opacity: 0; 91 | } 92 | } 93 | 94 | .titlebar-fullscreen, 95 | .titlebar-maximize { 96 | border: 1px solid #12ac28; 97 | background-color: #28c940; 98 | } 99 | 100 | .titlebar-fullscreen { 101 | &:active { 102 | border-color: #128622; 103 | background-color: #1f9a31; 104 | } 105 | 106 | svg.fullscreen-svg { 107 | width: 6px; 108 | height: 6px; 109 | margin-top: 2px; 110 | margin-left: 2px; 111 | opacity: 0; 112 | } 113 | 114 | svg.maximize-svg { 115 | width: 8px; 116 | height: 8px; 117 | margin-top: 1px; 118 | margin-left: 1px; 119 | opacity: 0; 120 | display: none; 121 | } 122 | } 123 | 124 | &:hover svg, 125 | &:hover svg.fullscreen-svg, 126 | &:hover svg.maximize-svg { 127 | opacity: 1; 128 | } 129 | } 130 | 131 | .titlebar:after, 132 | .titlebar-stoplight:after { 133 | content: ' '; 134 | display: table; 135 | clear: both; 136 | } 137 | -------------------------------------------------------------------------------- /lib/css/Titlebar.css: -------------------------------------------------------------------------------- 1 | .titlebar { 2 | padding: 0 3px; 3 | display: flex; 4 | background-color: #f6f6f6; } 5 | .titlebar.transparent { 6 | background-color: transparent; } 7 | .titlebar.webkit-draggable { 8 | -webkit-app-region: drag; } 9 | .titlebar.alt svg.fullscreen-svg { 10 | display: none; } 11 | .titlebar.alt svg.maximize-svg { 12 | display: block !important; } 13 | .titlebar.webkit-draggable .titlebar-close, .titlebar.webkit-draggable .titlebar-minimize, .titlebar.webkit-draggable .titlebar-fullscreen { 14 | -webkit-app-region: no-drag; } 15 | 16 | .titlebar-text { 17 | flex-grow: 1; 18 | text-align: center; 19 | font-family: 'Helvetica Neue', Helvetica; 20 | font-size: 14px; 21 | line-height: 24px; 22 | -webkit-touch-callout: none; 23 | -webkit-user-select: none; 24 | user-select: none; 25 | cursor: default; } 26 | 27 | .titlebar-stoplight { 28 | flex-grow: 0; 29 | display: flex; } 30 | .titlebar-stoplight .titlebar-close, 31 | .titlebar-stoplight .titlebar-minimize, 32 | .titlebar-stoplight .titlebar-fullscreen { 33 | width: 10px; 34 | height: 10px; 35 | border-radius: 50%; 36 | margin: 6px 4px; 37 | line-height: 0; } 38 | .titlebar-stoplight .titlebar-close { 39 | border: 1px solid #e2463f; 40 | background-color: #ff5f57; 41 | margin-left: 6px; } 42 | .titlebar-stoplight .titlebar-close:active { 43 | border-color: #ad3934; 44 | background-color: #bf4943; } 45 | .titlebar-stoplight .titlebar-close svg { 46 | width: 6px; 47 | height: 6px; 48 | margin-top: 2px; 49 | margin-left: 2px; 50 | opacity: 0; } 51 | .titlebar-stoplight .titlebar-minimize { 52 | border: 1px solid #e1a116; 53 | background-color: #ffbd2e; } 54 | .titlebar-stoplight .titlebar-minimize:active { 55 | border-color: #ad7d15; 56 | background-color: #bf9123; } 57 | .titlebar-stoplight .titlebar-minimize svg { 58 | width: 8px; 59 | height: 8px; 60 | margin-top: 1px; 61 | margin-left: 1px; 62 | opacity: 0; } 63 | .titlebar-stoplight .titlebar-fullscreen, 64 | .titlebar-stoplight .titlebar-maximize { 65 | border: 1px solid #12ac28; 66 | background-color: #28c940; } 67 | .titlebar-stoplight .titlebar-fullscreen:active { 68 | border-color: #128622; 69 | background-color: #1f9a31; } 70 | .titlebar-stoplight .titlebar-fullscreen svg.fullscreen-svg { 71 | width: 6px; 72 | height: 6px; 73 | margin-top: 2px; 74 | margin-left: 2px; 75 | opacity: 0; } 76 | .titlebar-stoplight .titlebar-fullscreen svg.maximize-svg { 77 | width: 8px; 78 | height: 8px; 79 | margin-top: 1px; 80 | margin-left: 1px; 81 | opacity: 0; 82 | display: none; } 83 | .titlebar-stoplight:hover svg, .titlebar-stoplight:hover svg.fullscreen-svg, .titlebar-stoplight:hover svg.maximize-svg { 84 | opacity: 1; } 85 | 86 | .titlebar:after, 87 | .titlebar-stoplight:after { 88 | content: ' '; 89 | display: table; 90 | clear: both; } 91 | -------------------------------------------------------------------------------- /src/css/Titlebar.css: -------------------------------------------------------------------------------- 1 | .titlebar { 2 | padding: 0 3px; 3 | display: flex; 4 | background-color: #f6f6f6; } 5 | .titlebar.transparent { 6 | background-color: transparent; } 7 | .titlebar.webkit-draggable { 8 | -webkit-app-region: drag; } 9 | .titlebar.alt svg.fullscreen-svg { 10 | display: none; } 11 | .titlebar.alt svg.maximize-svg { 12 | display: block !important; } 13 | .titlebar.webkit-draggable .titlebar-close, .titlebar.webkit-draggable .titlebar-minimize, .titlebar.webkit-draggable .titlebar-fullscreen { 14 | -webkit-app-region: no-drag; } 15 | 16 | .titlebar-text { 17 | flex-grow: 1; 18 | text-align: center; 19 | font-family: 'Helvetica Neue', Helvetica; 20 | font-size: 14px; 21 | line-height: 24px; 22 | -webkit-touch-callout: none; 23 | -webkit-user-select: none; 24 | user-select: none; 25 | cursor: default; } 26 | 27 | .titlebar-stoplight { 28 | flex-grow: 0; 29 | display: flex; } 30 | .titlebar-stoplight .titlebar-close, 31 | .titlebar-stoplight .titlebar-minimize, 32 | .titlebar-stoplight .titlebar-fullscreen { 33 | width: 10px; 34 | height: 10px; 35 | border-radius: 50%; 36 | margin: 6px 4px; 37 | line-height: 0; } 38 | .titlebar-stoplight .titlebar-close { 39 | border: 1px solid #e2463f; 40 | background-color: #ff5f57; 41 | margin-left: 6px; } 42 | .titlebar-stoplight .titlebar-close:active { 43 | border-color: #ad3934; 44 | background-color: #bf4943; } 45 | .titlebar-stoplight .titlebar-close svg { 46 | width: 6px; 47 | height: 6px; 48 | margin-top: 2px; 49 | margin-left: 2px; 50 | opacity: 0; } 51 | .titlebar-stoplight .titlebar-minimize { 52 | border: 1px solid #e1a116; 53 | background-color: #ffbd2e; } 54 | .titlebar-stoplight .titlebar-minimize:active { 55 | border-color: #ad7d15; 56 | background-color: #bf9123; } 57 | .titlebar-stoplight .titlebar-minimize svg { 58 | width: 8px; 59 | height: 8px; 60 | margin-top: 1px; 61 | margin-left: 1px; 62 | opacity: 0; } 63 | .titlebar-stoplight .titlebar-fullscreen, 64 | .titlebar-stoplight .titlebar-maximize { 65 | border: 1px solid #12ac28; 66 | background-color: #28c940; } 67 | .titlebar-stoplight .titlebar-fullscreen:active { 68 | border-color: #128622; 69 | background-color: #1f9a31; } 70 | .titlebar-stoplight .titlebar-fullscreen svg.fullscreen-svg { 71 | width: 6px; 72 | height: 6px; 73 | margin-top: 2px; 74 | margin-left: 2px; 75 | opacity: 0; } 76 | .titlebar-stoplight .titlebar-fullscreen svg.maximize-svg { 77 | width: 8px; 78 | height: 8px; 79 | margin-top: 1px; 80 | margin-left: 1px; 81 | opacity: 0; 82 | display: none; } 83 | .titlebar-stoplight:hover svg, .titlebar-stoplight:hover svg.fullscreen-svg, .titlebar-stoplight:hover svg.maximize-svg { 84 | opacity: 1; } 85 | 86 | .titlebar:after, 87 | .titlebar-stoplight:after { 88 | content: ' '; 89 | display: table; 90 | clear: both; } 91 | -------------------------------------------------------------------------------- /src/Titlebar.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable class-methods-use-this */ 2 | 3 | import React, { PropTypes } from 'react'; 4 | import classNames from 'classnames'; 5 | 6 | const KEY_ALT = 18; 7 | 8 | class Titlebar extends React.Component { 9 | static defaultProps = { 10 | draggable: false, 11 | title: null, 12 | transparent: false, 13 | padding: 3, 14 | } 15 | 16 | constructor(props, defaultProps) { 17 | super(props, defaultProps); 18 | 19 | this.state = { 20 | keyAltDown: false, 21 | }; 22 | 23 | this.handleMaximize = this.handleMaximize.bind(this); 24 | } 25 | 26 | componentDidMount() { 27 | this.setStyleHeader(); 28 | 29 | document.body.addEventListener('keydown', this.handleKeyDown.bind(this)); // eslint-disable-line no-undef 30 | document.body.addEventListener('keyup', this.handleKeyUp.bind(this)); // eslint-disable-line no-undef 31 | } 32 | 33 | componentWillUnMount() { 34 | document.body.removeEventListener('keydown', this.handleKeyDown); // eslint-disable-line no-undef 35 | document.body.removeEventListener('keyup', this.handleKeyUp); // eslint-disable-line no-undef 36 | } 37 | 38 | handleKeyDown(e) { 39 | if (e.keyCode === KEY_ALT) { 40 | this.setState({ 41 | keyAltDown: true, 42 | }); 43 | } 44 | } 45 | 46 | handleKeyUp(e) { 47 | if (e.keyCode === KEY_ALT) { 48 | this.setState({ 49 | keyAltDown: false, 50 | }); 51 | } 52 | } 53 | 54 | handleMaximize() { 55 | const { onMaximize, onFullscreen } = this.props; 56 | const { keyAltDown } = this.state; 57 | 58 | if (keyAltDown) { 59 | onMaximize(); 60 | } else { 61 | onFullscreen(); 62 | } 63 | } 64 | 65 | /** 66 | * Set style tag in header 67 | * in this way we can insert default css 68 | */ 69 | setStyleHeader() { 70 | if (!document.getElementsByTagName('head')[0].querySelector('style[id="react-titlebar-osx"]')) { // eslint-disable-line no-undef 71 | const tag = document.createElement('style'); // eslint-disable-line no-undef 72 | tag.id = 'react-titlebar-osx'; 73 | tag.innerHTML = this.styles(); 74 | document.getElementsByTagName('head')[0].appendChild(tag); // eslint-disable-line no-undef 75 | } 76 | } 77 | 78 | render() { 79 | const { 80 | draggable, 81 | title, 82 | transparent, 83 | padding, 84 | onClose, 85 | onMinimize, 86 | } = this.props; 87 | const { keyAltDown } = this.state; 88 | 89 | const clazz = classNames({ 90 | titlebar: true, 91 | 'webkit-draggable': draggable, 92 | transparent, 93 | alt: keyAltDown, 94 | }); 95 | 96 | const ztyle = { 97 | paddingTop: padding, 98 | paddingBottom: padding, 99 | }; 100 | 101 | return ( 102 |
103 |
104 |
105 | 106 | 107 | 108 |
109 |
110 | 111 | 112 | 113 |
114 |
115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
123 |
124 | { 125 | title && 126 |
{title}
127 | } 128 |
129 | ); 130 | } 131 | 132 | // TODO ugly way to fix the styles 133 | styles() { 134 | return `.titlebar { 135 | padding: 0 3px; 136 | display: flex; 137 | background-color: #f6f6f6; } 138 | .titlebar.transparent { 139 | background-color: transparent; } 140 | .titlebar.webkit-draggable { 141 | -webkit-app-region: drag; } 142 | .titlebar.alt svg.fullscreen-svg { 143 | display: none; } 144 | .titlebar.alt svg.maximize-svg { 145 | display: block !important; } 146 | .titlebar.webkit-draggable .titlebar-close, .titlebar.webkit-draggable .titlebar-minimize, .titlebar.webkit-draggable .titlebar-fullscreen { 147 | -webkit-app-region: no-drag; } 148 | 149 | .titlebar-text { 150 | flex-grow: 1; 151 | text-align: center; 152 | font-family: 'Helvetica Neue', Helvetica; 153 | font-size: 14px; 154 | line-height: 24px; 155 | -webkit-touch-callout: none; 156 | -webkit-user-select: none; 157 | user-select: none; 158 | cursor: default; } 159 | 160 | .titlebar-stoplight { 161 | flex-grow: 0; 162 | display: flex; } 163 | .titlebar-stoplight .titlebar-close, 164 | .titlebar-stoplight .titlebar-minimize, 165 | .titlebar-stoplight .titlebar-fullscreen { 166 | width: 10px; 167 | height: 10px; 168 | border-radius: 50%; 169 | margin: 6px 4px; 170 | line-height: 0; } 171 | .titlebar-stoplight .titlebar-close { 172 | border: 1px solid #e2463f; 173 | background-color: #ff5f57; 174 | margin-left: 6px; } 175 | .titlebar-stoplight .titlebar-close:active { 176 | border-color: #ad3934; 177 | background-color: #bf4943; } 178 | .titlebar-stoplight .titlebar-close svg { 179 | width: 6px; 180 | height: 6px; 181 | margin-top: 2px; 182 | margin-left: 2px; 183 | opacity: 0; } 184 | .titlebar-stoplight .titlebar-minimize { 185 | border: 1px solid #e1a116; 186 | background-color: #ffbd2e; } 187 | .titlebar-stoplight .titlebar-minimize:active { 188 | border-color: #ad7d15; 189 | background-color: #bf9123; } 190 | .titlebar-stoplight .titlebar-minimize svg { 191 | width: 8px; 192 | height: 8px; 193 | margin-top: 1px; 194 | margin-left: 1px; 195 | opacity: 0; } 196 | .titlebar-stoplight .titlebar-fullscreen, 197 | .titlebar-stoplight .titlebar-maximize { 198 | border: 1px solid #12ac28; 199 | background-color: #28c940; } 200 | .titlebar-stoplight .titlebar-fullscreen:active { 201 | border-color: #128622; 202 | background-color: #1f9a31; } 203 | .titlebar-stoplight .titlebar-fullscreen svg.fullscreen-svg { 204 | width: 6px; 205 | height: 6px; 206 | margin-top: 2px; 207 | margin-left: 2px; 208 | opacity: 0; } 209 | .titlebar-stoplight .titlebar-fullscreen svg.maximize-svg { 210 | width: 8px; 211 | height: 8px; 212 | margin-top: 1px; 213 | margin-left: 1px; 214 | opacity: 0; 215 | display: none; } 216 | .titlebar-stoplight:hover svg, .titlebar-stoplight:hover svg.fullscreen-svg, .titlebar-stoplight:hover svg.maximize-svg { 217 | opacity: 1; } 218 | 219 | .titlebar:after, 220 | .titlebar-stoplight:after { 221 | content: ' '; 222 | display: table; 223 | clear: both; } 224 | `; 225 | } 226 | } 227 | 228 | Titlebar.propTypes = { 229 | padding: PropTypes.number, 230 | title: PropTypes.string, 231 | transparent: PropTypes.bool, 232 | draggable: PropTypes.bool, 233 | onClose: PropTypes.func.isRequired, 234 | onMinimize: PropTypes.func.isRequired, 235 | onMaximize: PropTypes.func.isRequired, 236 | onFullscreen: PropTypes.func.isRequired, 237 | }; 238 | 239 | export default Titlebar; 240 | -------------------------------------------------------------------------------- /umd/ReactTitlebar.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports.ReactTitlebar=t(require("react")):e.ReactTitlebar=t(e.React)}(this,function(e){return function(e){function t(i){if(n[i])return n[i].exports;var r=n[i]={exports:{},id:i,loaded:!1};return e[i].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}var r=n(1),l=i(r);e.exports={Titlebar:l.default}},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function l(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!==("undefined"==typeof t?"undefined":o(t))&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+("undefined"==typeof t?"undefined":o(t)));e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};Object.defineProperty(t,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0;n