Add Flipper logs to leftrail

Summary:
This adds support for flipper logs in Sandy, including some theme adjustments.

Did remove storage and showing of debug messages, as I noticed it tends to crash Flipper after a while since there are so many. Also added a fixed limit of only remembering last 1000

Also converted Toolbar and button with dropdown items to Sandy.

Reviewed By: cekkaewnumchai

Differential Revision: D23824528

fbshipit-source-id: b89d1182d4f14682251dbb482d93c2c009ddc7a4
This commit is contained in:
Michel Weststrate
2020-09-24 05:56:14 -07:00
committed by Facebook GitHub Bot
parent 191df465b7
commit aea04dd0cf
10 changed files with 275 additions and 46 deletions

View File

@@ -12,8 +12,13 @@ import {Button, Toolbar, ButtonGroup, Layout} from '../ui';
import React from 'react'; import React from 'react';
import {Console, Hook} from 'console-feed'; import {Console, Hook} from 'console-feed';
import type {Methods} from 'console-feed/lib/definitions/Methods'; import type {Methods} from 'console-feed/lib/definitions/Methods';
import type {Styles} from 'console-feed/lib/definitions/Styles';
import {createState, useValue} from 'flipper-plugin'; import {createState, useValue} from 'flipper-plugin';
import {useLocalStorage} from '../utils/useLocalStorage'; import {useLocalStorage} from '../utils/useLocalStorage';
import {theme, useIsDarkMode} from '../sandy-chrome/theme';
import {useIsSandy} from '../sandy-chrome/SandyContext';
const MAX_LOG_ITEMS = 1000;
const logsAtom = createState<any[]>([]); const logsAtom = createState<any[]>([]);
export const errorCounterAtom = createState(0); export const errorCounterAtom = createState(0);
@@ -23,7 +28,12 @@ export function enableConsoleHook() {
Hook( Hook(
window.console, window.console,
(log) => { (log) => {
logsAtom.set([...logsAtom.get(), log]); if (log.method === 'debug') {
return; // See below, skip debug messages which are generated very aggressively by Flipper
}
const newLogs = logsAtom.get().slice(-MAX_LOG_ITEMS);
newLogs.push(log);
logsAtom.set(newLogs);
if (log.method === 'error' || log.method === 'assert') { if (log.method === 'error' || log.method === 'assert') {
errorCounterAtom.set(errorCounterAtom.get() + 1); errorCounterAtom.set(errorCounterAtom.get() + 1);
} }
@@ -39,7 +49,8 @@ function clearLogs() {
const allLogLevels: Methods[] = [ const allLogLevels: Methods[] = [
'log', 'log',
'debug', // 'debug', We typically don't want to allow users to enable the debug logs, as they are used very intensively by flipper itself,
// making Flipper / console-feed. For debug level logging, use the Chrome devtools.
'info', 'info',
'warn', 'warn',
'error', 'error',
@@ -54,6 +65,8 @@ const allLogLevels: Methods[] = [
const defaultLogLevels: Methods[] = ['warn', 'error', 'table', 'assert']; const defaultLogLevels: Methods[] = ['warn', 'error', 'table', 'assert'];
export function ConsoleLogs() { export function ConsoleLogs() {
const isSandy = useIsSandy();
const isDarkMode = useIsDarkMode();
const logs = useValue(logsAtom); const logs = useValue(logsAtom);
const [logLevels, setLogLevels] = useLocalStorage<Methods[]>( const [logLevels, setLogLevels] = useLocalStorage<Methods[]>(
'console-logs-loglevels', 'console-logs-loglevels',
@@ -77,6 +90,8 @@ export function ConsoleLogs() {
); );
}, [logLevels, setLogLevels]); }, [logLevels, setLogLevels]);
const styles = useMemo(() => buildTheme(isSandy), [isSandy]);
return ( return (
<Layout.Top scrollable> <Layout.Top scrollable>
<Toolbar> <Toolbar>
@@ -87,7 +102,46 @@ export function ConsoleLogs() {
<Button dropdown={dropdown}>Log Levels</Button> <Button dropdown={dropdown}>Log Levels</Button>
</ButtonGroup> </ButtonGroup>
</Toolbar> </Toolbar>
<Console logs={logs} filter={logLevels} variant="light" /> <Console
logs={logs}
filter={logLevels}
variant={isDarkMode || !isSandy ? 'dark' : 'light'}
styles={styles}
/>
</Layout.Top> </Layout.Top>
); );
} }
function buildTheme(isSandy: boolean): Styles {
if (!isSandy) {
const bg = '#333';
return {
BASE_BACKGROUND_COLOR: bg,
BASE_COLOR: 'white',
LOG_BACKGROUND: bg,
};
}
return {
// See: https://github.com/samdenty/console-feed/blob/master/src/definitions/Styles.d.ts
BASE_BACKGROUND_COLOR: 'transparent',
BASE_COLOR: theme.textColorPrimary,
LOG_COLOR: theme.textColorPrimary,
LOG_BACKGROUND: 'transparent',
LOG_INFO_BACKGROUND: 'transparent',
LOG_COMMAND_BACKGROUND: 'transparent',
LOG_RESULT_BACKGROUND: 'transparent',
LOG_WARN_BACKGROUND: theme.warningColor,
LOG_ERROR_BACKGROUND: theme.errorColor,
LOG_INFO_COLOR: theme.textColorPrimary,
LOG_COMMAND_COLOR: theme.textColorSecondary,
LOG_RESULT_COLOR: theme.textColorSecondary,
LOG_WARN_COLOR: 'white',
LOG_ERROR_COLOR: 'white',
LOG_INFO_BORDER: theme.dividerColor,
LOG_COMMAND_BORDER: theme.dividerColor,
LOG_RESULT_BORDER: theme.dividerColor,
LOG_WARN_BORDER: theme.dividerColor,
LOG_ERROR_BORDER: theme.dividerColor,
LOG_BORDER: theme.dividerColor,
};
}

View File

@@ -43,7 +43,7 @@ exports[`SettingsSheet snapshot with nothing enabled 1`] = `
TestDevicePlugin TestDevicePlugin
</span> </span>
<div <div
className="css-te359u-View-FlexBox-Spacer e13mj6h81" className="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<input <input
checked={false} checked={false}
@@ -84,7 +84,7 @@ exports[`SettingsSheet snapshot with nothing enabled 1`] = `
TestPlugin TestPlugin
</span> </span>
<div <div
className="css-te359u-View-FlexBox-Spacer e13mj6h81" className="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<input <input
checked={false} checked={false}
@@ -109,7 +109,7 @@ exports[`SettingsSheet snapshot with nothing enabled 1`] = `
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20" className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
> >
<div <div
className="css-te359u-View-FlexBox-Spacer e13mj6h81" className="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<div <div
className="css-12n892b" className="css-12n892b"
@@ -188,7 +188,7 @@ exports[`SettingsSheet snapshot with one plugin enabled 1`] = `
TestDevicePlugin TestDevicePlugin
</span> </span>
<div <div
className="css-te359u-View-FlexBox-Spacer e13mj6h81" className="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<input <input
checked={false} checked={false}
@@ -229,7 +229,7 @@ exports[`SettingsSheet snapshot with one plugin enabled 1`] = `
TestPlugin TestPlugin
</span> </span>
<div <div
className="css-te359u-View-FlexBox-Spacer e13mj6h81" className="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<input <input
checked={true} checked={true}
@@ -254,7 +254,7 @@ exports[`SettingsSheet snapshot with one plugin enabled 1`] = `
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20" className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
> >
<div <div
className="css-te359u-View-FlexBox-Spacer e13mj6h81" className="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<div <div
className="css-12n892b" className="css-12n892b"

View File

@@ -27,7 +27,7 @@ exports[`ShareSheetPendingDialog is rendered with status update 1`] = `
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20" className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
> >
<div <div
className="css-te359u-View-FlexBox-Spacer e13mj6h81" className="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<div <div
className="css-xing9h-StyledButton enfqd40" className="css-xing9h-StyledButton enfqd40"
@@ -77,7 +77,7 @@ exports[`ShareSheetPendingDialog is rendered without status update 1`] = `
className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20" className="css-17wo7w2-View-FlexBox-FlexRow epz0qe20"
> >
<div <div
className="css-te359u-View-FlexBox-Spacer e13mj6h81" className="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<div <div
className="css-xing9h-StyledButton enfqd40" className="css-xing9h-StyledButton enfqd40"

View File

@@ -6,7 +6,7 @@ exports[`load PluginInstaller list 1`] = `
class="css-13jp8bd-View-FlexBox-FlexColumn" class="css-13jp8bd-View-FlexBox-FlexColumn"
> >
<div <div
class="css-mx54fh-View-FlexBox-FlexRow-Toolbar e13mj6h80" class="css-1qqef1i-View-FlexBox-FlexRow-ToolbarContainer e13mj6h80"
> >
<div <div
class="css-awcbnc-View-FlexBox-SearchBox e271nro1" class="css-awcbnc-View-FlexBox-SearchBox e271nro1"
@@ -137,7 +137,7 @@ exports[`load PluginInstaller list 1`] = `
World? World?
</span> </span>
<div <div
class="css-te359u-View-FlexBox-Spacer e13mj6h81" class="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<span <span
class="css-ad6n9d-StyledLink e1mzoj7l0" class="css-ad6n9d-StyledLink e1mzoj7l0"
@@ -204,7 +204,7 @@ exports[`load PluginInstaller list 1`] = `
Hello? Hello?
</span> </span>
<div <div
class="css-te359u-View-FlexBox-Spacer e13mj6h81" class="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<span <span
class="css-ad6n9d-StyledLink e1mzoj7l0" class="css-ad6n9d-StyledLink e1mzoj7l0"
@@ -235,7 +235,7 @@ exports[`load PluginInstaller list 1`] = `
</div> </div>
</div> </div>
<div <div
class="css-mx54fh-View-FlexBox-FlexRow-Toolbar e13mj6h80" class="css-1qqef1i-View-FlexBox-FlexRow-ToolbarContainer e13mj6h80"
> >
<div <div
class="css-1stmykz-View-FlexBox-FlexRow-Container ersmi541" class="css-1stmykz-View-FlexBox-FlexRow-Container ersmi541"
@@ -301,7 +301,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
class="css-13jp8bd-View-FlexBox-FlexColumn" class="css-13jp8bd-View-FlexBox-FlexColumn"
> >
<div <div
class="css-mx54fh-View-FlexBox-FlexRow-Toolbar e13mj6h80" class="css-1qqef1i-View-FlexBox-FlexRow-ToolbarContainer e13mj6h80"
> >
<div <div
class="css-awcbnc-View-FlexBox-SearchBox e271nro1" class="css-awcbnc-View-FlexBox-SearchBox e271nro1"
@@ -432,7 +432,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
World? World?
</span> </span>
<div <div
class="css-te359u-View-FlexBox-Spacer e13mj6h81" class="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<span <span
class="css-ad6n9d-StyledLink e1mzoj7l0" class="css-ad6n9d-StyledLink e1mzoj7l0"
@@ -498,7 +498,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
Hello? Hello?
</span> </span>
<div <div
class="css-te359u-View-FlexBox-Spacer e13mj6h81" class="css-te359u-View-FlexBox-Spacer e13mj6h82"
/> />
<span <span
class="css-ad6n9d-StyledLink e1mzoj7l0" class="css-ad6n9d-StyledLink e1mzoj7l0"
@@ -529,7 +529,7 @@ exports[`load PluginInstaller list with one plugin installed 1`] = `
</div> </div>
</div> </div>
<div <div
class="css-mx54fh-View-FlexBox-FlexRow-Toolbar e13mj6h80" class="css-1qqef1i-View-FlexBox-FlexRow-ToolbarContainer e13mj6h80"
> >
<div <div
class="css-1stmykz-View-FlexBox-FlexRow-Container ersmi541" class="css-1stmykz-View-FlexBox-FlexRow-Container ersmi541"

View File

@@ -27,6 +27,10 @@ import {toggleLeftSidebarVisible} from '../reducers/application';
import {theme} from './theme'; import {theme} from './theme';
import SettingsSheet from '../chrome/SettingsSheet'; import SettingsSheet from '../chrome/SettingsSheet';
import WelcomeScreen from './WelcomeScreen'; import WelcomeScreen from './WelcomeScreen';
import {isStaticViewActive} from '../chrome/mainsidebar/sidebarUtils';
import {ConsoleLogs, errorCounterAtom} from '../chrome/ConsoleLogs';
import {setStaticView} from '../reducers/connections';
import {useValue} from 'flipper-plugin';
const LeftRailContainer = styled(FlexColumn)({ const LeftRailContainer = styled(FlexColumn)({
background: theme.backgroundDefault, background: theme.backgroundDefault,
@@ -109,10 +113,7 @@ export function LeftRail() {
title="Notifications" title="Notifications"
/> />
<LeftRailDivider /> <LeftRailDivider />
<LeftRailButton <DebugLogsButton />
icon={<FileExclamationOutlined />}
title="Flipper Logs"
/>
</LeftRailSection> </LeftRailSection>
<LeftRailSection> <LeftRailSection>
<LeftRailButton <LeftRailButton
@@ -159,6 +160,25 @@ function LeftSidebarToggleButton() {
); );
} }
function DebugLogsButton() {
const staticView = useStore((state) => state.connections.staticView);
const active = isStaticViewActive(staticView, ConsoleLogs);
const errorCount = useValue(errorCounterAtom);
const dispatch = useDispatch();
return (
<LeftRailButton
icon={<FileExclamationOutlined />}
title="Flipper Logs"
selected={active}
count={errorCount}
onClick={() => {
dispatch(setStaticView(ConsoleLogs));
}}
/>
);
}
function ShowSettingsButton() { function ShowSettingsButton() {
const [showSettings, setShowSettings] = useState(false); const [showSettings, setShowSettings] = useState(false);
const onClose = useCallback(() => setShowSettings(false), []); const onClose = useCallback(() => setShowSettings(false), []);

View File

@@ -29,8 +29,10 @@ export function SandyApp({logger}: {logger: Logger}) {
}, []); }, []);
const mainMenuVisible = useStore( const mainMenuVisible = useStore(
(state) => state.application.leftSidebarVisible, (state) =>
state.application.leftSidebarVisible && !state.connections.staticView,
); );
const staticView = useStore((state) => state.connections.staticView);
return ( return (
<SandyContext.Provider value={true}> <SandyContext.Provider value={true}>
@@ -51,7 +53,13 @@ export function SandyApp({logger}: {logger: Logger}) {
<Layout.Right initialSize={300} minSize={200}> <Layout.Right initialSize={300} minSize={200}>
<MainContentWrapper> <MainContentWrapper>
<ContentContainer> <ContentContainer>
{staticView ? (
React.createElement(staticView, {
logger: logger,
})
) : (
<TemporarilyContent /> <TemporarilyContent />
)}
</ContentContainer> </ContentContainer>
</MainContentWrapper> </MainContentWrapper>
<MainContentWrapper> <MainContentWrapper>

View File

@@ -7,6 +7,8 @@
* @format * @format
*/ */
import {useStore} from '../utils/useStore';
// Exposes all the variables defined in themes/base.less: // Exposes all the variables defined in themes/base.less:
export const theme = { export const theme = {
@@ -33,3 +35,14 @@ export const theme = {
smallBody: '12px', smallBody: '12px',
}, },
}; };
/**
* This hook returns whether dark mode is currently being used.
* Generally should be avoided in favor of using the above theme object,
* which will provide colors that reflect the theme
*/
export function useIsDarkMode(): boolean {
return useStore(
(state) => state.settingsState.enableSandy && state.settingsState.darkMode,
);
}

View File

@@ -7,12 +7,12 @@
* @format * @format
*/ */
import React, {useContext, useState, useRef, useCallback} from 'react'; import React, {useContext, useState, useRef, useCallback, useMemo} from 'react';
import electron, {MenuItemConstructorOptions} from 'electron'; import electron, {MenuItemConstructorOptions} from 'electron';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import {findDOMNode} from 'react-dom'; import {findDOMNode} from 'react-dom';
import {keyframes} from 'emotion'; import {keyframes} from 'emotion';
import {Button as AntdButton} from 'antd'; import {Button as AntdButton, Dropdown, Menu} from 'antd';
import {colors} from './colors'; import {colors} from './colors';
import Glyph, {IconSize} from './Glyph'; import Glyph, {IconSize} from './Glyph';
@@ -20,6 +20,8 @@ import {ButtonGroupContext} from './ButtonGroup';
import {useStore} from '../../utils/useStore'; import {useStore} from '../../utils/useStore';
import {useIsSandy} from '../../sandy-chrome/SandyContext'; import {useIsSandy} from '../../sandy-chrome/SandyContext';
import type {ButtonProps} from 'antd/lib/button'; import type {ButtonProps} from 'antd/lib/button';
import {DownOutlined, CheckOutlined} from '@ant-design/icons';
import {theme} from '../../sandy-chrome/theme';
type ButtonType = 'primary' | 'success' | 'warning' | 'danger'; type ButtonType = 'primary' | 'success' | 'warning' | 'danger';
@@ -217,7 +219,7 @@ type Props = {
/** /**
* Type of button. * Type of button.
*/ */
type?: ButtonType; type?: ButtonType; // TODO: normalize to Sandy
/** /**
* Children. * Children.
*/ */
@@ -264,11 +266,15 @@ type Props = {
* A simple button, used in many parts of the application. * A simple button, used in many parts of the application.
*/ */
export default function Button(props: Props) { export default function Button(props: Props) {
const isSandy = useIsSandy();
return isSandy ? <SandyButton {...props} /> : <ClassicButton {...props} />;
}
function ClassicButton(props: Props) {
const windowIsFocused = useStore( const windowIsFocused = useStore(
(state) => state.application.windowIsFocused, (state) => state.application.windowIsFocused,
); );
const inButtonGroup = useContext(ButtonGroupContext); const inButtonGroup = useContext(ButtonGroupContext);
const isSandy = useIsSandy();
const [active, setActive] = useState(false); const [active, setActive] = useState(false);
const [wasClosed, setWasClosed] = useState(false); const [wasClosed, setWasClosed] = useState(false);
@@ -359,19 +365,7 @@ export default function Button(props: Props) {
); );
} }
return isSandy ? ( return (
<AntdButton
{...restProps}
type={props.type === 'primary' ? 'primary' : 'default'}
danger={props.type === 'danger'}
ref={_ref}
onClick={onClick}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
icon={iconComponent}>
{children}
</AntdButton>
) : (
<StyledButton <StyledButton
{...restProps} {...restProps}
ref={_ref as any} ref={_ref as any}
@@ -385,3 +379,108 @@ export default function Button(props: Props) {
</StyledButton> </StyledButton>
); );
} }
/**
* A simple button, used in many parts of the application.
*/
export function SandyButton({
compact,
disabled,
icon,
children,
iconSize,
iconVariant,
dropdown,
type,
onClick,
href,
...restProps
}: Props) {
const [dropdownVisible, setDropdownVible] = useState(false);
const handleClick = useCallback(
(e: React.MouseEvent) => {
if (disabled === true) {
return;
}
onClick?.(e);
if (href != null) {
electron.shell.openExternal(href); // TODO: decouple from Electron
}
},
[disabled, onClick, href],
);
const handleVisibleChange = useCallback((flag: boolean) => {
setDropdownVible(flag);
}, []);
let iconComponent;
if (icon != null) {
iconComponent = (
<Icon
name={icon}
size={iconSize || (compact === true ? 12 : 16)}
color={theme.textColorPrimary}
variant={iconVariant || 'filled'}
hasText={Boolean(children)}
/>
);
}
const dropdownItems = useMemo(
() =>
dropdown && (
<Menu>
{dropdown!.map((item, idx) => (
<Menu.Item
onClick={(e) => {
// @ts-ignore this event args are bound to electron, remove in the future
item.click();
if (item.checked !== undefined) {
// keep the menu item for check lists
e.domEvent.stopPropagation();
e.domEvent.preventDefault();
}
}}
disabled={item.enabled === false}
icon={
item.checked !== undefined && (
<CheckOutlined
style={{visibility: item.checked ? 'visible' : 'hidden'}}
/>
)
}
key={idx}>
{item.label}
</Menu.Item>
))}
</Menu>
),
[dropdown],
);
const button = (
<AntdButton
/* Probably more properties need passing on, but lets be explicit about it */
style={restProps.style}
disabled={disabled}
type={type === 'primary' ? 'primary' : 'default'}
danger={type === 'danger'}
onClick={handleClick}
icon={iconComponent}>
{children}
{dropdown ? <DownOutlined /> : null}
</AntdButton>
);
if (dropdown) {
return (
<Dropdown
overlay={dropdownItems!}
visible={dropdownVisible}
onVisibleChange={handleVisibleChange}>
{button}
</Dropdown>
);
} else {
return button;
}
}

View File

@@ -9,6 +9,8 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import React, {createContext} from 'react'; import React, {createContext} from 'react';
import {useIsSandy} from '../../sandy-chrome/SandyContext';
import {Space} from 'antd';
const ButtonGroupContainer = styled.div({ const ButtonGroupContainer = styled.div({
display: 'inline-flex', display: 'inline-flex',
@@ -33,7 +35,12 @@ export const ButtonGroupContext = createContext(false);
* ``` * ```
*/ */
export default function ButtonGroup({children}: {children: React.ReactNode}) { export default function ButtonGroup({children}: {children: React.ReactNode}) {
return ( const isSandy = useIsSandy(); // according to Ant design guides buttons should only be grouped if they are radios
return isSandy ? (
<ButtonGroupContext.Provider value={true}>
<Space>{children}</Space>
</ButtonGroupContext.Provider>
) : (
<ButtonGroupContext.Provider value={true}> <ButtonGroupContext.Provider value={true}>
<ButtonGroupContainer>{children}</ButtonGroupContainer> <ButtonGroupContainer>{children}</ButtonGroupContainer>
</ButtonGroupContext.Provider> </ButtonGroupContext.Provider>

View File

@@ -7,15 +7,19 @@
* @format * @format
*/ */
import React from 'react';
import {colors} from './colors'; import {colors} from './colors';
import FlexRow from './FlexRow'; import FlexRow from './FlexRow';
import FlexBox from './FlexBox'; import FlexBox from './FlexBox';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import {Space} from 'antd';
import {useIsSandy} from '../../sandy-chrome/SandyContext';
import {theme} from '../../sandy-chrome/theme';
/** /**
* A toolbar. * A toolbar.
*/ */
const Toolbar = styled(FlexRow)<{ const ToolbarContainer = styled(FlexRow)<{
position?: 'bottom' | 'top'; position?: 'bottom' | 'top';
compact?: boolean; compact?: boolean;
}>((props) => ({ }>((props) => ({
@@ -36,11 +40,35 @@ const Toolbar = styled(FlexRow)<{
padding: 6, padding: 6,
width: '100%', width: '100%',
})); }));
Toolbar.displayName = 'Toolbar'; ToolbarContainer.displayName = 'ToolbarContainer';
const SandyToolbarContainer = styled(Space)({
width: '100%',
padding: theme.space.small,
boxShadow: `inset 0px -1px 0px ${theme.dividerColor}`,
});
export const Spacer = styled(FlexBox)({ export const Spacer = styled(FlexBox)({
flexGrow: 1, flexGrow: 1,
}); });
Spacer.displayName = 'Spacer'; Spacer.displayName = 'Spacer';
export default Toolbar; export default function Toolbar({
children,
style,
...rest
}: {
children?: React.ReactNode;
position?: 'bottom' | 'top';
compact?: boolean;
style?: React.CSSProperties;
}) {
const isSandy = useIsSandy();
return isSandy ? (
<SandyToolbarContainer style={style}>{children}</SandyToolbarContainer>
) : (
<ToolbarContainer style={style} {...rest}>
{children}
</ToolbarContainer>
);
}