Use native dark theme titlebar on Windows

This commit is contained in:
Fedor Indutny 2024-01-16 22:32:38 +01:00 committed by GitHub
parent b574ba531d
commit 23e3883ce0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 152 additions and 965 deletions

View file

@ -3,17 +3,12 @@
import React from 'react';
import type { ExecuteMenuRoleType } from './TitleBarContainer';
import type { LocalizerType } from '../types/Util';
import { TitleBarContainer } from './TitleBarContainer';
import { useEscapeHandling } from '../hooks/useEscapeHandling';
import { useTheme } from '../hooks/useTheme';
export type PropsType = {
closeAbout: () => unknown;
environment: string;
executeMenuRole: ExecuteMenuRoleType;
hasCustomTitleBar: boolean;
i18n: LocalizerType;
version: string;
};
@ -21,46 +16,36 @@ export type PropsType = {
export function About({
closeAbout,
environment,
executeMenuRole,
hasCustomTitleBar,
i18n,
version,
}: PropsType): JSX.Element {
useEscapeHandling(closeAbout);
const theme = useTheme();
return (
<TitleBarContainer
hasCustomTitleBar={hasCustomTitleBar}
theme={theme}
executeMenuRole={executeMenuRole}
>
<div className="About">
<div className="module-splash-screen">
<div className="module-splash-screen__logo module-img--150" />
<div className="About">
<div className="module-splash-screen">
<div className="module-splash-screen__logo module-img--150" />
<div className="version">{version}</div>
<div className="environment">{environment}</div>
<div>
<a href="https://signal.org">signal.org</a>
</div>
<br />
<div>
<a
className="acknowledgments"
href="https://github.com/signalapp/Signal-Desktop/blob/main/ACKNOWLEDGMENTS.md"
>
{i18n('icu:softwareAcknowledgments')}
</a>
</div>
<div>
<a className="privacy" href="https://signal.org/legal">
{i18n('icu:privacyPolicy')}
</a>
</div>
<div className="version">{version}</div>
<div className="environment">{environment}</div>
<div>
<a href="https://signal.org">signal.org</a>
</div>
<br />
<div>
<a
className="acknowledgments"
href="https://github.com/signalapp/Signal-Desktop/blob/main/ACKNOWLEDGMENTS.md"
>
{i18n('icu:softwareAcknowledgments')}
</a>
</div>
<div>
<a className="privacy" href="https://signal.org/legal">
{i18n('icu:privacyPolicy')}
</a>
</div>
</div>
</TitleBarContainer>
</div>
);
}

View file

@ -5,8 +5,6 @@ import React, { useEffect } from 'react';
import { Globals } from '@react-spring/web';
import classNames from 'classnames';
import type { ExecuteMenuRoleType } from './TitleBarContainer';
import type { MenuOptionsType, MenuActionType } from '../types/menu';
import type { AnyToast } from '../types/Toast';
import type { ViewStoryActionCreatorType } from '../state/ducks/stories';
import type { LocalizerType } from '../types/Util';
@ -15,7 +13,6 @@ import { ThemeType } from '../types/Util';
import { AppViewType } from '../state/ducks/app';
import { SmartInstallScreen } from '../state/smart/InstallScreen';
import { StandaloneRegistration } from './StandaloneRegistration';
import { TitleBarContainer } from './TitleBarContainer';
import { ToastManager } from './ToastManager';
import { usePageVisibility } from '../hooks/usePageVisibility';
import { useReducedMotion } from '../hooks/useReducedMotion';
@ -42,18 +39,12 @@ type PropsType = {
theme: ThemeType;
isMaximized: boolean;
isFullScreen: boolean;
menuOptions: MenuOptionsType;
onUndoArchive: (conversationId: string) => unknown;
openFileInFolder: (target: string) => unknown;
hasCustomTitleBar: boolean;
OS: string;
osClassName: string;
hideMenuBar: boolean;
executeMenuRole: ExecuteMenuRoleType;
executeMenuAction: (action: MenuActionType) => void;
hideToast: () => unknown;
titleBarDoubleClick: () => void;
toast?: AnyToast;
scrollToMessage: (conversationId: string, messageId: string) => unknown;
viewStory: ViewStoryActionCreatorType;
@ -62,16 +53,11 @@ type PropsType = {
export function App({
appView,
executeMenuAction,
executeMenuRole,
hasCustomTitleBar,
hasSelectedStoryData,
hideMenuBar,
hideToast,
i18n,
isFullScreen,
isMaximized,
menuOptions,
onUndoArchive,
openFileInFolder,
openInbox,
@ -85,7 +71,6 @@ export function App({
renderStoryViewer,
requestVerification,
theme,
titleBarDoubleClick,
toast,
viewStory,
}: PropsType): JSX.Element {
@ -127,10 +112,6 @@ export function App({
document.body.classList.add(osClassName);
}, [osClassName]);
useEffect(() => {
document.body.classList.toggle('os-has-custom-titlebar', hasCustomTitleBar);
}, [hasCustomTitleBar]);
useEffect(() => {
document.body.classList.toggle('full-screen', isFullScreen);
document.body.classList.toggle('maximized', isMaximized);
@ -150,41 +131,27 @@ export function App({
}, [prefersReducedMotion]);
return (
<TitleBarContainer
theme={theme}
isMaximized={isMaximized}
isFullScreen={isFullScreen}
hasCustomTitleBar={hasCustomTitleBar}
executeMenuRole={executeMenuRole}
titleBarDoubleClick={titleBarDoubleClick}
hasMenu
hideMenuBar={hideMenuBar}
i18n={i18n}
menuOptions={menuOptions}
executeMenuAction={executeMenuAction}
<div
className={classNames({
App: true,
'light-theme': theme === ThemeType.light,
'dark-theme': theme === ThemeType.dark,
})}
>
<div
className={classNames({
App: true,
'light-theme': theme === ThemeType.light,
'dark-theme': theme === ThemeType.dark,
})}
>
{contents}
<ToastManager
OS={OS}
hideToast={hideToast}
i18n={i18n}
onUndoArchive={onUndoArchive}
openFileInFolder={openFileInFolder}
toast={toast}
/>
{renderGlobalModalContainer()}
{renderCallManager()}
{renderLightbox()}
{hasSelectedStoryData &&
renderStoryViewer(() => viewStory({ closeViewer: true }))}
</div>
</TitleBarContainer>
{contents}
<ToastManager
OS={OS}
hideToast={hideToast}
i18n={i18n}
onUndoArchive={onUndoArchive}
openFileInFolder={openFileInFolder}
toast={toast}
/>
{renderGlobalModalContainer()}
{renderCallManager()}
{renderLightbox()}
{hasSelectedStoryData &&
renderStoryViewer(() => viewStory({ closeViewer: true }))}
</div>
);
}

View file

@ -294,7 +294,7 @@ export function CallingPip({
positionState.mode === PositionMode.BeingDragged
? '-webkit-grabbing'
: '-webkit-grab',
transform: `translate3d(${localizedTranslateX}px,calc(${translateY}px - var(--titlebar-height)), 0)`,
transform: `translate3d(${localizedTranslateX}px,calc(${translateY}px), 0)`,
transition:
positionState.mode === PositionMode.BeingDragged
? 'none'

View file

@ -26,8 +26,6 @@ const createProps = (): PropsType => ({
await sleep(5000);
return 'https://picsum.photos/1800/900';
},
executeMenuRole: action('executeMenuRole'),
hasCustomTitleBar: true,
});
export default {

View file

@ -4,20 +4,17 @@
import type { MouseEvent } from 'react';
import React, { useEffect, useState } from 'react';
import copyText from 'copy-text-to-clipboard';
import type { ExecuteMenuRoleType } from './TitleBarContainer';
import type { LocalizerType } from '../types/Util';
import * as Errors from '../types/errors';
import * as log from '../logging/log';
import { Button, ButtonVariant } from './Button';
import { Spinner } from './Spinner';
import { TitleBarContainer } from './TitleBarContainer';
import { ToastDebugLogError } from './ToastDebugLogError';
import { ToastLinkCopied } from './ToastLinkCopied';
import { ToastLoadingFullLogs } from './ToastLoadingFullLogs';
import { createSupportUrl } from '../util/createSupportUrl';
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
import { useEscapeHandling } from '../hooks/useEscapeHandling';
import { useTheme } from '../hooks/useTheme';
enum LoadState {
NotStarted,
@ -32,8 +29,6 @@ export type PropsType = {
i18n: LocalizerType;
fetchLogs: () => Promise<string>;
uploadLogs: (logs: string) => Promise<string>;
hasCustomTitleBar: boolean;
executeMenuRole: ExecuteMenuRoleType;
};
enum ToastType {
@ -48,8 +43,6 @@ export function DebugLogWindow({
i18n,
fetchLogs,
uploadLogs,
hasCustomTitleBar,
executeMenuRole,
}: PropsType): JSX.Element {
const [loadState, setLoadState] = useState<LoadState>(LoadState.NotStarted);
const [logText, setLogText] = useState<string | undefined>();
@ -59,8 +52,6 @@ export function DebugLogWindow({
);
const [toastType, setToastType] = useState<ToastType | undefined>();
const theme = useTheme();
useEscapeHandling(closeWindow);
useEffect(() => {
@ -144,41 +135,35 @@ export function DebugLogWindow({
});
return (
<TitleBarContainer
hasCustomTitleBar={hasCustomTitleBar}
theme={theme}
executeMenuRole={executeMenuRole}
>
<div className="DebugLogWindow">
<div>
<div className="DebugLogWindow__title">
{i18n('icu:debugLogSuccess')}
</div>
<p className="DebugLogWindow__subtitle">
{i18n('icu:debugLogSuccessNextSteps')}
</p>
<div className="DebugLogWindow">
<div>
<div className="DebugLogWindow__title">
{i18n('icu:debugLogSuccess')}
</div>
<div className="DebugLogWindow__container">
<input
className="DebugLogWindow__link"
readOnly
type="text"
dir="auto"
value={publicLogURL}
/>
</div>
<div className="DebugLogWindow__footer">
<Button
onClick={() => openLinkInWebBrowser(supportURL)}
variant={ButtonVariant.Secondary}
>
{i18n('icu:reportIssue')}
</Button>
<Button onClick={copyLog}>{i18n('icu:debugLogCopy')}</Button>
</div>
{toastElement}
<p className="DebugLogWindow__subtitle">
{i18n('icu:debugLogSuccessNextSteps')}
</p>
</div>
</TitleBarContainer>
<div className="DebugLogWindow__container">
<input
className="DebugLogWindow__link"
readOnly
type="text"
dir="auto"
value={publicLogURL}
/>
</div>
<div className="DebugLogWindow__footer">
<Button
onClick={() => openLinkInWebBrowser(supportURL)}
variant={ButtonVariant.Secondary}
>
{i18n('icu:reportIssue')}
</Button>
<Button onClick={copyLog}>{i18n('icu:debugLogCopy')}</Button>
</div>
{toastElement}
</div>
);
}
@ -188,49 +173,43 @@ export function DebugLogWindow({
loadState === LoadState.Started || loadState === LoadState.Submitting;
return (
<TitleBarContainer
hasCustomTitleBar={hasCustomTitleBar}
theme={theme}
executeMenuRole={executeMenuRole}
>
<div className="DebugLogWindow">
<div>
<div className="DebugLogWindow__title">
{i18n('icu:submitDebugLog')}
</div>
<p className="DebugLogWindow__subtitle">
{i18n('icu:debugLogExplanation')}
</p>
<div className="DebugLogWindow">
<div>
<div className="DebugLogWindow__title">
{i18n('icu:submitDebugLog')}
</div>
{isLoading ? (
<div className="DebugLogWindow__container">
<Spinner svgSize="normal" />
</div>
) : (
<div className="DebugLogWindow__scroll_area">
<pre className="DebugLogWindow__scroll_area__text">
{textAreaValue}
</pre>
</div>
)}
<div className="DebugLogWindow__footer">
<Button
disabled={!canSave}
onClick={() => {
if (logText) {
downloadLog(logText);
}
}}
variant={ButtonVariant.Secondary}
>
{i18n('icu:debugLogSave')}
</Button>
<Button disabled={!canSubmit} onClick={handleSubmit}>
{i18n('icu:submit')}
</Button>
</div>
{toastElement}
<p className="DebugLogWindow__subtitle">
{i18n('icu:debugLogExplanation')}
</p>
</div>
</TitleBarContainer>
{isLoading ? (
<div className="DebugLogWindow__container">
<Spinner svgSize="normal" />
</div>
) : (
<div className="DebugLogWindow__scroll_area">
<pre className="DebugLogWindow__scroll_area__text">
{textAreaValue}
</pre>
</div>
)}
<div className="DebugLogWindow__footer">
<Button
disabled={!canSave}
onClick={() => {
if (logText) {
downloadLog(logText);
}
}}
variant={ButtonVariant.Secondary}
>
{i18n('icu:debugLogSave')}
</Button>
<Button disabled={!canSubmit} onClick={handleSubmit}>
{i18n('icu:submit')}
</Button>
</div>
{toastElement}
</div>
);
}

View file

@ -127,12 +127,10 @@ export const ModalHost = React.memo(function ModalHostInner({
}
// Exemptions:
// - TitleBar should always receive clicks.
// - Quill suggestions since they are placed in the document.body
// - Calling module (and pip) are always above everything else
const exemptParent = target.closest(
'.TitleBarContainer__title, ' +
'.module-composition-input__suggestions, ' +
'.module-composition-input__suggestions, ' +
'.module-composition-input__format-menu, ' +
'.module-calling__modal-container'
);

View file

@ -82,7 +82,6 @@ export default {
hasCallNotifications: true,
hasCallRingtoneNotification: false,
hasCountMutedConversations: false,
hasCustomTitleBar: true,
hasHideMenuBar: false,
hasIncomingCallNotifications: true,
hasLinkPreviews: true,
@ -131,7 +130,6 @@ export default {
doDeleteAllData: action('doDeleteAllData'),
doneRendering: action('doneRendering'),
editCustomColor: action('editCustomColor'),
executeMenuRole: action('executeMenuRole'),
makeSyncRequest: action('makeSyncRequest'),
onAudioNotificationsChange: action('onAudioNotificationsChange'),
onAutoConvertEmojiChange: action('onAutoConvertEmojiChange'),

View file

@ -33,7 +33,6 @@ import type {
SentMediaQualityType,
ThemeType,
} from '../types/Util';
import type { ExecuteMenuRoleType } from './TitleBarContainer';
import { Button, ButtonVariant } from './Button';
import { ChatColorPicker } from './ChatColorPicker';
@ -48,7 +47,6 @@ import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability';
import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
import { Select } from './Select';
import { Spinner } from './Spinner';
import { TitleBarContainer } from './TitleBarContainer';
import { getCustomColorStyle } from '../util/getCustomColorStyle';
import {
DEFAULT_DURATIONS_IN_SECONDS,
@ -58,7 +56,6 @@ import {
import { DurationInSeconds } from '../util/durations';
import { useEscapeHandling } from '../hooks/useEscapeHandling';
import { useUniqueId } from '../hooks/useUniqueId';
import { useTheme } from '../hooks/useTheme';
import { focusableSelectors } from '../util/focusableSelectors';
import { Modal } from './Modal';
import { SearchInput } from './SearchInput';
@ -116,7 +113,6 @@ export type PropsDataType = {
resolvedLocale: string;
// Other props
hasCustomTitleBar: boolean;
initialSpellCheckSetting: boolean;
// Limited support features
@ -141,7 +137,6 @@ type PropsFunctionType = {
doDeleteAllData: () => unknown;
doneRendering: () => unknown;
editCustomColor: (colorId: string, color: CustomColorType) => unknown;
executeMenuRole: ExecuteMenuRoleType;
getConversationsWithCustomColor: (
colorId: string
) => Promise<Array<ConversationType>>;
@ -256,7 +251,6 @@ export function Preferences({
doDeleteAllData,
doneRendering,
editCustomColor,
executeMenuRole,
getConversationsWithCustomColor,
hasAudioNotifications,
hasAutoConvertEmoji,
@ -291,7 +285,6 @@ export function Preferences({
isSyncSupported,
isSystemTraySupported,
isMinimizeToAndStartInSystemTraySupported,
hasCustomTitleBar,
lastSyncTime,
makeSyncRequest,
notificationContent,
@ -364,7 +357,6 @@ export function Preferences({
string | null
>(localeOverride);
const [languageSearchInput, setLanguageSearchInput] = useState('');
const theme = useTheme();
function closeLanguageDialog() {
setLanguageDialog(null);
@ -1502,11 +1494,7 @@ export function Preferences({
}
return (
<TitleBarContainer
hasCustomTitleBar={hasCustomTitleBar}
theme={theme}
executeMenuRole={executeMenuRole}
>
<>
<div className="module-title-bar-drag-area" />
<div className="Preferences">
<div className="Preferences__page-selector">
@ -1584,7 +1572,7 @@ export function Preferences({
{settings}
</div>
</div>
</TitleBarContainer>
</>
);
}

View file

@ -1,274 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useMemo } from 'react';
import type { ReactNode } from 'react';
import TitleBar from '@indutny/frameless-titlebar';
import type { MenuItem } from '@indutny/frameless-titlebar';
import type { MenuItemConstructorOptions } from 'electron';
import classNames from 'classnames';
import { createTemplate } from '../../app/menu';
import { ThemeType } from '../types/Util';
import type { LocalizerType } from '../types/I18N';
import type { MenuOptionsType, MenuActionType } from '../types/menu';
import { useIsWindowActive } from '../hooks/useIsWindowActive';
export type MenuPropsType = Readonly<{
hasMenu: true;
i18n: LocalizerType;
menuOptions: MenuOptionsType;
executeMenuAction: (action: MenuActionType) => void;
}>;
export type ExecuteMenuRoleType = (
role: MenuItemConstructorOptions['role']
) => void;
export type PropsType = Readonly<{
theme: ThemeType;
isMaximized?: boolean;
isFullScreen?: boolean;
hasCustomTitleBar: boolean;
hideMenuBar?: boolean;
executeMenuRole: ExecuteMenuRoleType;
titleBarDoubleClick?: () => void;
children: ReactNode;
}> &
(MenuPropsType | { hasMenu?: false });
const TITLEBAR_HEIGHT = 28;
// Windows only
const ROLE_TO_ACCELERATOR = new Map<
MenuItemConstructorOptions['role'],
string
>();
ROLE_TO_ACCELERATOR.set('undo', 'CmdOrCtrl+Z');
ROLE_TO_ACCELERATOR.set('redo', 'CmdOrCtrl+Y');
ROLE_TO_ACCELERATOR.set('cut', 'CmdOrCtrl+X');
ROLE_TO_ACCELERATOR.set('copy', 'CmdOrCtrl+C');
ROLE_TO_ACCELERATOR.set('paste', 'CmdOrCtrl+V');
ROLE_TO_ACCELERATOR.set('pasteAndMatchStyle', 'CmdOrCtrl+Shift+V');
ROLE_TO_ACCELERATOR.set('selectAll', 'CmdOrCtrl+A');
ROLE_TO_ACCELERATOR.set('resetZoom', 'CmdOrCtrl+0');
ROLE_TO_ACCELERATOR.set('zoomIn', 'CmdOrCtrl+=');
ROLE_TO_ACCELERATOR.set('zoomOut', 'CmdOrCtrl+-');
ROLE_TO_ACCELERATOR.set('togglefullscreen', 'F11');
ROLE_TO_ACCELERATOR.set('toggleDevTools', 'CmdOrCtrl+Shift+I');
ROLE_TO_ACCELERATOR.set('minimize', 'CmdOrCtrl+M');
function convertMenu(
menuList: ReadonlyArray<MenuItemConstructorOptions>,
executeMenuRole: (role: MenuItemConstructorOptions['role']) => void,
i18n: LocalizerType
): Array<MenuItem> {
return menuList.map(item => {
const {
type,
label,
accelerator: originalAccelerator,
click: originalClick,
submenu: originalSubmenu,
role,
} = item;
let submenu: Array<MenuItem> | undefined;
if (Array.isArray(originalSubmenu)) {
submenu = convertMenu(originalSubmenu, executeMenuRole, i18n);
} else if (originalSubmenu) {
throw new Error('Non-array submenu is not supported');
}
let click: (() => unknown) | undefined;
if (originalClick) {
if (role) {
throw new Error(`Menu item: ${label} has both click and role`);
}
// We don't use arguments in app/menu.ts
click = originalClick as () => unknown;
} else if (role) {
click = () => executeMenuRole(role);
}
let accelerator: string | undefined;
if (originalAccelerator) {
accelerator = originalAccelerator.toString();
} else if (role) {
accelerator = ROLE_TO_ACCELERATOR.get(role);
}
// Custom titlebar is visible only on Windows and this string is used only
// in UI. Actual accelerator interception is handled by Electron through
// `app/main.ts`.
accelerator = accelerator?.replace(
/CommandOrControl|CmdOrCtrl/g,
i18n('icu:Keyboard--Key--ctrl')
);
accelerator = accelerator?.replace(
/Shift/g,
i18n('icu:Keyboard--Key--shift')
);
return {
type,
label,
accelerator,
click,
submenu,
};
});
}
export function TitleBarContainer(props: PropsType): JSX.Element {
const {
theme,
isMaximized,
isFullScreen,
hasCustomTitleBar,
hideMenuBar,
executeMenuRole,
titleBarDoubleClick,
children,
hasMenu,
} = props;
const isWindowActive = useIsWindowActive();
const titleBarTheme = useMemo(
() => ({
bar: {
// See stylesheets/_global.scss
height: TITLEBAR_HEIGHT,
palette:
theme === ThemeType.light ? ('light' as const) : ('dark' as const),
...(theme === ThemeType.dark
? {
// $color-gray-05
color: '#e9e9e9',
// $color-gray-80
background: '#2e2e2e',
// $color-gray-95
borderBottom: '1px solid #121212',
//
button: {
active: {
// $color-gray-05
color: '#e9e9e9',
// $color-gray-75
background: '#3b3b3b',
},
hover: {
// $color-gray-05
color: '#e9e9e9',
// $color-gray-75
background: '#3b3b3b',
},
},
}
: {}),
},
// Hide overlay
menu: {
overlay: {
opacity: 0,
},
autoHide: hideMenuBar,
...(theme === ThemeType.dark
? {
separator: {
// $color-gray-95
color: '#5e5e5e',
},
accelerator: {
// $color-gray-25
color: '#b9b9b9',
},
list: {
// $color-gray-75
background: '#3b3b3b',
boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.12)',
borderRadius: '0px 0px 6px 6px',
},
}
: {
list: {
boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.12)',
borderRadius: '0px 0px 6px 6px',
},
}),
},
// Zoom support
enableOverflow: false,
scalingFunction(value: string) {
return `calc(${value} * var(--zoom-factor))`;
},
}),
[theme, hideMenuBar]
);
if (!hasCustomTitleBar) {
return <>{children}</>;
}
let maybeMenu: Array<MenuItem> | undefined;
if (hasMenu) {
const { i18n, menuOptions, executeMenuAction } = props;
const menuTemplate = createTemplate(
{
...menuOptions,
// actions
forceUpdate: () => executeMenuAction('forceUpdate'),
openArtCreator: () => executeMenuAction('openArtCreator'),
openContactUs: () => executeMenuAction('openContactUs'),
openForums: () => executeMenuAction('openForums'),
openJoinTheBeta: () => executeMenuAction('openJoinTheBeta'),
openReleaseNotes: () => executeMenuAction('openReleaseNotes'),
openSupportPage: () => executeMenuAction('openSupportPage'),
setupAsNewDevice: () => executeMenuAction('setupAsNewDevice'),
setupAsStandalone: () => executeMenuAction('setupAsStandalone'),
showAbout: () => executeMenuAction('showAbout'),
showDebugLog: () => executeMenuAction('showDebugLog'),
showKeyboardShortcuts: () => executeMenuAction('showKeyboardShortcuts'),
showSettings: () => executeMenuAction('showSettings'),
showWindow: () => executeMenuAction('showWindow'),
zoomIn: () => executeMenuAction('zoomIn'),
zoomOut: () => executeMenuAction('zoomOut'),
zoomReset: () => executeMenuAction('zoomReset'),
},
i18n
);
maybeMenu = convertMenu(menuTemplate, executeMenuRole, i18n);
}
return (
<div
className={classNames(
'TitleBarContainer',
isWindowActive ? 'TitleBarContainer--active' : null,
isFullScreen ? 'TitleBarContainer--fullscreen' : null
)}
>
<div className="TitleBarContainer__padding" />
<div className="TitleBarContainer__content">{children}</div>
<TitleBar
className="TitleBarContainer__title"
platform="win32"
iconSrc="images/titlebar_icon.svg"
theme={titleBarTheme}
maximized={isMaximized}
menu={maybeMenu}
onDoubleClick={titleBarDoubleClick}
hideControls
/>
</div>
);
}