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:
committed by
Facebook GitHub Bot
parent
0100224833
commit
95638af321
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
<div>LeftMenu</div>
|
||||
</LeftMenu>
|
||||
<MainContainer>
|
||||
<Layout.Right initialSize={300} minSize={200}>
|
||||
<MainContentWrapper>
|
||||
<ContentContainer>
|
||||
<TemporarilyContent />
|
||||
</MainContainer>
|
||||
</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>
|
||||
<>
|
||||
New UI for Flipper, Sandy Project! Nothing to see now. Go back to current
|
||||
Flipper
|
||||
<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);
|
||||
|
||||
65
desktop/app/src/sandy-chrome/TemporarilyTitlebar.tsx
Normal file
65
desktop/app/src/sandy-chrome/TemporarilyTitlebar.tsx
Normal 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>
|
||||
));
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,15 +53,34 @@ 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}>
|
||||
@@ -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>> = {
|
||||
|
||||
@@ -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,
|
||||
...(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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user