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

View File

@@ -8,135 +8,84 @@
*/
import React from 'react';
import {connect} from 'react-redux';
import {State as Store} from '../reducers';
import {Settings, updateSettings} from '../reducers/settings';
import {styled, FlexColumn, colors, Text} from 'flipper';
import {DatePicker, Button} from 'antd';
import {Layout, FlexBox} from '../ui';
import {styled} from 'flipper';
import {DatePicker} from 'antd';
import {Layout, FlexRow} from '../ui';
import {theme} from './theme';
import {LeftRail} from './LeftRail';
import {CloseCircleOutlined} from '@ant-design/icons';
import {TemporarilyTitlebar} from './TemporarilyTitlebar';
type StateFromProps = {settings: Settings};
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) {
export function SandyApp() {
return (
<Layout.Top>
<TemporarilyTitlebar focused /*TODO: make dynamic */>
[Sandy] Flipper{' '}
<Button
size="small"
type="link"
icon={<CloseCircleOutlined />}
onClick={() => props.disableSandy(props.settings)}></Button>
</TemporarilyTitlebar>
<Layout.Left>
<LeftContainer>
<TemporarilyTitlebar />
<Layout.Left initialSize={348} minSize={200}>
<LeftMenu>
<LeftRail />
<LeftMenu />
</LeftContainer>
<Layout.Right>
<MainContainer>
<TemporarilyContent />
</MainContainer>
<RightMenu />
</Layout.Right>
<div>LeftMenu</div>
</LeftMenu>
<MainContainer>
<Layout.Right initialSize={300} minSize={200}>
<MainContentWrapper>
<ContentContainer>
<TemporarilyContent />
</ContentContainer>
</MainContentWrapper>
<MainContentWrapper>
<ContentContainer>
<RightMenu />
</ContentContainer>
</MainContentWrapper>
</Layout.Right>
</MainContainer>
</Layout.Left>
</Layout.Top>
);
}
function LeftMenu() {
return <div>LeftMenu</div>;
}
const LeftMenu = styled(FlexRow)({
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() {
return <div>RightMenu</div>;
}
function MainContainer({children}: any) {
return (
<div>
MainContainer
<br />
{children}
</div>
);
}
function TemporarilyContent() {
return (
<Container>
<Box>
<AnnoucementText>
New UI for Flipper, Sandy Project! Nothing to see now. Go back to
current Flipper
</AnnoucementText>
<DatePicker />
</Box>
</Container>
<>
New UI for Flipper, Sandy Project! Nothing to see now. Go back to current
Flipper
<DatePicker />
</>
);
}
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)',
dividerColor: 'var(--flipper-divider-color)',
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;
className?: string;
children?: React.ReactNode;
gutterWidth?: number;
};
type InteractiveState = {
@@ -549,11 +550,12 @@ export default class Interactive extends React.Component<
const x = event.clientX - offsetLeft;
const y = event.clientY - offsetTop;
const atTop: boolean = y <= WINDOW_CURSOR_BOUNDARY;
const atBottom: boolean = y >= height - WINDOW_CURSOR_BOUNDARY;
const gutterWidth = this.props.gutterWidth || WINDOW_CURSOR_BOUNDARY;
const atTop: boolean = y <= gutterWidth;
const atBottom: boolean = y >= height - gutterWidth;
const atLeft: boolean = x <= WINDOW_CURSOR_BOUNDARY;
const atRight: boolean = x >= width - WINDOW_CURSOR_BOUNDARY;
const atLeft: boolean = x <= gutterWidth;
const atRight: boolean = x >= width - gutterWidth;
return {
bottom: canResize.bottom === true && atBottom,

View File

@@ -9,9 +9,20 @@
import React from 'react';
import styled from '@emotion/styled';
import {Sidebar} from '..';
type Props = {
/**
* If set, the dynamically sized pane will get scrollbars when needed
*/
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];
};
@@ -42,16 +53,35 @@ const Container = styled('div')<{horizontal: boolean}>(({horizontal}) => ({
Container.displayName = 'Layout:Container';
function renderLayout(
{children, scrollable}: Props,
{children, scrollable, initialSize, minSize}: Props,
horizontal: boolean,
reverse: boolean,
) {
if (children.length !== 2) {
throw new Error('Layout expects exactly 2 children');
}
const fixedElement = (
<FixedContainer>{reverse ? children[1] : children[0]}</FixedContainer>
);
const fixedChild = reverse ? children[1] : children[0];
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 = (
<ScrollContainer scrollable={!!scrollable}>
{reverse ? children[0] : children[1]}
@@ -77,6 +107,8 @@ function renderLayout(
* The main area will be scrollable by default, but if multiple containers are nested,
* 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.
*/
const Layout: Record<'Left' | 'Right' | 'Top' | 'Bottom', React.FC<Props>> = {

View File

@@ -10,7 +10,7 @@
import Interactive from './Interactive';
import FlexColumn from './FlexColumn';
import {colors} from './colors';
import {Component} from 'react';
import {Component, ReactNode} from 'react';
import styled from '@emotion/styled';
import {
BackgroundClipProperty,
@@ -19,6 +19,9 @@ import {
BackgroundColorProperty,
} from 'csstype';
import React from 'react';
import FlexRow from './FlexRow';
import {MoreOutlined} from '@ant-design/icons';
import {theme} from '../../sandy-chrome/theme';
const SidebarInteractiveContainer = styled(Interactive)({
flex: 'none',
@@ -31,12 +34,18 @@ const SidebarContainer = styled(FlexColumn)<{
position: 'right' | 'top' | 'left' | 'bottom';
backgroundColor?: BackgroundClipProperty;
overflow?: boolean;
unstyled?: boolean;
}>((props) => ({
backgroundColor: props.backgroundColor || colors.macOSTitleBarBackgroundBlur,
borderLeft: props.position === 'right' ? '1px solid #b3b3b3' : 'none',
borderTop: props.position === 'bottom' ? '1px solid #b3b3b3' : 'none',
borderRight: props.position === 'left' ? '1px solid #b3b3b3' : 'none',
borderBottom: props.position === 'top' ? '1px solid #b3b3b3' : 'none',
...(props.unstyled
? undefined
: {
backgroundColor:
props.backgroundColor || colors.macOSTitleBarBackgroundBlur,
borderLeft: props.position === 'right' ? '1px solid #b3b3b3' : 'none',
borderTop: props.position === 'bottom' ? '1px solid #b3b3b3' : 'none',
borderRight: props.position === 'left' ? '1px solid #b3b3b3' : 'none',
borderBottom: props.position === 'top' ? '1px solid #b3b3b3' : 'none',
}),
height: '100%',
overflowX: 'hidden',
overflowY: 'auto',
@@ -93,6 +102,10 @@ type SidebarProps = {
* Class name to customise styling.
*/
className?: string;
/**
* use a Sandy themed large gutter
*/
gutter?: boolean;
};
type SidebarState = {
@@ -138,7 +151,7 @@ export default class Sidebar extends Component<SidebarProps, SidebarState> {
};
render() {
const {backgroundColor, onResize, position, children} = this.props;
const {backgroundColor, onResize, position, children, gutter} = this.props;
let height: number | undefined;
let minHeight: number | undefined;
let maxHeight: number | undefined;
@@ -183,11 +196,58 @@ export default class Sidebar extends Component<SidebarProps, SidebarState> {
maxHeight={maxHeight}
height={!horizontal ? (onResize ? height : this.state.height) : '100%'}
resizable={resizable}
onResize={this.onResize}>
<SidebarContainer position={position} backgroundColor={backgroundColor}>
{children}
onResize={this.onResize}
gutterWidth={gutter ? theme.space.middle : undefined}>
<SidebarContainer
position={position}
backgroundColor={backgroundColor}
unstyled={gutter}>
{gutter ? (
<GutterWrapper position={position}>{children}</GutterWrapper>
) : (
children
)}
</SidebarContainer>
</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>,
): React.ComponentType<any> =>
class extends PureComponent<Props, State> {
): React.ComponentType<any> {
return class extends PureComponent<Props, State> {
static displayName = `Searchable(${Component.displayName})`;
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 AlternatingRows} from './components/AlternatingRows';
export {default as Layout} from './components/Layout';
export {default as Scrollable} from './components/Scrollable';
export * from './components/Highlight';

View File

@@ -23,7 +23,7 @@
/**
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 {