├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmrc ├── .prettierrc ├── .travis.yml ├── README.md ├── example ├── .gitignore ├── README.md ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── App.js │ ├── App.test.js │ ├── index.css │ └── index.js └── yarn.lock ├── package.json ├── src ├── .eslintrc ├── index.js ├── index.test.js ├── styles.css └── theme.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ 4 | .snapshots/ 5 | *.min.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "standard", 5 | "standard-react", 6 | "plugin:prettier/recommended", 7 | "prettier/standard", 8 | "prettier/react" 9 | ], 10 | "env": { 11 | "node": true 12 | }, 13 | "parserOptions": { 14 | "ecmaVersion": 2020, 15 | "ecmaFeatures": { 16 | "legacyDecorators": true, 17 | "jsx": true 18 | } 19 | }, 20 | "settings": { 21 | "react": { 22 | "version": "16" 23 | } 24 | }, 25 | "rules": { 26 | "space-before-function-paren": 0, 27 | "react/prop-types": 0, 28 | "react/jsx-handler-names": 0, 29 | "react/jsx-fragments": 0, 30 | "react/no-unused-prop-types": 0, 31 | "import/export": 0 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | .rpt2_cache 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | 24 | .vercel 25 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | @{{OWNER}}:registry=https://npm.pkg.github.com 2 | //npm.pkg.github.com/:_authToken=7a658a630f1448b2c4f3594bc017761b1e723c42 -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "semi": false, 5 | "tabWidth": 2, 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": false, 8 | "arrowParens": "always", 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | - 10 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-browser-tabs 🖥 2 | 3 | > Browser like tabs for you custom browser, you can use this in Electron App. 🎉 4 | 5 | ![react-browser-tab](https://i.imgur.com/ZiK6XbQ.png) 6 | [DEMO](https://react-browser-tabs.vercel.app/) 7 | 8 | ### when to use this ? 9 | 10 | If one day you wake up 🌄 and decided to create your own browser 💻 with some javascript frameworks 🦾, this library will come in handy 🤟. 11 | 12 | [![NPM](https://img.shields.io/npm/v/browser-tabs.svg)](https://www.npmjs.com/package/browser-tabs) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 13 | 14 | ## Install 15 | 16 | ```bash 17 | npm install --save react-browser-tabs 18 | ``` 19 | 20 | ## Usage 21 | 22 | ```jsx 23 | import React, { useState } from 'react' 24 | 25 | import MyComponent from 'react-browser-tabs' 26 | 27 | const Example = () => { 28 | const defaultTabs = [ 29 | { 30 | title: 'getting started', 31 | url: 'https://google.com/', // auto fetch url 32 | id: 'tab1', 33 | content: (props) => Hello World 34 | } 35 | ] 36 | const tabs = useState(defaultTabs) 37 | const activeTab = useState(0) 38 | 39 | return ( 40 | 47 | ) 48 | } 49 | ``` 50 | 51 | ## Add Tabs 52 | 53 | ```jsx 54 | const addTab = () => { 55 | activeTab[1](tabs[0].length) 56 | tabs[1]([ 57 | ...tabs[0], 58 | { 59 | title: 'New Tab ', 60 | url: 'https://rajaosama.me/', 61 | id: 'tab1', 62 | content: (props) => ( 63 | 64 | New Tab Opened 65 | 66 | ) 67 | } 68 | ]) 69 | } 70 | ``` 71 | 72 | it automatically get the favicon for your desire app, just pass the url as it is. 73 | 74 | ## License 75 | 76 | MIT © [rajaosama](https://github.com/rajaosama) 77 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This example was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | It is linked to the browser-tabs package in the parent directory for development purposes. 4 | 5 | You can run `yarn install` and then `yarn start` to test your package. 6 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser-tabs-example", 3 | "homepage": ".", 4 | "version": "0.0.0", 5 | "private": true, 6 | "scripts": { 7 | "start": "node ../node_modules/react-scripts/bin/react-scripts.js start", 8 | "build": "node ../node_modules/react-scripts/bin/react-scripts.js build", 9 | "test": "node ../node_modules/react-scripts/bin/react-scripts.js test", 10 | "eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject" 11 | }, 12 | "dependencies": { 13 | "react": "link:../node_modules/react", 14 | "react-dom": "link:../node_modules/react-dom", 15 | "react-scripts": "link:../node_modules/react-scripts", 16 | "browser-tabs": "link:.." 17 | }, 18 | "devDependencies": { 19 | "@babel/plugin-syntax-object-rest-spread": "^7.8.3" 20 | }, 21 | "eslintConfig": { 22 | "extends": "react-app" 23 | }, 24 | "browserslist": [ 25 | ">0.2%", 26 | "not dead", 27 | "not ie <= 11", 28 | "not op_mini all" 29 | ] 30 | } -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Raja0sama/browser-tabs/0ee1477a506eadb4c2d2f174a508e69458ba2316/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 16 | 17 | 18 | 27 | browser-tabs 28 | 29 | 30 | 31 | 34 | 35 |
36 | 37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "browser-tabs", 3 | "name": "browser-tabs", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /example/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from 'react' 2 | 3 | import { BrowserTabs } from 'browser-tabs' 4 | import 'browser-tabs/dist/index.css' 5 | import { Dark, Light } from 'browser-tabs/src/theme' 6 | 7 | const App = () => { 8 | const [isDark, setisDark] = useState(false) 9 | const toggle = useCallback(() => { 10 | setisDark(!isDark) 11 | }, [isDark]) 12 | 13 | const defaultTabs = [ 14 | { 15 | title: 'Welcome to React Browser', 16 | url: 'https://google.com/', 17 | id: 'one', 18 | content: (props) => { 19 | return ( 20 |
29 | 37 | RO 38 | 39 | 45 | Made with 💖 46 | 47 | 53 | This Project was created for Electron and React.js, a well 54 | designed Browser tabs component build with REACT + Love.{' '} 55 | 56 |
57 | 58 | 69 | 70 |
71 | 72 | 83 | 84 |
85 |
86 | ) 87 | } 88 | }, 89 | { 90 | title: 'Looking for Contributors', 91 | url: 'https://github.com/', 92 | id: 'one', 93 | content: (props) => ( 94 |
102 | 108 | Looking for contributor to work on this project and make it better. 109 | 110 |
111 | ) 112 | } 113 | ] 114 | const tabs = useState(defaultTabs) 115 | const activeTab = useState(0) 116 | 117 | const addTab = () => { 118 | activeTab[1](tabs[0].length) 119 | tabs[1]([ 120 | ...tabs[0], 121 | { 122 | title: 'New Tab ', 123 | url: 'https://rajaosama.me/', 124 | id: 'tab1', 125 | content: (props) => ( 126 | 127 | New Tab Opened 128 | 129 | ) 130 | } 131 | ]) 132 | } 133 | 134 | return ( 135 |
136 |
145 | 153 |
160 | 166 |
167 |
168 |
169 | ) 170 | } 171 | 172 | export default App 173 | -------------------------------------------------------------------------------- /example/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div') 7 | ReactDOM.render(, div) 8 | ReactDOM.unmountComponentAtNode(div) 9 | }) 10 | -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,900&display=swap'); 2 | 3 | body { 4 | margin: 0; 5 | padding: 0; 6 | font-family: 'Poppins', sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /example/src/index.js: -------------------------------------------------------------------------------- 1 | import './index.css' 2 | 3 | import React from 'react' 4 | import ReactDOM from 'react-dom' 5 | import App from './App' 6 | 7 | ReactDOM.render(, document.getElementById('root')) 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-browser-tabs", 3 | "version": "1.0.13", 4 | "description": "Browser Like Tabs for your custom Browser.", 5 | "author": "rajaosama", 6 | "license": "MIT", 7 | "repository": "Raja0sama/browser-tabs", 8 | "main": "dist/index.js", 9 | "module": "dist/index.modern.js", 10 | "source": "src/index.js", 11 | "engines": { 12 | "node": ">=10" 13 | }, 14 | "scripts": { 15 | "build": "microbundle-crl --no-compress --format modern,cjs", 16 | "start": "microbundle-crl watch --no-compress --format modern,cjs", 17 | "prepare": "run-s build", 18 | "test": "run-s test:unit test:lint test:build", 19 | "test:build": "run-s build", 20 | "test:lint": "eslint .", 21 | "test:unit": "cross-env CI=1 react-scripts test --env=jsdom", 22 | "test:watch": "react-scripts test --env=jsdom", 23 | "predeploy": "cd example && yarn install && yarn run build", 24 | "deploy": "gh-pages -d example/build" 25 | }, 26 | "peerDependencies": { 27 | "react": "^16.0.0" 28 | }, 29 | "devDependencies": { 30 | "microbundle-crl": "^0.13.10", 31 | "babel-eslint": "^10.0.3", 32 | "cross-env": "^7.0.2", 33 | "eslint": "^6.8.0", 34 | "eslint-config-prettier": "^6.7.0", 35 | "eslint-config-standard": "^14.1.0", 36 | "eslint-config-standard-react": "^9.2.0", 37 | "eslint-plugin-import": "^2.18.2", 38 | "eslint-plugin-node": "^11.0.0", 39 | "eslint-plugin-prettier": "^3.1.1", 40 | "eslint-plugin-promise": "^4.2.1", 41 | "eslint-plugin-react": "^7.17.0", 42 | "eslint-plugin-standard": "^4.0.1", 43 | "gh-pages": "^2.2.0", 44 | "npm-run-all": "^4.1.5", 45 | "prettier": "^2.0.4", 46 | "react": "^16.13.1", 47 | "react-dom": "^16.13.1", 48 | "react-scripts": "^3.4.1" 49 | }, 50 | "files": [ 51 | "dist" 52 | ] 53 | } -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import './styles.css' 3 | 4 | export const BrowserTabs = ({ 5 | tabs, 6 | activeTab, 7 | style = {}, 8 | theme = Light, 9 | onAddTabPress, 10 | injectProps 11 | }) => { 12 | const [currTabs, setNewTabs] = tabs 13 | const { 14 | tabStyle = {}, 15 | tabsStyle = {}, 16 | contentStyle = {}, 17 | activeTabsStyle = {}, 18 | activeTabBoxStyle = {}, 19 | tabBoxStyle = {}, 20 | labelStyle = {} 21 | } = style 22 | const [activeTabIndex, setActiveTabIndex] = activeTab 23 | const styles = themedStyle(theme) 24 | console.log({ injectProps }) 25 | return ( 26 |
36 | 41 |
42 | {currTabs && Array.isArray(currTabs) 43 | ? tabsLoop( 44 | currTabs, 45 | activeTabIndex, 46 | tabStyle, 47 | activeTabsStyle, 48 | tabBoxStyle, 49 | activeTabBoxStyle, 50 | setActiveTabIndex, 51 | setNewTabs, 52 | onAddTabPress, 53 | theme 54 | ) 55 | : console.log('Some thing wrong with currtabs ', { currTabs })} 56 |
57 | {currTabs.map((tabs, index) => ( 58 |
65 | {} 66 |
67 | ))} 68 |
69 | ) 70 | } 71 | 72 | const PlusSvg = (props) => ( 73 | 85 | 86 | 87 | 93 | 94 | 95 | 96 | ) 97 | 98 | const themedStyle = (theme) => ({ 99 | tabs: { 100 | height: '34px', 101 | display: 'flex', 102 | padding: '0 0 0 0', 103 | overflow: 'hidden', 104 | overflowX: 'auto', 105 | backgroundColor: theme.topBarColor, 106 | paddingLeft: 4, 107 | paddingTop: 4 108 | }, 109 | tab: { 110 | display: 'flex', 111 | flex: '1', 112 | minWidth: '110px', 113 | maxWidth: '280px', 114 | height: '45px', 115 | overflow: 'hidden' 116 | }, 117 | activeTab: { zIndex: '40', position: 'relative', paddingBottom: '1px' }, 118 | tabBox: { 119 | height: '50px', 120 | flex: '1', 121 | minWidth: '100px', 122 | borderRadius: '4px', 123 | display: 'flex', 124 | backgroundColor: theme.tabColor 125 | 126 | // boxShadow: '0 0 2px #fff inset' 127 | }, 128 | activeTabBox: { 129 | // border: '1px solid #ccc', 130 | flex: 1, 131 | 132 | display: 'flex', 133 | marginBottom: -4, 134 | backgroundColor: theme.activeTabColor, 135 | // borderTopLeftRadius: '5px', 136 | // borderTopRightRadius: '5px', 137 | borderRadius: 5 138 | // boxShadow: '0 0 2px 0 #fff inset' 139 | }, 140 | content: { 141 | zIndex: '1', 142 | background: theme.contentColor, 143 | position: 'relative', 144 | height: '100%' 145 | // padding: 20 146 | }, 147 | border: { 148 | margin: '8px 0', 149 | height: 19, 150 | borderRight: '1px solid rgb(0 0 0 / 6%)' 151 | }, 152 | title: { 153 | color: theme.labelColor, 154 | fontSize: 12, 155 | flex: 1 156 | }, 157 | cross: { 158 | flexGrow: '0', 159 | flexShrink: '0', 160 | position: 'relative', 161 | width: '16px', 162 | height: '16px', 163 | borderRadius: '50%', 164 | backgroundPosition: 'center center', 165 | backgroundRepeat: 'no-repeat', 166 | backgroundSize: '8px 8px' 167 | } 168 | }) 169 | 170 | function tabsLoop( 171 | currTabs, 172 | activeTabIndex, 173 | tabStyle, 174 | activeTabsStyle, 175 | tabBoxStyle, 176 | activeTabBoxStyle, 177 | setActiveTabIndex, 178 | setNewTabs, 179 | onAddTabPress, 180 | theme 181 | ) { 182 | return currTabs.map((tabs, index) => { 183 | const styles = themedStyle(theme) 184 | const isLast = index == currTabs.length - 1 185 | const lastStyle = isLast && { maxWidth: 280 + 45 } 186 | const active = 187 | activeTabIndex == index 188 | ? { 189 | ...styles.tab, 190 | ...styles.activeTab, 191 | ...lastStyle, 192 | ...tabStyle, 193 | ...activeTabsStyle 194 | } 195 | : { ...styles.tab, ...lastStyle, ...tabStyle } 196 | const activeTabBox = 197 | activeTabIndex == index 198 | ? { 199 | ...styles.tabbox, 200 | ...styles.activeTabBox, 201 | ...tabBoxStyle, 202 | ...activeTabBoxStyle 203 | } 204 | : { ...styles.tabBox, ...tabBoxStyle } 205 | 206 | return ( 207 |
208 | 218 | {activeTabIndex - 1 != index && activeTabIndex != index && !isLast && ( 219 |
220 | )} 221 | {isLast && ( 222 | 230 | )} 231 |
232 | ) 233 | }) 234 | } 235 | 236 | function Cross({ active, theme }) { 237 | const styles = themedStyle(theme) 238 | const [hover, sethover] = useState(false) 239 | 240 | return ( 241 | 242 | 243 | 250 | 251 | 252 | // // 253 | // // 262 | // // 263 | // 268 | // 272 | // 273 | ) 274 | } 275 | 276 | function AddButton({ 277 | setActiveTabIndex, 278 | setNewTabs, 279 | currTabs, 280 | index, 281 | theme, 282 | onAddTabPress 283 | }) { 284 | const [hover, sethover] = useState(false) 285 | const styles = themedStyle(theme) 286 | return ( 287 |
sethover(true)} 289 | onMouseLeave={() => sethover(false)} 290 | onClick={onAddTabPress} 291 | style={{ 292 | width: 45, 293 | height: 'calc(100% - 8px)', 294 | background: hover ? theme.addButtonHoverColor : theme.addButtonColor 295 | }} 296 | > 297 | 303 |
304 | ) 305 | } 306 | 307 | function Tab({ 308 | activeTabBox, 309 | setActiveTabIndex, 310 | index, 311 | tabs, 312 | theme, 313 | activeTabIndex, 314 | setNewTabs, 315 | currTabs 316 | }) { 317 | const styles = themedStyle(theme) 318 | const [hover, sethover] = useState(false) 319 | // useEffect(() => { 320 | // console.log({ hover }) 321 | // }, [hover]) 322 | 323 | const hovered = () => 324 | activeTabIndex != index && 325 | hover && { 326 | backgroundColor: theme.tabHoverColor, 327 | borderRadius: 4 328 | } 329 | return ( 330 |
sethover(true)} 332 | onMouseLeave={() => sethover(false)} 333 | style={{ ...activeTabBox, borderRadius: 5, ...hovered() }} 334 | > 335 |
344 |
setActiveTabIndex(index)} 346 | style={{ 347 | cursor: 'pointer', 348 | display: 'flex', 349 | flex: 1, 350 | height: '100%', 351 | alignItems: 'center' 352 | }} 353 | > 354 | 361 | 370 | {tabs.title} 371 | 372 |
373 | 379 |
{ 381 | if (currTabs.length == 1) return 382 | const tabs = currTabs.filter((e, i) => i != index) 383 | setActiveTabIndex( 384 | activeTabIndex == 0 ? activeTabIndex + 1 : activeTabIndex - 1 385 | ) 386 | setNewTabs(tabs) 387 | }} 388 | style={{ 389 | width: 10, 390 | display: 'flex', 391 | height: 10, 392 | marginRight: 10, 393 | alignSelf: 'center' 394 | }} 395 | > 396 | 397 |
398 |
399 |
400 | ) 401 | } 402 | export const Dark = { 403 | contentColor: '#252729', 404 | activeTabColor: '#323639', 405 | topBarColor: '#202124', 406 | tabColor: 'transparent', 407 | tabHoverColor: '#292b2e', 408 | cancelButtonColor: '#71757a', 409 | cancelButtonHoverColor: 'red', 410 | cancelActiveButtonColor: '#858b8f', 411 | cancelActiveButtonHoverColor: 'red', 412 | labelColor: '#9ca1a7', 413 | addButtonColor: 'transparent', 414 | addButtonHoverColor: '#292b2e', 415 | addButton: '#71757a' 416 | } 417 | export const Light = { 418 | contentColor: 'white', 419 | activeTabColor: 'white', 420 | topBarColor: '#dee1e6', 421 | tabColor: 'transparent', 422 | tabHoverColor: '#f4f5f6', 423 | cancelButtonColor: 'black', 424 | cancelButtonHoverColor: 'red', 425 | cancelActiveButtonColor: 'black', 426 | cancelActiveButtonHoverColor: 'red', 427 | labelColor: '#45474a', 428 | addButtonColor: 'transparent', 429 | addButtonHoverColor: '#ffffff26', 430 | addButton: 'black' 431 | } 432 | -------------------------------------------------------------------------------- /src/index.test.js: -------------------------------------------------------------------------------- 1 | import { BrowserTabs } from '.' 2 | 3 | describe('BrowserTabs', () => { 4 | it('is truthy', () => { 5 | expect(BrowserTabs).toBeTruthy() 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Raja0sama/browser-tabs/0ee1477a506eadb4c2d2f174a508e69458ba2316/src/styles.css -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Raja0sama/browser-tabs/0ee1477a506eadb4c2d2f174a508e69458ba2316/src/theme.js --------------------------------------------------------------------------------