Bless "Layout" and "theme"
Summary: This blesses the `Layout` and `theme` components and exposes them from `flipper-plugin`, so that they can be used in (public) Sandy plugins. Also marked old abstractions as going to be deprecated. Reviewed By: cekkaewnumchai Differential Revision: D24503560 fbshipit-source-id: a8f384667b8f66e3b9f00771a123fe5c9d755eb3
This commit is contained in:
committed by
Facebook GitHub Bot
parent
dfdc02fbc2
commit
9f3df3406d
@@ -32,6 +32,9 @@ export {
|
||||
DefaultKeyboardAction,
|
||||
} from './plugin/MenuEntry';
|
||||
|
||||
export {theme} from './ui/theme';
|
||||
export {Layout} from './ui/Layout';
|
||||
|
||||
// It's not ideal that this exists in flipper-plugin sources directly,
|
||||
// but is the least pain for plugin authors.
|
||||
// Probably we should make sure that testing-library doesn't end up in our final Flipper bundle (which packages flipper-plugin)
|
||||
|
||||
213
desktop/flipper-plugin/src/ui/Layout.tsx
Normal file
213
desktop/flipper-plugin/src/ui/Layout.tsx
Normal file
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* 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 './theme';
|
||||
|
||||
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;
|
||||
// grow to available space?
|
||||
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>(
|
||||
({
|
||||
bordered,
|
||||
borderBottom,
|
||||
borderLeft,
|
||||
borderRight,
|
||||
borderTop,
|
||||
rounded,
|
||||
width,
|
||||
height,
|
||||
grow,
|
||||
shrink,
|
||||
gap,
|
||||
center,
|
||||
...rest
|
||||
}) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex:
|
||||
grow && shrink
|
||||
? `1 1 0` // allow growing, and shrinking smaller than actual size
|
||||
: grow
|
||||
? `1 0 auto` // allow grow, start at natural size
|
||||
: 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,
|
||||
height,
|
||||
padding: normalizePadding(rest),
|
||||
borderRadius: rounded ? theme.containerBorderRadius : undefined,
|
||||
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 Horizontal = styled(Container)({
|
||||
flexDirection: 'row',
|
||||
});
|
||||
|
||||
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(Container)<{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,
|
||||
padv,
|
||||
padh,
|
||||
pad,
|
||||
...rest
|
||||
}: React.HTMLAttributes<HTMLDivElement> & {
|
||||
horizontal?: boolean;
|
||||
vertical?: boolean;
|
||||
} & PaddingProps) => {
|
||||
const axis =
|
||||
horizontal && !vertical ? 'x' : !horizontal && vertical ? 'y' : 'both';
|
||||
return (
|
||||
<ScrollParent axis={axis} {...rest}>
|
||||
<ScrollChild axis={axis} padv={padv} padh={padh} pad={pad}>
|
||||
{children}
|
||||
</ScrollChild>
|
||||
</ScrollParent>
|
||||
) as any;
|
||||
};
|
||||
|
||||
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];
|
||||
};
|
||||
|
||||
function renderSplitLayout(
|
||||
props: SplitLayoutProps,
|
||||
direction: 'column' | 'row',
|
||||
grow: 1 | 2,
|
||||
) {
|
||||
const [child1, child2] = props.children;
|
||||
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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
export const Layout = {
|
||||
Top(props: SplitLayoutProps) {
|
||||
return renderSplitLayout(props, 'column', 2);
|
||||
},
|
||||
Bottom(props: SplitLayoutProps) {
|
||||
return renderSplitLayout(props, 'column', 1);
|
||||
},
|
||||
Left(props: SplitLayoutProps) {
|
||||
return renderSplitLayout(props, 'row', 2);
|
||||
},
|
||||
Right(props: SplitLayoutProps) {
|
||||
return renderSplitLayout(props, 'row', 1);
|
||||
},
|
||||
Container,
|
||||
ScrollContainer,
|
||||
Horizontal,
|
||||
};
|
||||
|
||||
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',
|
||||
'> :nth-child(1)': {
|
||||
flex: props.grow === 1 ? splitGrowStyle : splitFixedStyle,
|
||||
minWidth: props.grow === 1 ? 0 : undefined,
|
||||
},
|
||||
'> :nth-child(2)': {
|
||||
flex: props.grow === 2 ? splitGrowStyle : splitFixedStyle,
|
||||
minWidth: props.grow === 2 ? 0 : undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
const splitFixedStyle = `0 0 auto`;
|
||||
const splitGrowStyle = `1 0 0`;
|
||||
74
desktop/flipper-plugin/src/ui/theme.tsx
Normal file
74
desktop/flipper-plugin/src/ui/theme.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
// Exposes all the variables defined in themes/base.less:
|
||||
|
||||
export const theme = {
|
||||
white: 'white', // use as counter color for primary
|
||||
primaryColor: 'var(--flipper-primary-color)',
|
||||
successColor: 'var(--flipper-success-color)',
|
||||
errorColor: 'var(--flipper-error-color)',
|
||||
warningColor: 'var(--flipper-warning-color)',
|
||||
textColorPrimary: 'var(--flipper-text-color-primary)',
|
||||
textColorSecondary: 'var(--flipper-text-color-secondary)',
|
||||
textColorPlaceholder: 'var(--flipper-text-color-placeholder)',
|
||||
disabledColor: 'var(--flipper-disabled-color)',
|
||||
backgroundDefault: 'var(--flipper-background-default)',
|
||||
backgroundWash: 'var(--flipper-background-wash)',
|
||||
buttonDefaultBackground: 'var(--flipper-button-default-background)',
|
||||
backgroundTransparentHover: 'var(--flipper-background-transparent-hover)',
|
||||
dividerColor: 'var(--flipper-divider-color)',
|
||||
borderRadius: 'var(--flipper-border-radius)',
|
||||
containerBorderRadius: 8,
|
||||
inlinePaddingV: 6, // vertical padding on inline elements like buttons
|
||||
inlinePaddingH: 12, // horizontal ,,,
|
||||
space: {
|
||||
// from Space component in Ant
|
||||
tiny: 4,
|
||||
small: 8,
|
||||
medium: 12,
|
||||
large: 16,
|
||||
huge: 24,
|
||||
} as const,
|
||||
fontSize: {
|
||||
smallBody: '12px',
|
||||
} as const,
|
||||
} as const;
|
||||
|
||||
export type Spacing = keyof typeof theme['space'] | number | undefined | true;
|
||||
|
||||
export type PaddingProps = {
|
||||
padv?: Spacing;
|
||||
padh?: Spacing;
|
||||
pad?: Spacing;
|
||||
};
|
||||
|
||||
export function normalizePadding({
|
||||
padv,
|
||||
padh,
|
||||
pad,
|
||||
}: PaddingProps): string | undefined {
|
||||
if (padv === undefined && padh === undefined && pad === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return `${normalizeSpace(
|
||||
padv ?? pad ?? 0,
|
||||
theme.inlinePaddingV,
|
||||
)}px ${normalizeSpace(padh ?? pad ?? 0, theme.inlinePaddingH)}px`;
|
||||
}
|
||||
|
||||
export function normalizeSpace(spacing: Spacing, defaultSpace: number): number {
|
||||
return spacing === true
|
||||
? defaultSpace
|
||||
: spacing === undefined
|
||||
? 0
|
||||
: typeof spacing === 'string'
|
||||
? theme.space[spacing]
|
||||
: spacing;
|
||||
}
|
||||
Reference in New Issue
Block a user