Small design system simplifications

Summary:
So far we distinguished `Layout.Container` from `Layout.Vertical`, but they did almost exactly the same, so figured to unify them, so smaller API ftw :) Normal containers put children vertical, and if you want to use horizontal, use Layout.Horzontal

Also simplified code in Layout file a little bit.

Fixed issue I split container where the main container didn't go "underneath" the fixed container

Reviewed By: cekkaewnumchai

Differential Revision: D24502547

fbshipit-source-id: 517db3692749e670cda8f0cd7cb1c807df818b4d
This commit is contained in:
Michel Weststrate
2020-10-23 06:44:04 -07:00
committed by Facebook GitHub Bot
parent 26e040ea73
commit 30f5f0b59a
13 changed files with 137 additions and 182 deletions

View File

@@ -67,8 +67,7 @@ const someText = <Text>Some text</Text>;
const demos: PreviewProps[] = [
{
title: 'Layout.Container',
description:
'Layout.Container can be used to organize the UI in regions. It takes care of paddings and borders. To arrange multiple children use one of the other Layout components. If you need a margin on this component, try to wrap it in other Layout component instead.',
description: `Layout.Container can be used to organize the UI in regions. It takes care of paddings and borders. Children will be arranged vertically. Use Layout.Horizontal instead for arranging children horizontally. If you need a margin on this component, try to wrap it in other Layout component instead.`,
props: [
['rounded', 'boolean (false)', 'Make the corners rounded'],
[
@@ -91,6 +90,16 @@ const demos: PreviewProps[] = [
'boolean (false)',
'Use a standard padding on the top side',
],
[
'gap',
'true / number (0)',
'Set the spacing between children. If just set, theme.space.small will be used.',
],
[
'center',
'boolean (false)',
'If set, all children will use their own naturally width, and they will be centered horizontally in the Container. If not set, all children will be stretched to the width of the Container.',
],
],
demos: {
'Basic container with fixed dimensions': (
@@ -112,6 +121,52 @@ const demos: PreviewProps[] = [
<div style={demoStyle.square}>child</div>
</Layout.Container>
),
'Multiple children, gap={24}': (
<Layout.Container gap={24}>
{aButton}
{someText}
{aBox}
{aDynamicBox}
</Layout.Container>
),
'Multiple children icmw. pad center gap': (
<Layout.Container pad center gap>
{aButton}
{someText}
{aBox}
{aDynamicBox}
</Layout.Container>
),
},
},
{
title: 'Layout.Horizontal',
description:
'Use this component to arrange multiple items horizontally. All vanilla Container props can be used as well.',
props: [
[
'center',
'boolean (false)',
'If set, all children will use their own height, and they will be centered vertically in the layout. If not set, all children will be stretched to the height of the layout.',
],
],
demos: {
'Basic usage, gap="large"': (
<Layout.Horizontal gap="large">
{aButton}
{someText}
{aBox}
{aDynamicBox}
</Layout.Horizontal>
),
'Using flags: pad center gap={8} (great for toolbars and such)': (
<Layout.Horizontal pad center gap={8}>
{aButton}
{someText}
{aBox}
{aDynamicBox}
</Layout.Horizontal>
),
},
},
{
@@ -139,87 +194,17 @@ const demos: PreviewProps[] = [
width: 100,
border: `2px solid ${theme.primaryColor}`,
}}>
<Layout.Vertical>
<Layout.Container>
<Text ellipsis>
This text is truncated because it is too long and scroll is
vertical only...
</Text>
{largeChild}
</Layout.Vertical>
</Layout.Container>
</Layout.ScrollContainer>
),
},
},
{
title: 'Layout.Horizontal',
description:
'Use this component to arrange multiple items horizontally. All vanilla Container props can be used as well.',
props: [
[
'gap',
Object.keys(theme.space).join(' | ') + ' | number | true',
'Set the spacing between children. For `true` theme.space.small should be used. Defaults to 0.',
],
[
'center',
'boolean (false)',
'If set, all children will use their own height, and they will be centered vertically in the layout. If not set, all children will be stretched to the height of the layout.',
],
],
demos: {
'Basic usage, gap="large"': (
<Layout.Horizontal gap="large">
{aButton}
{someText}
{aBox}
{aDynamicBox}
</Layout.Horizontal>
),
'Using flags: pad center gap={8} (great for toolbars and such)': (
<Layout.Horizontal pad center gap={8}>
{aButton}
{someText}
{aBox}
{aDynamicBox}
</Layout.Horizontal>
),
},
},
{
title: 'Layout.Vertical',
description:
'Use this component to arrange multiple items vertically. All vanilla Container props can be used as well.',
props: [
[
'gap',
'number (0)',
'Set the spacing between children. Typically theme.space.small should be used.',
],
[
'center',
'boolean (false)',
'If set, all children will use their own height, and they will be centered vertically in the layout. If not set, all children will be stretched to the height of the layout.',
],
],
demos: {
'Basic usage, gap={24}': (
<Layout.Vertical gap={24}>
{aButton}
{someText}
{aBox}
{aDynamicBox}
</Layout.Vertical>
),
'Using flags: pad center gap (great for toolbars and such)': (
<Layout.Vertical pad center gap>
{aButton}
{someText}
{aBox}
{aDynamicBox}
</Layout.Vertical>
),
},
},
{
title: 'Layout.Top|Left|Right|Bottom',
description:
@@ -300,11 +285,11 @@ const demos: PreviewProps[] = [
function ComponentPreview({title, demos, description, props}: PreviewProps) {
return (
<Card title={title} size="small" type="inner">
<Layout.Vertical gap="small">
<Layout.Container gap="small">
<Text type="secondary">{description}</Text>
<Collapse ghost>
<Collapse.Panel header="Examples" key="demos">
<Layout.Vertical gap="large">
<Layout.Container gap="large">
{Object.entries(demos).map(([name, children]) => (
<div key={name}>
<Tabs type="line">
@@ -330,7 +315,7 @@ function ComponentPreview({title, demos, description, props}: PreviewProps) {
</Tabs>
</div>
))}
</Layout.Vertical>
</Layout.Container>
</Collapse.Panel>
<Collapse.Panel header="Props" key="props">
<Table
@@ -358,15 +343,15 @@ function ComponentPreview({title, demos, description, props}: PreviewProps) {
/>
</Collapse.Panel>
</Collapse>
</Layout.Vertical>
</Layout.Container>
</Card>
);
}
export const DesignComponentDemos = () => (
<Layout.Vertical gap={theme.space.large}>
<Layout.Container gap={theme.space.large}>
{demos.map((demo) => (
<ComponentPreview key={demo.title} {...demo} />
))}
</Layout.Vertical>
</Layout.Container>
);

View File

@@ -36,7 +36,7 @@ import {ToplevelProps} from './SandyApp';
import {useValue} from 'flipper-plugin';
import {logout} from '../reducers/user';
import config from '../fb-stubs/config';
import Layout from '../ui/components/Layout';
import {Layout} from '../ui/components/Layout';
import styled from '@emotion/styled';
const LeftRailButtonElem = styled(Button)<{kind?: 'small'}>(({kind}) => ({
@@ -108,7 +108,7 @@ export function LeftRail({
return (
<Layout.Container borderRight padv={12} width={48}>
<Layout.Bottom>
<Layout.Vertical center gap={10} padh={6}>
<Layout.Container center gap={10} padh={6}>
<LeftRailButton
icon={<MobileFilled />}
title="App Inspect"
@@ -127,8 +127,8 @@ export function LeftRail({
toplevelSelection={toplevelSelection}
setToplevelSelection={setToplevelSelection}
/>
</Layout.Vertical>
<Layout.Vertical center gap={10} padh={6}>
</Layout.Container>
<Layout.Container center gap={10} padh={6}>
<SetupDoctorButton />
<WelcomeScreenButton />
<ShowSettingsButton />
@@ -140,7 +140,7 @@ export function LeftRail({
<RightSidebarToggleButton />
<LeftSidebarToggleButton />
{config.showLogin && <LoginButton />}
</Layout.Vertical>
</Layout.Container>
</Layout.Bottom>
</Layout.Container>
);

View File

@@ -15,9 +15,9 @@ import {Button, Tooltip, Typography} from 'antd';
import {InfoCircleOutlined} from '@ant-design/icons';
export const LeftSidebar: React.FC = ({children}) => (
<Layout.Vertical borderRight padv="small" grow shrink>
<Layout.Container borderRight padv="small" grow shrink>
{children}
</Layout.Vertical>
</Layout.Container>
);
export function SidebarTitle({

View File

@@ -19,7 +19,7 @@ const {Title, Text, Link} = Typography;
export default function SandyDesignSystem() {
return (
<Layout.ScrollContainer className={reset}>
<Layout.Vertical gap="large">
<Layout.Container gap="large">
<Card title="Flipper Design System" bordered={false}>
<p>
Welcome to the Flipper Design System. The Flipper design system is
@@ -122,7 +122,7 @@ export default function SandyDesignSystem() {
<Card title="Layout components" bordered={false}>
<DesignComponentDemos />
</Card>
</Layout.Vertical>
</Layout.Container>
</Layout.ScrollContainer>
);
}

View File

@@ -160,7 +160,7 @@ function CollapsableCategory(props: {checks: Array<HealthcheckReportItem>}) {
function HealthCheckList(props: {report: HealthcheckReport}) {
useEffect(() => reportUsage('doctor:report:opened'), []);
return (
<Layout.Vertical>
<Layout.Container>
<ResultTopDialog status={props.report.result.status} />
{Object.values(props.report.categories).map((category) => (
<Layout.Container key={category.key}>
@@ -187,7 +187,7 @@ function HealthCheckList(props: {report: HealthcheckReport}) {
/>
</Layout.Container>
))}
</Layout.Vertical>
</Layout.Container>
);
}

View File

@@ -40,7 +40,7 @@ export function AppInspect() {
<SidebarTitle actions={<InfoIcon>{appTooltip}</InfoIcon>}>
App Inspect
</SidebarTitle>
<Layout.Vertical padv="small" padh="medium" gap={theme.space.large}>
<Layout.Container padv="small" padh="medium" gap={theme.space.large}>
<AppSelector />
<Input addonAfter={<SettingOutlined />} defaultValue="mysite" />
<Layout.Horizontal gap>
@@ -55,7 +55,7 @@ export function AppInspect() {
}}
/>
</Layout.Horizontal>
</Layout.Vertical>
</Layout.Container>
</Layout.Container>
<Layout.Container padv={theme.space.large}>
<Layout.ScrollContainer vertical>

View File

@@ -79,12 +79,12 @@ export function AppSelector() {
<AppInspectButton title="Select the device / app to inspect">
<Layout.Horizontal gap center>
<AppIcon appname={client?.query.app} />
<Layout.Vertical grow shrink>
<Layout.Container grow shrink>
<Text strong>{client?.query.app ?? ''}</Text>
<Text>
{selectedDevice?.displayTitle() || 'Available devices'}
</Text>
</Layout.Vertical>
</Layout.Container>
<CaretDownOutlined />
</Layout.Horizontal>
</AppInspectButton>

View File

@@ -14,7 +14,7 @@ import {renderReactRoot} from '../../utils/renderReactRoot';
import {Store} from '../../reducers';
import {useStore} from '../../utils/useStore';
import {launchEmulator} from '../../devices/AndroidDevice';
import Layout from '../../ui/components/Layout';
import {Layout} from '../../ui/components/Layout';
import {
launchSimulator,
getSimulators,
@@ -100,9 +100,9 @@ export function LaunchEmulatorDialog({
title="Launch Emulator"
footer={null}
bodyStyle={{maxHeight: 400, overflow: 'auto'}}>
<Layout.Vertical gap>
<Layout.Container gap>
{items.length ? items : <Alert message="No emulators available" />}
</Layout.Vertical>
</Layout.Container>
</Modal>
);
}

View File

@@ -125,7 +125,7 @@ function NotificationEntry({notification}: {notification: PluginNotification}) {
style: {color: theme.primaryColor},
});
return (
<Layout.Vertical gap="small" pad="medium">
<Layout.Container gap="small" pad="medium">
<Layout.Horizontal gap="tiny" center>
{icon}
<Text>{pluginId}</Text>
@@ -140,7 +140,7 @@ function NotificationEntry({notification}: {notification: PluginNotification}) {
Open
</Button>
<DetailCollapse detail={content.message} />
</Layout.Vertical>
</Layout.Container>
);
}
@@ -151,14 +151,14 @@ function NotificationList({
}) {
return (
<Layout.ScrollContainer vertical>
<Layout.Vertical>
<Layout.Container>
{notifications.map((notification) => (
<NotificationEntry
key={notification.notification.id}
notification={notification}
/>
))}
</Layout.Vertical>
</Layout.Container>
</Layout.ScrollContainer>
);
}
@@ -175,12 +175,12 @@ export function Notification() {
return (
<LeftSidebar>
<Layout.Top>
<Layout.Vertical gap="tiny" padv="tiny" borderBottom>
<Layout.Container gap="tiny" padv="tiny" borderBottom>
<SidebarTitle actions={actions}>notifications</SidebarTitle>
<Layout.Container padh="medium" padv="small">
<Input placeholder="Search..." prefix={<SearchOutlined />} />
</Layout.Container>
</Layout.Vertical>
</Layout.Container>
<NotificationList notifications={notificationExample} />
</Layout.Top>
</LeftSidebar>

View File

@@ -35,6 +35,14 @@ type ContainerProps = {
grow?: boolean;
// allow shrinking beyond minally needed size? Makes using ellipsis on children possible
shrink?: boolean;
/**
* 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;
} & PaddingProps;
const Container = styled.div<ContainerProps>(
@@ -49,6 +57,8 @@ const Container = styled.div<ContainerProps>(
height,
grow,
shrink,
gap,
center,
...rest
}) => ({
display: 'flex',
@@ -61,6 +71,9 @@ const Container = styled.div<ContainerProps>(
: shrink
? `0 1 0` // allow shrinking smaller than natural size
: `0 0 auto`, // (default) use natural size, don't grow or shrink (in parent flex direction)
alignItems: center ? 'center' : 'stretch',
gap: normalizeSpace(gap, theme.space.small),
minWidth: shrink ? 0 : undefined,
boxSizing: 'border-box',
width,
@@ -77,33 +90,9 @@ const Container = styled.div<ContainerProps>(
}),
);
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;
};
function distributionStyle({gap, center}: DistributionProps) {
return {
gap: normalizeSpace(gap, theme.space.small),
alignItems: center ? 'center' : 'stretch',
};
}
const Horizontal = styled(Container)<DistributionProps>((props) => ({
...distributionStyle(props),
const Horizontal = styled(Container)({
flexDirection: 'row',
}));
const Vertical = styled(Container)<DistributionProps>((props) => ({
...distributionStyle(props),
flexDirection: 'column',
}));
});
const ScrollParent = styled.div<{axis?: ScrollAxis}>(({axis}) => ({
flex: 1,
@@ -113,7 +102,7 @@ const ScrollParent = styled.div<{axis?: ScrollAxis}>(({axis}) => ({
overflowY: axis === 'x' ? 'hidden' : 'auto',
}));
const ScrollChild = styled(Vertical)<{axis?: ScrollAxis}>(({axis}) => ({
const ScrollChild = styled(Container)<{axis?: ScrollAxis}>(({axis}) => ({
position: 'absolute',
minHeight: '100%',
minWidth: '100%',
@@ -153,6 +142,24 @@ type SplitLayoutProps = {
children: [React.ReactNode, React.ReactNode];
};
function renderSplitLayout(
props: SplitLayoutProps,
direction: 'column' | 'row',
grow: 1 | 2,
) {
// eslint-disable-next-line
const isSandy = useIsSandy();
if (!isSandy) return renderLayout(props, direction === 'row', grow === 1);
let [child1, child2] = props.children;
if (props.scrollable) child2 = <ScrollContainer>{child2}</ScrollContainer>;
return (
<SandySplitContainer {...props} flexDirection={direction} grow={grow}>
{child1}
{child2}
</SandySplitContainer>
);
}
/**
* The Layout component divides all available screenspace over two components:
* A fixed top (or left) component, and all remaining space to a bottom component.
@@ -164,59 +171,22 @@ type SplitLayoutProps = {
*
* Use Layout.Top / Right / Bottom / Left to indicate where the fixed element should live.
*/
const Layout = {
export 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>
);
return renderSplitLayout(props, 'column', 2);
},
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>
);
return renderSplitLayout(props, 'column', 1);
},
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>
);
return renderSplitLayout(props, 'row', 2);
},
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>
);
return renderSplitLayout(props, 'row', 1);
},
Container,
ScrollContainer,
Horizontal,
Vertical,
};
Object.keys(Layout).forEach((key) => {
@@ -235,14 +205,14 @@ const SandySplitContainer = styled.div<{
alignItems: props.center ? 'center' : 'stretch',
overflow: 'hidden',
'> :nth-child(1)': {
flex: props.grow === 1 ? growStyle : fixedStyle,
flex: props.grow === 1 ? splitGrowStyle : splitFixedStyle,
minWidth: props.grow === 1 ? 0 : undefined,
},
'> :nth-child(2)': {
flex: props.grow === 2 ? growStyle : fixedStyle,
flex: props.grow === 2 ? splitGrowStyle : splitFixedStyle,
minWidth: props.grow === 2 ? 0 : undefined,
},
}));
const fixedStyle = `0 0 auto`;
const growStyle = `1 0 0`;
export default Layout;
const splitFixedStyle = `0 0 auto`;
const splitGrowStyle = `1 0 0`;

View File

@@ -14,7 +14,7 @@ import FlexBox from './FlexBox';
import styled from '@emotion/styled';
import {useIsSandy} from '../../sandy-chrome/SandyContext';
import {theme} from '../../sandy-chrome/theme';
import Layout from './Layout';
import {Layout} from './Layout';
/**
* A toolbar.

View File

@@ -21,7 +21,7 @@ import styled from '@emotion/styled';
import {debounce} from 'lodash';
import ToggleButton from '../ToggleSwitch';
import React from 'react';
import Layout from '../Layout';
import {Layout} from '../Layout';
import {theme} from '../../../sandy-chrome/theme';
const SearchBar = styled(Toolbar)({

View File

@@ -178,7 +178,7 @@ export {default as CenteredView} from './components/CenteredView';
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 {Layout} from './components/Layout';
export {default as Scrollable} from './components/Scrollable';
export * from './components/Highlight';