Move Sidebar to flipper-plugin
Summary: This diff deprecates the Sidebar concept, and copies the implementation to Sandy (tried moving first, but since existing plugins use the Sidebar in non-flex (Layout) contexts, that the layout of several plugins, so rather deprecated the old implementation. Instead of exposing `Sidebar` explicitly, one can now put the `resizable` flag on a Layout.Top/Left/Bottom/Right, which makes building layouts even simpler, see demo. The gutter logic was moved to the new implementation, since that was only used by the Sandy chrome anyway. Changelog: Layout.Top / Left / Bottom / Right now support a resizable option Reviewed By: passy Differential Revision: D27233899 fbshipit-source-id: fbbdeb2ebf30d49d0837705a00ea86bb07fc2ba2
This commit is contained in:
committed by
Facebook GitHub Bot
parent
0bf786544a
commit
dd1f2fdeaa
@@ -55,6 +55,7 @@ export {
|
||||
NuxManagerContext as _NuxManagerContext,
|
||||
createNuxManager as _createNuxManager,
|
||||
} from './ui/NUX';
|
||||
export {Sidebar as _Sidebar} from './ui/Sidebar';
|
||||
|
||||
export {renderReactRoot} from './utils/renderReactRoot';
|
||||
export {
|
||||
|
||||
@@ -141,14 +141,79 @@ type SplitLayoutProps = {
|
||||
gap?: Spacing;
|
||||
children: [React.ReactNode, React.ReactNode];
|
||||
style?: CSSProperties;
|
||||
};
|
||||
} & SplitHorizontalResizableProps &
|
||||
SplitVerticalResizableProps;
|
||||
|
||||
type SplitHorizontalResizableProps =
|
||||
| {
|
||||
resizable: true;
|
||||
/**
|
||||
* Width describes the width of the resizable pane. To set a global width use the style attribute.
|
||||
*/
|
||||
width?: number;
|
||||
minWidth?: number;
|
||||
maxWidth?: number;
|
||||
}
|
||||
| {};
|
||||
|
||||
type SplitVerticalResizableProps =
|
||||
| {
|
||||
resizable: true;
|
||||
/**
|
||||
* Width describes the width of the resizable pane. To set a global width use the style attribute.
|
||||
*/
|
||||
height?: number;
|
||||
minHeight?: number;
|
||||
maxHeight?: number;
|
||||
}
|
||||
| {};
|
||||
|
||||
function renderSplitLayout(
|
||||
props: SplitLayoutProps,
|
||||
direction: 'column' | 'row',
|
||||
grow: 1 | 2,
|
||||
) {
|
||||
const [child1, child2] = props.children;
|
||||
let [child1, child2] = props.children;
|
||||
if ('resizable' in props && props.resizable) {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
minHeight,
|
||||
minWidth,
|
||||
maxHeight,
|
||||
maxWidth,
|
||||
} = props as any;
|
||||
const sizeProps =
|
||||
direction === 'column'
|
||||
? ({
|
||||
minHeight,
|
||||
height: height ?? 300,
|
||||
maxHeight,
|
||||
} as const)
|
||||
: ({
|
||||
minWidth,
|
||||
width: width ?? 300,
|
||||
maxWidth,
|
||||
} as const);
|
||||
const Sidebar = require('./Sidebar').Sidebar;
|
||||
if (grow === 2) {
|
||||
child1 = (
|
||||
<Sidebar
|
||||
position={direction === 'column' ? 'top' : 'left'}
|
||||
{...sizeProps}>
|
||||
{child1}
|
||||
</Sidebar>
|
||||
);
|
||||
} else {
|
||||
child2 = (
|
||||
<Sidebar
|
||||
position={direction === 'column' ? 'bottom' : 'right'}
|
||||
{...sizeProps}>
|
||||
{child2}
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<SandySplitContainer {...props} flexDirection={direction} grow={grow}>
|
||||
{child1}
|
||||
@@ -169,16 +234,16 @@ function renderSplitLayout(
|
||||
* Use Layout.Top / Right / Bottom / Left to indicate where the fixed element should live.
|
||||
*/
|
||||
export const Layout = {
|
||||
Top(props: SplitLayoutProps) {
|
||||
Top(props: SplitLayoutProps & SplitVerticalResizableProps) {
|
||||
return renderSplitLayout(props, 'column', 2);
|
||||
},
|
||||
Bottom(props: SplitLayoutProps) {
|
||||
Bottom(props: SplitLayoutProps & SplitVerticalResizableProps) {
|
||||
return renderSplitLayout(props, 'column', 1);
|
||||
},
|
||||
Left(props: SplitLayoutProps) {
|
||||
Left(props: SplitLayoutProps & SplitHorizontalResizableProps) {
|
||||
return renderSplitLayout(props, 'row', 2);
|
||||
},
|
||||
Right(props: SplitLayoutProps) {
|
||||
Right(props: SplitLayoutProps & SplitHorizontalResizableProps) {
|
||||
return renderSplitLayout(props, 'row', 1);
|
||||
},
|
||||
Container,
|
||||
|
||||
275
desktop/flipper-plugin/src/ui/Sidebar.tsx
Normal file
275
desktop/flipper-plugin/src/ui/Sidebar.tsx
Normal file
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* 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 {Layout} from './Layout';
|
||||
import {Component, ReactNode} from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import {Property} from 'csstype';
|
||||
import React from 'react';
|
||||
import {MoreOutlined} from '@ant-design/icons';
|
||||
import {Interactive, InteractiveProps} from './Interactive';
|
||||
import {theme} from './theme';
|
||||
|
||||
const SidebarInteractiveContainer = styled(Interactive)<InteractiveProps>({
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
});
|
||||
SidebarInteractiveContainer.displayName = 'Sidebar:SidebarInteractiveContainer';
|
||||
|
||||
type SidebarPosition = 'left' | 'top' | 'right' | 'bottom';
|
||||
|
||||
const borderStyle = `1px solid ${theme.dividerColor}`;
|
||||
|
||||
const SidebarContainer = styled(Layout.Container)<{
|
||||
position: 'right' | 'top' | 'left' | 'bottom';
|
||||
overflow?: boolean;
|
||||
unstyled?: boolean;
|
||||
}>((props) => ({
|
||||
...(props.unstyled
|
||||
? undefined
|
||||
: {
|
||||
borderLeft: props.position === 'right' ? borderStyle : 'none',
|
||||
borderTop: props.position === 'bottom' ? borderStyle : 'none',
|
||||
borderRight: props.position === 'left' ? borderStyle : 'none',
|
||||
borderBottom: props.position === 'top' ? borderStyle : 'none',
|
||||
backgroundColor: theme.backgroundDefault,
|
||||
}),
|
||||
flex: 1,
|
||||
}));
|
||||
SidebarContainer.displayName = 'Sidebar:SidebarContainer';
|
||||
|
||||
type SidebarProps = {
|
||||
/**
|
||||
* Position of the sidebar.
|
||||
*/
|
||||
position: SidebarPosition;
|
||||
|
||||
/**
|
||||
* Default width of the sidebar. Only used for left/right sidebars.
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* Minimum sidebar width. Only used for left/right sidebars.
|
||||
*/
|
||||
minWidth?: number;
|
||||
/**
|
||||
* Maximum sidebar width. Only used for left/right sidebars.
|
||||
*/
|
||||
maxWidth?: number;
|
||||
|
||||
/**
|
||||
* Default height of the sidebar.
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
* Minimum sidebar height. Only used for top/bottom sidebars.
|
||||
*/
|
||||
minHeight?: number;
|
||||
/**
|
||||
* Maximum sidebar height. Only used for top/bottom sidebars.
|
||||
*/
|
||||
maxHeight?: number;
|
||||
/**
|
||||
* Callback when the sidebar size ahs changed.
|
||||
*/
|
||||
onResize?: (width: number, height: number) => void;
|
||||
/**
|
||||
* Contents of the sidebar.
|
||||
*/
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* Class name to customise styling.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* use a Sandy themed large gutter
|
||||
*/
|
||||
gutter?: boolean;
|
||||
};
|
||||
|
||||
type SidebarState = {
|
||||
width?: Property.Width<number>;
|
||||
height?: Property.Height<number>;
|
||||
userChange: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A resizable sidebar.
|
||||
*/
|
||||
export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||
constructor(props: SidebarProps, context: Object) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
userChange: false,
|
||||
width: props.width,
|
||||
height: props.height,
|
||||
};
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
position: 'left',
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(
|
||||
nextProps: SidebarProps,
|
||||
state: SidebarState,
|
||||
) {
|
||||
if (!state.userChange) {
|
||||
return {width: nextProps.width, height: nextProps.height};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
onResize = (width: number, height: number) => {
|
||||
const {onResize} = this.props;
|
||||
if (onResize) {
|
||||
onResize(width, height);
|
||||
} else {
|
||||
this.setState({userChange: true, width, height});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {onResize, position, children, gutter} = this.props;
|
||||
let height: number | undefined;
|
||||
let minHeight: number | undefined;
|
||||
let maxHeight: number | undefined;
|
||||
let width: number | undefined;
|
||||
let minWidth: number | undefined;
|
||||
let maxWidth: number | undefined;
|
||||
|
||||
const resizable: {[key: string]: boolean} = {};
|
||||
if (position === 'left') {
|
||||
resizable.right = true;
|
||||
({width, minWidth, maxWidth} = this.props);
|
||||
} else if (position === 'top') {
|
||||
resizable.bottom = true;
|
||||
({height, minHeight, maxHeight} = this.props);
|
||||
} else if (position === 'right') {
|
||||
resizable.left = true;
|
||||
({width, minWidth, maxWidth} = this.props);
|
||||
} else if (position === 'bottom') {
|
||||
resizable.top = true;
|
||||
({height, minHeight, maxHeight} = this.props);
|
||||
}
|
||||
|
||||
const horizontal = position === 'left' || position === 'right';
|
||||
const gutterWidth = gutter ? theme.space.large : 0;
|
||||
|
||||
if (horizontal) {
|
||||
width = width == null ? 200 : width;
|
||||
minWidth = (minWidth == null ? 100 : minWidth) + gutterWidth;
|
||||
maxWidth = maxWidth == null ? 600 : maxWidth;
|
||||
} else {
|
||||
height = height == null ? 200 : height;
|
||||
minHeight = minHeight == null ? 100 : minHeight;
|
||||
maxHeight = maxHeight == null ? 600 : maxHeight;
|
||||
}
|
||||
return (
|
||||
<SidebarInteractiveContainer
|
||||
className={this.props.className}
|
||||
minWidth={minWidth}
|
||||
maxWidth={maxWidth}
|
||||
width={
|
||||
horizontal
|
||||
? !children
|
||||
? gutterWidth
|
||||
: onResize
|
||||
? width
|
||||
: this.state.width
|
||||
: undefined
|
||||
}
|
||||
minHeight={minHeight}
|
||||
maxHeight={maxHeight}
|
||||
height={
|
||||
!horizontal
|
||||
? onResize
|
||||
? height
|
||||
: this.state.height
|
||||
: gutter
|
||||
? undefined
|
||||
: '100%'
|
||||
}
|
||||
resizable={resizable}
|
||||
onResize={this.onResize}
|
||||
gutterWidth={gutter ? theme.space.large : undefined}>
|
||||
<SidebarContainer position={position} unstyled={gutter}>
|
||||
{gutter ? (
|
||||
<GutterWrapper position={position}>{children}</GutterWrapper>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</SidebarContainer>
|
||||
</SidebarInteractiveContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const GutterWrapper = ({
|
||||
position,
|
||||
children,
|
||||
}: {
|
||||
position: SidebarPosition;
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
switch (position) {
|
||||
case 'right':
|
||||
return (
|
||||
<Layout.Left>
|
||||
<VerticalGutter enabled={!!children} />
|
||||
{children}
|
||||
</Layout.Left>
|
||||
);
|
||||
case 'left':
|
||||
return (
|
||||
<Layout.Right>
|
||||
{children}
|
||||
<VerticalGutter enabled={!!children} />
|
||||
</Layout.Right>
|
||||
);
|
||||
case 'bottom':
|
||||
// TODO: needs rotated styling
|
||||
return (
|
||||
<Layout.Top>
|
||||
<VerticalGutter enabled={!!children} />
|
||||
{children}
|
||||
</Layout.Top>
|
||||
);
|
||||
case 'top':
|
||||
// TODO: needs rotated styling
|
||||
return (
|
||||
<Layout.Bottom>
|
||||
{children}
|
||||
<VerticalGutter enabled={!!children} />
|
||||
</Layout.Bottom>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const VerticalGutterContainer = styled('div')<{enabled: boolean}>(
|
||||
({enabled}) => ({
|
||||
width: theme.space.large,
|
||||
minWidth: theme.space.large,
|
||||
cursor: enabled ? undefined : 'default', // hide cursor from interactive container
|
||||
color: enabled ? theme.textColorPlaceholder : theme.backgroundWash,
|
||||
fontSize: '16px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
background: theme.backgroundWash,
|
||||
':hover': {
|
||||
background: enabled ? theme.dividerColor : undefined,
|
||||
},
|
||||
}),
|
||||
);
|
||||
const VerticalGutter = ({enabled}: {enabled: boolean}) => (
|
||||
<VerticalGutterContainer enabled={enabled}>
|
||||
<MoreOutlined />
|
||||
</VerticalGutterContainer>
|
||||
);
|
||||
Reference in New Issue
Block a user