Build main layout

Summary: This diff introduces the. main sections and restyled resizable panes according to the Figma design

Reviewed By: cekkaewnumchai

Differential Revision: D23758349

fbshipit-source-id: 7f09574f6b5fb54551141c13667c664e1769f09a
This commit is contained in:
Michel Weststrate
2020-09-21 11:50:45 -07:00
committed by Facebook GitHub Bot
parent 0100224833
commit 95638af321
11 changed files with 257 additions and 142 deletions

View File

@@ -36,12 +36,13 @@ const LeftRailSection = styled(FlexColumn)({
}); });
LeftRailSection.displayName = 'LeftRailSection'; LeftRailSection.displayName = 'LeftRailSection';
const LeftRailButtonElem = styled(Button)<{small?: boolean}>(({small}) => ({ const LeftRailButtonElem = styled(Button)<{margin: number}>(({margin}) => ({
width: 36, width: 36,
height: 36, height: 36,
margin: small ? 2 : 6, margin,
padding: '5px 0', padding: '5px 0',
border: 'none', border: 'none',
boxShadow: 'none',
})); }));
LeftRailButtonElem.displayName = 'LeftRailButtonElem'; LeftRailButtonElem.displayName = 'LeftRailButtonElem';
@@ -66,7 +67,7 @@ function LeftRailButton({
return ( return (
<Tooltip title={title} placement="right"> <Tooltip title={title} placement="right">
<LeftRailButtonElem <LeftRailButtonElem
small={small} margin={small ? 2 : 6}
type={active ? 'primary' : 'ghost'} type={active ? 'primary' : 'ghost'}
icon={iconElement} icon={iconElement}
/> />

View File

@@ -8,135 +8,84 @@
*/ */
import React from 'react'; import React from 'react';
import {connect} from 'react-redux'; import {styled} from 'flipper';
import {State as Store} from '../reducers'; import {DatePicker} from 'antd';
import {Settings, updateSettings} from '../reducers/settings'; import {Layout, FlexRow} from '../ui';
import {styled, FlexColumn, colors, Text} from 'flipper';
import {DatePicker, Button} from 'antd';
import {Layout, FlexBox} from '../ui';
import {theme} from './theme'; import {theme} from './theme';
import {LeftRail} from './LeftRail'; import {LeftRail} from './LeftRail';
import {CloseCircleOutlined} from '@ant-design/icons'; import {TemporarilyTitlebar} from './TemporarilyTitlebar';
type StateFromProps = {settings: Settings}; export function SandyApp() {
type DispatchFromProps = {disableSandy: (settings: Settings) => void};
type OwnProps = {};
type Props = StateFromProps & DispatchFromProps & OwnProps;
const Container = styled(FlexColumn)({
height: '100%',
width: '100%',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.light02,
});
const Box = styled(FlexColumn)({
justifyContent: 'center',
alignItems: 'center',
background: colors.white,
borderRadius: 10,
boxShadow: '0 1px 3px rgba(0,0,0,0.25)',
paddingBottom: 16,
});
const AnnoucementText = styled(Text)({
fontSize: 24,
fontWeight: 300,
textAlign: 'center',
margin: 16,
color: theme.primaryColor,
background: theme.backgroundWash,
});
const LeftContainer = styled(FlexBox)({
height: '100% ',
});
// This component should be dropped, and insetTitlebar should be removed from Electron startup once Sandy is the default
const TemporarilyTitlebar = styled('div')<{focused?: boolean}>(({focused}) => ({
textAlign: 'center',
userSelect: 'none',
height: '38px',
lineHeight: '38px',
fontSize: '10pt',
color: colors.macOSTitleBarIcon,
background: true
? `linear-gradient(to bottom, ${colors.macOSTitleBarBackgroundTop} 0%, ${colors.macOSTitleBarBackgroundBottom} 100%)`
: colors.macOSTitleBarBackgroundBlur,
borderBottom: `1px solid ${
focused ? colors.macOSTitleBarBorder : colors.macOSTitleBarBorderBlur
}`,
WebkitAppRegion: 'drag',
}));
function SandyApp(props: Props) {
return ( return (
<Layout.Top> <Layout.Top>
<TemporarilyTitlebar focused /*TODO: make dynamic */> <TemporarilyTitlebar />
[Sandy] Flipper{' '} <Layout.Left initialSize={348} minSize={200}>
<Button <LeftMenu>
size="small"
type="link"
icon={<CloseCircleOutlined />}
onClick={() => props.disableSandy(props.settings)}></Button>
</TemporarilyTitlebar>
<Layout.Left>
<LeftContainer>
<LeftRail /> <LeftRail />
<LeftMenu /> <div>LeftMenu</div>
</LeftContainer> </LeftMenu>
<Layout.Right>
<MainContainer> <MainContainer>
<Layout.Right initialSize={300} minSize={200}>
<MainContentWrapper>
<ContentContainer>
<TemporarilyContent /> <TemporarilyContent />
</MainContainer> </ContentContainer>
</MainContentWrapper>
<MainContentWrapper>
<ContentContainer>
<RightMenu /> <RightMenu />
</ContentContainer>
</MainContentWrapper>
</Layout.Right> </Layout.Right>
</MainContainer>
</Layout.Left> </Layout.Left>
</Layout.Top> </Layout.Top>
); );
} }
function LeftMenu() { const LeftMenu = styled(FlexRow)({
return <div>LeftMenu</div>; boxShadow: `inset -1px 0px 0px ${theme.dividerColor}`,
} height: '100%',
width: '100%',
});
const MainContainer = styled('div')({
display: 'flex',
width: '100%',
height: '100%',
background: theme.backgroundWash,
paddingRight: theme.space.middle,
});
export const ContentContainer = styled('div')({
width: '100%',
margin: 0,
padding: 0,
background: theme.backgroundDefault,
border: `1px solid ${theme.dividerColor}`,
borderRadius: theme.space.small,
boxShadow: `0px 0px 5px rgba(0, 0, 0, 0.05), 0px 0px 1px rgba(0, 0, 0, 0.05)`,
});
const MainContentWrapper = styled('div')({
height: '100%',
width: '100%',
display: 'flex',
alignItems: 'stretch',
padding: `${theme.space.middle}px 0`,
});
function RightMenu() { function RightMenu() {
return <div>RightMenu</div>; return <div>RightMenu</div>;
} }
function MainContainer({children}: any) {
return (
<div>
MainContainer
<br />
{children}
</div>
);
}
function TemporarilyContent() { function TemporarilyContent() {
return ( return (
<Container> <>
<Box> New UI for Flipper, Sandy Project! Nothing to see now. Go back to current
<AnnoucementText> Flipper
New UI for Flipper, Sandy Project! Nothing to see now. Go back to
current Flipper
</AnnoucementText>
<DatePicker /> <DatePicker />
</Box> </>
</Container>
); );
} }
export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
({settingsState}) => ({settings: settingsState}),
(dispatch) => ({
disableSandy: (settings: Settings) => {
console.log(settings);
dispatch(updateSettings({...settings, enableSandy: false}));
},
}),
)(SandyApp);

View File

@@ -0,0 +1,65 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import React from 'react';
import {connect} from 'react-redux';
import {State as Store} from '../reducers';
import {Settings, updateSettings} from '../reducers/settings';
import {styled, colors} from 'flipper';
import {Button} from 'antd';
import {CloseCircleOutlined} from '@ant-design/icons';
type StateFromProps = {settings: Settings};
type DispatchFromProps = {disableSandy: (settings: Settings) => void};
type OwnProps = {};
type Props = StateFromProps & DispatchFromProps & OwnProps;
// This component should be dropped, and insetTitlebar should be removed from Electron startup once Sandy is the default
const TemporarilyTitlebarContainer = styled('div')<{focused?: boolean}>(
({focused}) => ({
textAlign: 'center',
userSelect: 'none',
height: '38px',
lineHeight: '38px',
fontSize: '10pt',
color: colors.macOSTitleBarIcon,
background: true
? `linear-gradient(to bottom, ${colors.macOSTitleBarBackgroundTop} 0%, ${colors.macOSTitleBarBackgroundBottom} 100%)`
: colors.macOSTitleBarBackgroundBlur,
borderBottom: `1px solid ${
focused ? colors.macOSTitleBarBorder : colors.macOSTitleBarBorderBlur
}`,
WebkitAppRegion: 'drag',
}),
);
export const TemporarilyTitlebar = connect<
StateFromProps,
DispatchFromProps,
OwnProps,
Store
>(
({settingsState}) => ({settings: settingsState}),
(dispatch) => ({
disableSandy: (settings: Settings) => {
console.log(settings);
dispatch(updateSettings({...settings, enableSandy: false}));
},
}),
)((props: Props) => (
<TemporarilyTitlebarContainer focused /*TODO: make dynamic */>
[Sandy] Flipper{' '}
<Button
size="small"
type="link"
icon={<CloseCircleOutlined />}
onClick={() => props.disableSandy(props.settings)}></Button>
</TemporarilyTitlebarContainer>
));

View File

@@ -22,4 +22,10 @@ export const theme = {
backgroundWash: 'var(--flipper-background-wash)', backgroundWash: 'var(--flipper-background-wash)',
dividerColor: 'var(--flipper-divider-color)', dividerColor: 'var(--flipper-divider-color)',
borderRadius: 'var(--flipper-border-radius)', borderRadius: 'var(--flipper-border-radius)',
space: {
// from Space component in Ant
small: 8,
middle: 16,
large: 24,
} as const,
}; };

View File

@@ -76,6 +76,7 @@ type InteractiveProps = {
style?: Object; style?: Object;
className?: string; className?: string;
children?: React.ReactNode; children?: React.ReactNode;
gutterWidth?: number;
}; };
type InteractiveState = { type InteractiveState = {
@@ -549,11 +550,12 @@ export default class Interactive extends React.Component<
const x = event.clientX - offsetLeft; const x = event.clientX - offsetLeft;
const y = event.clientY - offsetTop; const y = event.clientY - offsetTop;
const atTop: boolean = y <= WINDOW_CURSOR_BOUNDARY; const gutterWidth = this.props.gutterWidth || WINDOW_CURSOR_BOUNDARY;
const atBottom: boolean = y >= height - WINDOW_CURSOR_BOUNDARY; const atTop: boolean = y <= gutterWidth;
const atBottom: boolean = y >= height - gutterWidth;
const atLeft: boolean = x <= WINDOW_CURSOR_BOUNDARY; const atLeft: boolean = x <= gutterWidth;
const atRight: boolean = x >= width - WINDOW_CURSOR_BOUNDARY; const atRight: boolean = x >= width - gutterWidth;
return { return {
bottom: canResize.bottom === true && atBottom, bottom: canResize.bottom === true && atBottom,

View File

@@ -9,9 +9,20 @@
import React from 'react'; import React from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import {Sidebar} from '..';
type Props = { type Props = {
/**
* If set, the dynamically sized pane will get scrollbars when needed
*/
scrollable?: boolean; scrollable?: boolean;
/**
* If set, the 'fixed' child will no longer be sized based on it's own dimensions,
* but rather it will be possible to resize it
*/
initialSize?: number;
minSize?: number;
children: [React.ReactNode, React.ReactNode]; children: [React.ReactNode, React.ReactNode];
}; };
@@ -42,15 +53,34 @@ const Container = styled('div')<{horizontal: boolean}>(({horizontal}) => ({
Container.displayName = 'Layout:Container'; Container.displayName = 'Layout:Container';
function renderLayout( function renderLayout(
{children, scrollable}: Props, {children, scrollable, initialSize, minSize}: Props,
horizontal: boolean, horizontal: boolean,
reverse: boolean, reverse: boolean,
) { ) {
if (children.length !== 2) { if (children.length !== 2) {
throw new Error('Layout expects exactly 2 children'); throw new Error('Layout expects exactly 2 children');
} }
const fixedElement = ( const fixedChild = reverse ? children[1] : children[0];
<FixedContainer>{reverse ? children[1] : children[0]}</FixedContainer>
const fixedElement =
initialSize === undefined ? (
<FixedContainer>{fixedChild}</FixedContainer>
) : horizontal ? (
<Sidebar
position={reverse ? 'right' : 'left'}
width={initialSize}
minWidth={minSize}
gutter>
{fixedChild}
</Sidebar>
) : (
<Sidebar
position={reverse ? 'bottom' : 'top'}
height={initialSize}
minHeight={minSize}
gutter>
{fixedChild}
</Sidebar>
); );
const dynamicElement = ( const dynamicElement = (
<ScrollContainer scrollable={!!scrollable}> <ScrollContainer scrollable={!!scrollable}>
@@ -77,6 +107,8 @@ function renderLayout(
* The main area will be scrollable by default, but if multiple containers are nested, * The main area will be scrollable by default, but if multiple containers are nested,
* scrolling can be disabled by using `scrollable={false}` * scrolling can be disabled by using `scrollable={false}`
* *
* If initialSize is set, the fixed container will be made resizable
*
* Use Layout.Top / Right / Bottom / Left to indicate where the fixed element should live. * Use Layout.Top / Right / Bottom / Left to indicate where the fixed element should live.
*/ */
const Layout: Record<'Left' | 'Right' | 'Top' | 'Bottom', React.FC<Props>> = { const Layout: Record<'Left' | 'Right' | 'Top' | 'Bottom', React.FC<Props>> = {

View File

@@ -10,7 +10,7 @@
import Interactive from './Interactive'; import Interactive from './Interactive';
import FlexColumn from './FlexColumn'; import FlexColumn from './FlexColumn';
import {colors} from './colors'; import {colors} from './colors';
import {Component} from 'react'; import {Component, ReactNode} from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { import {
BackgroundClipProperty, BackgroundClipProperty,
@@ -19,6 +19,9 @@ import {
BackgroundColorProperty, BackgroundColorProperty,
} from 'csstype'; } from 'csstype';
import React from 'react'; import React from 'react';
import FlexRow from './FlexRow';
import {MoreOutlined} from '@ant-design/icons';
import {theme} from '../../sandy-chrome/theme';
const SidebarInteractiveContainer = styled(Interactive)({ const SidebarInteractiveContainer = styled(Interactive)({
flex: 'none', flex: 'none',
@@ -31,12 +34,18 @@ const SidebarContainer = styled(FlexColumn)<{
position: 'right' | 'top' | 'left' | 'bottom'; position: 'right' | 'top' | 'left' | 'bottom';
backgroundColor?: BackgroundClipProperty; backgroundColor?: BackgroundClipProperty;
overflow?: boolean; overflow?: boolean;
unstyled?: boolean;
}>((props) => ({ }>((props) => ({
backgroundColor: props.backgroundColor || colors.macOSTitleBarBackgroundBlur, ...(props.unstyled
? undefined
: {
backgroundColor:
props.backgroundColor || colors.macOSTitleBarBackgroundBlur,
borderLeft: props.position === 'right' ? '1px solid #b3b3b3' : 'none', borderLeft: props.position === 'right' ? '1px solid #b3b3b3' : 'none',
borderTop: props.position === 'bottom' ? '1px solid #b3b3b3' : 'none', borderTop: props.position === 'bottom' ? '1px solid #b3b3b3' : 'none',
borderRight: props.position === 'left' ? '1px solid #b3b3b3' : 'none', borderRight: props.position === 'left' ? '1px solid #b3b3b3' : 'none',
borderBottom: props.position === 'top' ? '1px solid #b3b3b3' : 'none', borderBottom: props.position === 'top' ? '1px solid #b3b3b3' : 'none',
}),
height: '100%', height: '100%',
overflowX: 'hidden', overflowX: 'hidden',
overflowY: 'auto', overflowY: 'auto',
@@ -93,6 +102,10 @@ type SidebarProps = {
* Class name to customise styling. * Class name to customise styling.
*/ */
className?: string; className?: string;
/**
* use a Sandy themed large gutter
*/
gutter?: boolean;
}; };
type SidebarState = { type SidebarState = {
@@ -138,7 +151,7 @@ export default class Sidebar extends Component<SidebarProps, SidebarState> {
}; };
render() { render() {
const {backgroundColor, onResize, position, children} = this.props; const {backgroundColor, onResize, position, children, gutter} = this.props;
let height: number | undefined; let height: number | undefined;
let minHeight: number | undefined; let minHeight: number | undefined;
let maxHeight: number | undefined; let maxHeight: number | undefined;
@@ -183,11 +196,58 @@ export default class Sidebar extends Component<SidebarProps, SidebarState> {
maxHeight={maxHeight} maxHeight={maxHeight}
height={!horizontal ? (onResize ? height : this.state.height) : '100%'} height={!horizontal ? (onResize ? height : this.state.height) : '100%'}
resizable={resizable} resizable={resizable}
onResize={this.onResize}> onResize={this.onResize}
<SidebarContainer position={position} backgroundColor={backgroundColor}> gutterWidth={gutter ? theme.space.middle : undefined}>
{children} <SidebarContainer
position={position}
backgroundColor={backgroundColor}
unstyled={gutter}>
{gutter ? (
<GutterWrapper position={position}>{children}</GutterWrapper>
) : (
children
)}
</SidebarContainer> </SidebarContainer>
</SidebarInteractiveContainer> </SidebarInteractiveContainer>
); );
} }
} }
const GutterWrapper = ({
position,
children,
}: {
position: SidebarPosition;
children: ReactNode;
}) => {
return position === 'right' ? (
<FlexRow grow>
<VerticalGutter />
{children}
</FlexRow>
) : (
<FlexRow grow>
{children}
<VerticalGutter />
</FlexRow>
); // TODO: support top / bottom
};
const VerticalGutterContainer = styled('div')({
width: theme.space.middle,
height: '100%',
color: theme.textColorPlaceholder,
fontSize: '16px',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
background: theme.backgroundWash,
':hover': {
background: theme.dividerColor,
},
});
const VerticalGutter = () => (
<VerticalGutterContainer>
<MoreOutlined />
</VerticalGutterContainer>
);

View File

@@ -144,10 +144,14 @@ function compileRegex(s: string): RegExp | null {
} }
} }
const Searchable = ( /**
* Higher-order-component that allows adding a searchbar on top of the wrapped
* component. See SearchableManagedTable for usage with a table.
*/
export default function Searchable(
Component: React.ComponentType<any>, Component: React.ComponentType<any>,
): React.ComponentType<any> => ): React.ComponentType<any> {
class extends PureComponent<Props, State> { return class extends PureComponent<Props, State> {
static displayName = `Searchable(${Component.displayName})`; static displayName = `Searchable(${Component.displayName})`;
static defaultProps = { static defaultProps = {
@@ -544,9 +548,4 @@ const Searchable = (
); );
} }
}; };
}
/**
* Higher-order-component that allows adding a searchbar on top of the wrapped
* component. See SearchableManagedTable for usage with a table.
*/
export default Searchable;

View File

@@ -176,5 +176,6 @@ export {default as Info} from './components/Info';
export {default as Bordered} from './components/Bordered'; export {default as Bordered} from './components/Bordered';
export {default as AlternatingRows} from './components/AlternatingRows'; export {default as AlternatingRows} from './components/AlternatingRows';
export {default as Layout} from './components/Layout'; export {default as Layout} from './components/Layout';
export {default as Scrollable} from './components/Scrollable'; export {default as Scrollable} from './components/Scrollable';
export * from './components/Highlight'; export * from './components/Highlight';

View File

@@ -23,7 +23,7 @@
/** /**
This section maps theme colors to CSS variables so that thye can be This section maps theme colors to CSS variables so that thye can be
used in styled components, see sandyColors.tsx used in styled components, see theme.tsx
*/ */
:root { :root {