├── .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 | 
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 | [](https://www.npmjs.com/package/browser-tabs) [](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 |
32 | You need to enable JavaScript to run this app.
33 |
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 |
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 |
164 | {isDark ? 'Light theme' : 'Dark Theme'}
165 |
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
--------------------------------------------------------------------------------