Poorly coded titlebar for Electron, NW.js and PWAs.
Light/Dark skin
Control button styles
It's a library for Electron, NW.js and PWAs, it can be used on a basic website but it's useless ยฏ\(ใ)/ยฏ
- Compatible with any version of Electron ๐
- Works with Electron, NW.js and probably others ๐คทโโ๏ธ
- Fully compatible with Progressive Web Apps and Window Controls Overlay ๐ฅ
- Works without any dependencies, so it won't break in the next major release of Electron ๐
- Very small footprint (< 30 kB) ๐ฃ
- Options and methods very similar to custom-electron-titlebar ๐
You can see a demo of this library in a PWA here: https://6c65726f79.github.io/custom-titlebar/
Notes:
- Check the list of compatible browsers.
- If the install button doesn't appear, try reloading and reopening the tab several times.
- You may need to manually enable Window Controls Overlay until Chrome 98 is released:
chrome://flags/#enable-desktop-pwas-window-controls-overlay
This package is highly inspired by custom-electron-titlebar.
I needed a custom titlebar for Electron 14 to replace the unmaintained custom-electron-titlebar, but I couldn't find any interesting ones, so I made it myself.
MenuItem role, icon, radioDone!Icons themeDone!- Submenu scrollbar
- Keyboard controls
Absolutely! Check out the advanced examples to see how it's done.
Simply add style-src 'unsafe-inline'
in the Content-Security-Policy
meta tag.
This package doesn't rely on the Electron or NW.js API so it doesn't need as much maintenance as other packages. Even if I stop maintaining it and the API change dramatically, you could modify your code to match the new API. So theoretically this package can't become obsolete.
npm i @6c65726f79/custom-titlebar
const Titlebar = require('@6c65726f79/custom-titlebar');
new Titlebar({
backgroundColor: '#000'
});
import Titlebar from '@6c65726f79/custom-titlebar';
new Titlebar({
backgroundColor: '#000'
});
<script src="https://cdn.jsdelivr.net/npm/@6c65726f79/custom-titlebar/lib/index.js"></script>
<script>
new Titlebar({
backgroundColor: '#000'
});
</script>
See the Wiki for more advanced examples.
const { initialize, enable } = require('@electron/remote/main');
const { app, BrowserWindow } = require('electron');
const path = require('path');
initialize();
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
frame: false,
webPreferences: {
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
})
enable(win.webContents);
win.loadFile('index.html');
}
app.whenReady().then(() => {
createWindow();
})
const { Menu, getCurrentWindow } = require('@electron/remote');
const Titlebar = require('@6c65726f79/custom-titlebar');
const { platform } = require('process');
const currentWindow = getCurrentWindow();
let titlebar;
currentWindow.webContents.once('dom-ready', () => {
titlebar = new Titlebar({
menu: Menu.getApplicationMenu(),
backgroundColor: '#37474f',
platform: platform,
browserWindow: currentWindow, /* Only needed if you use MenuItem roles */
onMinimize: () => currentWindow.minimize(),
onMaximize: () => currentWindow.isMaximized() ? currentWindow.unmaximize() : currentWindow.maximize(),
onClose: () => currentWindow.close(),
isMaximized: () => currentWindow.isMaximized()
});
});
{
"name": "helloworld",
"main": "index.html",
"window": {
"frame": false,
"toolbar": false
},
"dependencies": {
"@6c65726f79/custom-titlebar": "latest"
}
}
<!DOCTYPE html>
<html>
<head>
<title>Hello World!</title>
<script src="./node_modules/@6c65726f79/custom-titlebar/lib/index.js"></script>
</head>
<body>
<h1>Hello World!</h1>
<script>
const gui = require('nw.gui');
const { platform } = require('process');
const win = gui.Window.get();
let maximized = false;
win.onMaximized.addListener(() => { maximized=true; });
win.onRestore.addListener(() => { maximized=false; });
const titlebar = new Titlebar({
backgroundColor: '#37474f',
platform: platform,
onMinimize: () => win.minimize(),
onMaximize: () => maximized ? win.restore() : win.maximize(),
onClose: () => win.close(),
isMaximized: () => maximized
});
</script>
</body>
</html>
{
"background_color": "#2975ff",
"description": "Progressive Web Application with a custom titlebar",
"display": "standalone",
"display_override": ["window-controls-overlay"],
"icons": [],
"name": "Progressive Web Application",
"short_name": "custom-titlebar",
"start_url": "./",
"theme_color": "#2975ff"
}
<html>
<head>
<title>Titlebar</title>
<script src="https://cdn.jsdelivr.net/npm/@6c65726f79/custom-titlebar/lib/index.js"></script>
<link rel="manifest" href="manifest.webmanifest">
</head>
<body>
<div id="app" style="padding:8px;">
This is a web page
</div>
<script>
let titlebar;
const menu = [
{
label: 'File',
submenu: [
{
label: 'Checkbox',
type: 'checkbox',
id: 'checkbox'
},
{
label: 'Checked state',
click: () => {
alert(titlebar.getMenuItemById('checkbox')?.checked ? 'Checked' : 'Unchecked');
}
}
]
}
];
function createTitlebar() {
titlebar = new Titlebar({
backgroundUnfocusEffect: false,
windowControlsOverlay: true,
backgroundColor:"#2975ff",
menu
});
}
window.addEventListener('load', () => {
// Use the titlebar only in standalone mode
if (navigator.standalone || window.matchMedia('(display-mode: standalone)').matches) {
createTitlebar();
}
window.matchMedia('(display-mode: standalone)').onchange = (e) => {
e.matches ? createTitlebar() : titlebar.dispose();
}
});
</script>
</body>
</html>
All parameters are optional.
Parameter | Type | Description | Default |
---|---|---|---|
backgroundColor | string |
The background color of the titlebar. The value must be a valid CSS color. | "#FFFFFF" |
backgroundUnfocusEffect | boolean |
Enables or disables the unfocus effect on the background of the titlebar. | true |
browserWindow | object |
The current BrowserWindow . (Electron only) |
undefined |
condensed | boolean |
Force the menu bar to be condensed. | false |
closeable | boolean |
Enables or disables the close button. | true |
drag | boolean |
Define whether or not you can drag the window. | true |
hideControlsOnDarwin | boolean |
Set this option to true if you're using titleBarStyle: 'hidden' on macOS. (Electron only) |
false |
hideMenuOnDarwin | boolean |
Hide the menu bar when the platform is darwin . |
true |
height | number |
The height of the titlebar. | 30 |
icon | string |
The icon of the titlebar. | undefined |
isMaximized | function |
A function that return true or false if the window is maximized or not. |
undefined |
maximizable | boolean |
Enables or disables the maximize button. | true |
menu | object |
List of MenuItem to show in the menu bar. (Electron or NW.js) | undefined |
menuItemClickHandler | function |
A function that takes a commandId as parameter to handle menu item clicks. |
undefined |
minimizable | boolean |
Enables or disables the minimize button. | true |
onClose | function |
The function to call when the close button is clicked. | undefined |
onMaximize | function |
The function to call when the maximize/restore button is clicked. | undefined |
onMinimize | function |
The function to call when the minimize button is clicked. | undefined |
overflow | string |
The overflow of the container. (auto , visible , hidden ) |
"auto" |
platform | string |
Style of the control buttons. (win , darwin ) |
"win" |
title | string |
Window title. | document.title |
titleHorizontalAlignment | string |
Set horizontal alignment of the window title. (left , center , right ) |
"center" |
unfocusEffect | boolean |
Enables or disables the unfocus effect on the text and background of the titlebar. | true |
windowControlsOverlay | boolean |
Set this option to true if you're using Window Controls Overlay. | false |
Returns the MenuItem with the specified id
.
const titlebar = new Titlebar({
menu: [{label: 'Item 1', id: 'item1'}]
});
const menuItem = titlebar.getMenuItemById('item1');
console.log(menuItem.label); // Item 1
This method updates all parameters that are specified.
titlebar.updateOptions({
menu: Menu.getApplicationMenu(),
condensed: 'true',
titleHorizontalAlignment: 'left'
});
This method update the title of the titlebar. If you change the content of the title tag, you should call this method to update the title.
document.title = 'My new title';
titlebar.updateTitle();
// Or you can do as follows and avoid writing document.title
titlebar.updateTitle('New Title');
Deprecated: This method will be removed in v1.0.0, use
updateOptions
instead.
This method updates or creates the menu. You can use an array of MenuItem from Electron/NW.js, or directly Menu.getApplicationMenu()
in Electron.
// With a menu template
const menu = [
{
label: 'Item 1',
submenu: [
{
label: 'Subitem 1',
click: () => console.log('Clicked on subitem 1')
},
{
type: 'separator'
},
{
label: 'Subitem 2',
click: () => console.log('Clicked on subitem 2')
},
]
},
{
label: 'Item 2',
submenu: [
{
label: 'Subitem checkbox',
type: 'checkbox',
checked: true
},
{
type: 'separator'
},
{
label: 'Subitem with submenu',
submenu: [
{
label: 'Submenu item 1',
accelerator: 'Ctrl+T'
}
]
}
]
}
];
titlebar.updateMenu(menu);
// Or with getApplicationMenu in Electron
titlebar.updateMenu(Menu.getApplicationMenu());
// Disable menu
titlebar.updateMenu();
This method removes the titlebar completely and all recorded events.
titlebar.dispose();
The following CSS classes exist and can be used to customize the titlebar.
Class name | Description |
---|---|
custom-titlebar |
Style of the titlebar. |
custom-titlebar-appicon |
Style of the icon. |
custom-titlebar-container |
Style of the container under the titlebar. |
custom-titlebar-controls |
Style of the window controls. |
custom-titlebar-menu-item |
Style of the main menu items. |
custom-titlebar-separator |
Style of the separators. |
custom-titlebar-submenu |
Style of the submenus. |
custom-titlebar-submenu-item |
Style of the submenu items. |
Class name | Description |
---|---|
backdrop |
backdrop ์คํ์ผ |
custom-titlebar-menu-active |
ํ์ฌ ํ์ฑํ ์ํ์ ๋ฉ๋ด ์์ดํ . |
custom-titlebar-group |
label์ด ์ค์ ๋ ๊ทธ๋ฃน(separator ๋ผ์ธ) ์์ดํ . |
custom-titlebar-group-label |
๊ทธ๋ฃน ์์ดํ ์ label ์คํ์ผ. |
custom-titlebar-separator-line |
separators ๋ผ์ธ ์คํ์ผ. |
custom-titlebar-disabled-item |
๋นํ์ฑ ํญ๋ชฉ ์คํ์ผ. |
Made with love and fun from France โค
DOM์ ์ง์ ํ์ฌ ์ฌ์ฉํ๋ฉด container
๋ฅผ ์์ฑํ์ง ์๊ณ HTML ๊ตฌ์กฐ๋ฅผ ๊ทธ๋๋ก ์ ์ง ํ ์ ์์(DOM ์ด๋ ์์)
// HTML: <div id='custom-titlebar'></div>
const dom = document.querySelector('#custom-titlebar');
const titlebar = new Titlebar({}, dom);
// dom ๋ด๋ถ์ ๋ฉ๋ด๊ฐ ์์ฑ๋จ
separator ๋ผ์ธ๊ณผ ํจ๊นจ label์ ํ์ํ ์ ์์ (๋ฉ๋ด ๊ทธ๋ฃน ํ์)
...
submenu: [
...
{
type: 'separator',
label: 'Group title',
},
...
]
์ด๋ฒคํธ๋ ์ต์์ ๋ฉ๋ด๊ฐ ์ด๋ฆฌ๊ณ ๋ซํ๋ ํ๋ฒ์ฉ๋ง ๋ฐ์ํจ.
์๋ธ๋ฉ๋ด๊ฐ ์ด๋ฆฌ๊ณ ๋ซํ๋ ๊ฒ๊ณผ ๋ฌด๊ดํจ.
var titlebarDOM = document.querySelector('.custom-titlebar');
titlebarDOM.addEventListener('openMenu', onOpenMenu);
titlebarDOM.addEventListener('closeMenu', onCloseMenu);
function onOpenMenu (e){
window.console.error('onOpenMenu : ', e.detail);
}
function onCloseMenu(e){
window.console.error('onCloseMenu : ', e.detail);
}
๋ฉ๋ด๊ฐ ์ด๋ ธ์๋ ๋ฉ๋ด ๋ค์ชฝ์ ์์นํ DOM์ ๋ง์ฐ์ค ๊ธฐ๋ฅ์ ๋ง๋๋ก backdrop์ ์์ฑํจ
const titlebar = new Titlebar({backdrop: true});
// CSS
.backdrop:before{
content: '';
background-color: #33c74824;
width: 100%;
height: 100%;
position: fixed;
left: 0;
top: 0;
}
ํ๋ก๊ทธ๋จ์ผ๋ก ๋ฉ๋ด๋ฅผ ์ด๊ณ ๋ซ์์ ์๋๋ก ๋ฉ์๋ ์ถ๊ฐํจ. ์๋ธ ๋ฉ๋ด๋ ์ง์ํ์ง ์์
// ์๋์ผ๋ก ์ด๊ธฐ/๋ซ๊ธฐ (default index:0)
titlebar.open(index?:=0);
titlebar.close();
titlebar.updateOptions({
onOpenMenuHook: onOpenMenuHook
});
// ์๋์ผ๋ก index ์ด๊ธฐ/๋ซ๊ธฐ
// titlebar.open(index);
// titlebar.close();
function onOpenMenuHook(index){
// ๋ฉ๋ด ๋ด์ฉ ๊ต์ฒด
titlebar.updateOptions({
menu: ์๋ก์ด ๋ฉ๋ด ํ
ํ๋ฆฟ
});
// true๋ฅผ return ํ๋ฉด ๋ฉ๋ด๊ฐ ์ด๋ฆฌ๋๊ฒ์ ์ค์งํ๋ฏ๋ก ์ดํ ์๋์ผ๋ก ์ด์ด์ผ ํจ
// return true;
}
icon
์ต์
์ด ์ง์ ๋ ๊ฒฝ์ฐ img
ํ๊ทธ์ css๊ฐ ์ค์ ๋๊ณ , ์ง์ ๋์ง ์์ ๊ฒฝ์ฐ div
ํ๊ทธ์ ์ง์ ๋จ
#์ด๋ฆ
: id๋ก ์ง์ .์ด๋ฆ
,์ด๋ฆ
: class๋ก ์ง์ [์ด๋ฆ]
,[์ด๋ฆ=""]
,[์ด๋ฆ='']
: attribute์ผ๋ก ์ง์
titlebar.updateOptions({
iconSelector: 'CSS Selector ์ง์ ',
className: 'CSS Selector ์ง์ '
});
// CSS์์ ์์ด์ฝ ๋ชจ์ ์ง์
์๋์ฐ์ฐฝ์ด ํฌ์ปค์ฑ์ ์์์๋ ๋ฉ๋ด ์ฐฝ์ ๋ซ์์ง(true) ์ฌ๋ถ (default=true)
๊ฐ๋ฐ์ฉ์ผ๋ก ์ฌ์ฉ. true๋ก ์ง์ ํ๋ฉด blur, mouseleave ์ด๋ฒคํธ์ ๋ํด์ ์ฐฝ์ ๋ซ์ง ์์ (default=false)
webpack watch --mode development
npm run watch