Files
flipper/desktop/app/src/ui/components/Layout.tsx
Michel Weststrate 29e528115d Support 1 dimension scrolling
Summary: Added `axis` property to scroll container, to make scroll work in one direction, and fill out the other one.

Reviewed By: cekkaewnumchai

Differential Revision: D24390943

fbshipit-source-id: 5767e753edfb947f43b3998e10d6cebf57f9b53b
2020-10-20 03:24:47 -07:00

234 lines
6.2 KiB
TypeScript

/**
* 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, {CSSProperties} from 'react';
import styled from '@emotion/styled';
import {
normalizePadding,
normalizeSpace,
PaddingProps,
Spacing,
theme,
} from '../../sandy-chrome/theme';
import {useIsSandy} from '../../sandy-chrome/SandyContext';
import {renderLayout} from './LegacyLayout';
type ContainerProps = {
children?: React.ReactNode;
className?: string;
style?: React.CSSProperties;
borderBottom?: boolean;
borderTop?: boolean;
borderRight?: boolean;
borderLeft?: boolean;
bordered?: boolean;
rounded?: boolean;
width?: number;
height?: number;
} & PaddingProps;
const Container = styled.div<ContainerProps>(
({
bordered,
borderBottom,
borderLeft,
borderRight,
borderTop,
rounded,
width,
height,
...rest
}) => ({
boxSizing: 'border-box',
minWidth: `0`, // ensures the Container can shrink smaller than it's largest
width,
height,
display: 'flex',
flexDirection: 'column',
padding: normalizePadding(rest),
borderRadius: rounded ? theme.containerBorderRadius : undefined,
flex: 1,
borderStyle: 'solid',
borderColor: theme.dividerColor,
borderWidth: bordered
? 1
: `${borderTop ? 1 : 0}px ${borderRight ? 1 : 0}px ${
borderBottom ? 1 : 0
}px ${borderLeft ? 1 : 0}px`,
}),
);
const ScrollParent = styled.div<{axis?: ScrollAxis}>(({axis}) => ({
flex: 1,
boxSizing: 'border-box',
position: 'relative',
overflowX: axis === 'y' ? 'hidden' : 'auto',
overflowY: axis === 'x' ? 'hidden' : 'auto',
}));
const ScrollChild = styled.div<{axis?: ScrollAxis}>(({axis}) => ({
position: 'absolute',
minHeight: '100%',
minWidth: '100%',
maxWidth: axis === 'y' ? '100%' : undefined,
maxHeight: axis === 'x' ? '100%' : undefined,
}));
type ScrollAxis = 'x' | 'y' | 'both';
const ScrollContainer = ({
children,
horizontal,
vertical,
...rest
}: React.HTMLAttributes<HTMLDivElement> & {
horizontal?: boolean;
vertical?: boolean;
}) => {
const axis =
horizontal && !vertical ? 'x' : !horizontal && vertical ? 'y' : 'both';
return (
<ScrollParent axis={axis} {...rest}>
<ScrollChild axis={axis}>{children}</ScrollChild>
</ScrollParent>
) as any;
};
type DistributionProps = ContainerProps & {
/**
* Gab between individual items
*/
gap?: Spacing;
/**
* If set, items will be aligned in the center, if false (the default) items will be stretched.
*/
center?: boolean;
};
const Horizontal = styled(Container)<DistributionProps>(({gap, center}) => ({
display: 'flex',
flexDirection: 'row',
gap: normalizeSpace(gap, theme.space.small),
alignItems: center ? 'center' : 'stretch',
minWidth: 'auto', // corrects 0 on Container
}));
const Vertical = styled(Container)<DistributionProps>(({gap, center}) => ({
display: 'flex',
flexDirection: 'column',
gap: normalizeSpace(gap, theme.space.small),
alignItems: center ? 'center' : 'stretch',
}));
type SplitLayoutProps = {
/**
* If set, the dynamically sized pane will get scrollbars when needed
*/
scrollable?: boolean;
/**
* If set, items will be centered over the orthogonal direction, if false (the default) items will be stretched.
*/
center?: boolean;
children: [React.ReactNode, React.ReactNode];
};
/**
* The Layout component divides all available screenspace over two components:
* A fixed top (or left) component, and all remaining space to a bottom component.
*
* 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 = {
Top(props: SplitLayoutProps) {
const isSandy = useIsSandy();
if (!isSandy) return renderLayout(props, false, false);
let [child1, child2] = props.children;
if (props.scrollable) child2 = <ScrollContainer>{child2}</ScrollContainer>;
return (
<SandySplitContainer {...props} flexDirection="column" grow={2}>
{child1}
{child2}
</SandySplitContainer>
);
},
Bottom(props: SplitLayoutProps) {
const isSandy = useIsSandy();
if (!isSandy) return renderLayout(props, false, true);
let [child1, child2] = props.children;
if (props.scrollable) child1 = <ScrollContainer>{child1}</ScrollContainer>;
return (
<SandySplitContainer {...props} flexDirection="column" grow={1}>
{child1}
{child2}
</SandySplitContainer>
);
},
Left(props: SplitLayoutProps) {
const isSandy = useIsSandy();
if (!isSandy) return renderLayout(props, true, false);
let [child1, child2] = props.children;
if (props.scrollable) child2 = <ScrollContainer>{child2}</ScrollContainer>;
return (
<SandySplitContainer {...props} flexDirection="row" grow={2}>
{child1}
{child2}
</SandySplitContainer>
);
},
Right(props: SplitLayoutProps) {
const isSandy = useIsSandy();
if (!isSandy) return renderLayout(props, true, true);
let [child1, child2] = props.children;
if (props.scrollable) child1 = <ScrollContainer>{child1}</ScrollContainer>;
return (
<SandySplitContainer {...props} flexDirection="row" grow={1}>
{child1}
{child2}
</SandySplitContainer>
);
},
Container,
ScrollContainer,
Horizontal,
Vertical,
};
Object.keys(Layout).forEach((key) => {
(Layout as any)[key].displayName = `Layout.${key}`;
});
const SandySplitContainer = styled.div<{
grow: 1 | 2;
center?: boolean;
flexDirection: CSSProperties['flexDirection'];
}>((props) => ({
boxSizing: 'border-box',
display: 'flex',
flex: 1,
flexDirection: props.flexDirection,
alignItems: props.center ? 'center' : 'stretch',
overflow: 'hidden',
'> :first-child': {
flex: props.grow === 1 ? growStyle : fixedStyle,
},
'> :last-child': {
flex: props.grow === 2 ? growStyle : fixedStyle,
},
}));
const fixedStyle = `0 0 auto`;
const growStyle = `1 0 0`;
export default Layout;