Introduce Design System Page / Design system improvements

Summary:
This diff introduces:

- ScrollContainer
- Make sure Ant Link components always open URLs externally, to avoid users needing to use electron api's
- Introduce a design systems page where people can find docs on how to organise sandy layout, and it also provides a convenient way to test layout mechanisms.
- Fixed several layout bugs found as a result of adding the design system page

In next diff:
- more convenience around organizing paddings
- making the design system accessible from the menu

Reviewed By: cekkaewnumchai

Differential Revision: D23930274

fbshipit-source-id: 4aab058d15b3391287e0e32513a5d83831448857
This commit is contained in:
Michel Weststrate
2020-10-01 05:32:07 -07:00
committed by Facebook GitHub Bot
parent 7358711e07
commit e8370e9fc1
15 changed files with 908 additions and 327 deletions

View File

@@ -7,8 +7,98 @@
* @format
*/
import React from 'react';
import React, {CSSProperties} from 'react';
import styled from '@emotion/styled';
import {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;
padding?: CSSProperties['padding'];
borderBottom?: boolean;
borderTop?: boolean;
borderRight?: boolean;
borderLeft?: boolean;
bordered?: boolean;
rounded?: boolean;
padded?: boolean;
};
const Container = styled.div<ContainerProps>(
({
bordered,
borderBottom,
borderLeft,
borderRight,
borderTop,
padding,
rounded,
padded,
}) => ({
display: 'flex',
flexDirection: 'column',
padding: padded ? theme.space.small : padding,
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({
flex: 1,
position: 'relative',
overflow: 'auto',
});
const ScrollChild = styled.div({
position: 'absolute',
minHeight: '100%',
minWidth: '100%',
});
const ScrollContainer = ({
children,
...rest
}: React.HTMLAttributes<HTMLDivElement>) =>
(
<ScrollParent {...rest}>
<ScrollChild>{children}</ScrollChild>
</ScrollParent>
) as any;
type DistributionProps = ContainerProps & {
/**
* Gab between individual items
*/
gap?: CSSProperties['gap'];
/**
* 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,
alignItems: center ? 'center' : 'stretch',
}));
const Vertical = styled(Container)<DistributionProps>(({gap, center}) => ({
display: 'flex',
flexDirection: 'column',
gap,
alignItems: center ? 'center' : 'stretch',
}));
type SplitLayoutProps = {
/**
@@ -18,88 +108,9 @@ type SplitLayoutProps = {
/**
* 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];
};
const FixedContainer = styled('div')({
flex: 'none',
height: 'auto',
overflow: 'hidden',
});
FixedContainer.displayName = 'Layout:FixedContainer';
const ScrollContainer = styled('div')<{scrollable: boolean}>(
({scrollable}) => ({
overflow: scrollable ? 'auto' : 'hidden',
flex: 'auto',
display: 'flex',
}),
);
ScrollContainer.displayName = 'Layout:ScrollContainer';
const Container = styled('div')<{horizontal: boolean; center?: boolean}>(
({horizontal, center}) => ({
display: 'flex',
flex: 'auto',
flexDirection: horizontal ? 'row' : 'column',
height: '100%',
width: '100%',
overflow: 'hidden',
alignItems: center ? 'center' : undefined,
}),
);
Container.displayName = 'Layout:Container';
function renderLayout(
{children, scrollable, center}: SplitLayoutProps,
horizontal: boolean,
reverse: boolean,
) {
if (children.length !== 2) {
throw new Error('Layout expects exactly 2 children');
}
const fixedChild = reverse ? children[1] : children[0];
const fixedElement = <FixedContainer>{fixedChild}</FixedContainer>;
const dynamicElement = (
<ScrollContainer scrollable={!!scrollable}>
{reverse ? children[0] : children[1]}
</ScrollContainer>
);
return reverse ? (
<Container horizontal={horizontal} center={center}>
{dynamicElement}
{fixedElement}
</Container>
) : (
<Container horizontal={horizontal} center={center}>
{fixedElement}
{dynamicElement}
</Container>
);
}
type DistributionProps = {
/**
* Gab between individual items
*/
gap?: number;
/**
* If set, items will be aligned in the center, if false (the default) items will be stretched.
*/
center?: boolean;
/**
* If set, the layout will fill out to maximum width
*/
fillx?: boolean;
/**
* If set, the layout will fill out to maximum height
*/
filly?: boolean;
};
/**
* The Layout component divides all available screenspace over two components:
* A fixed top (or left) component, and all remaining space to a bottom component.
@@ -111,43 +122,90 @@ type DistributionProps = {
*
* Use Layout.Top / Right / Bottom / Left to indicate where the fixed element should live.
*/
const Layout: Record<
'Left' | 'Right' | 'Top' | 'Bottom',
React.FC<SplitLayoutProps>
> &
Record<'Horizontal' | 'Vertical', React.FC<DistributionProps>> = {
Top(props) {
return renderLayout(props, false, false);
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"
flex1={0}
flex2={1}>
{child1}
{child2}
</SandySplitContainer>
);
},
Bottom(props) {
return renderLayout(props, false, true);
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"
flex1={1}
flex2={0}>
{child1}
{child2}
</SandySplitContainer>
);
},
Left(props) {
return renderLayout(props, true, false);
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" flex1={0} flex2={1}>
{child1}
{child2}
</SandySplitContainer>
);
},
Right(props) {
return renderLayout(props, true, true);
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" flex1={1} flex2={0}>
{child1}
{child2}
</SandySplitContainer>
);
},
Horizontal: styled.div<DistributionProps>(({gap, center, fillx, filly}) => ({
display: 'flex',
flexDirection: 'row',
gap,
alignItems: center ? 'center' : 'stretch',
width: fillx ? '100%' : undefined,
height: filly ? '100%' : undefined,
})),
Vertical: styled.div<DistributionProps>(({gap, center, fillx, filly}) => ({
display: 'flex',
flexDirection: 'column',
gap,
alignItems: center ? 'center' : 'stretch',
width: fillx ? '100%' : undefined,
height: filly ? '100%' : undefined,
})),
Container,
ScrollContainer,
Horizontal,
Vertical,
};
Object.keys(Layout).forEach((key) => {
(Layout as any)[key].displayName = `Layout.${key}`;
});
const SandySplitContainer = styled.div<{
flex1: number;
flex2: number;
flexDirection: CSSProperties['flexDirection'];
}>((props) => ({
display: 'flex',
flex: 1,
flexDirection: props.flexDirection,
alignItems: 'stretch',
'> :first-child': {
flexGrow: props.flex1,
flexShrink: props.flex1,
},
'> :last-child': {
flexGrow: props.flex2,
flexShrink: props.flex2,
},
}));
export default Layout;