Introduce Tabs

Summary:
Ant'd tabs didn't allow for vertical fill out. Introduced our own tiny wrapper that has `grow` by default.

Also made sure the users last selection is remembered.

Reviewed By: cekkaewnumchai

Differential Revision: D28026345

fbshipit-source-id: 7703bc241cd1427336b7c917bdb5be9f56bba9b9
This commit is contained in:
Michel Weststrate
2021-04-27 08:12:19 -07:00
committed by Facebook GitHub Bot
parent fe10e9dcc2
commit 37a7b16774
6 changed files with 154 additions and 8 deletions

View File

@@ -8,9 +8,17 @@
*/ */
import React from 'react'; import React from 'react';
import {Typography, Card, Table, Collapse, Button, Tabs} from 'antd'; import {Typography, Card, Table, Collapse, Button} from 'antd';
import {Layout, Link} from '../ui'; import {Layout, Link} from '../ui';
import {NUX, Panel, theme, Tracked, TrackingScope} from 'flipper-plugin'; import {
NUX,
Panel,
theme,
Tracked,
TrackingScope,
Tabs,
Tab,
} from 'flipper-plugin';
import reactElementToJSXString from 'react-element-to-jsx-string'; import reactElementToJSXString from 'react-element-to-jsx-string';
import {CodeOutlined} from '@ant-design/icons'; import {CodeOutlined} from '@ant-design/icons';
@@ -341,6 +349,52 @@ const demos: PreviewProps[] = [
), ),
}, },
}, },
{
title: 'Tabs / Tab',
description:
"Tabs represents a tab control and all it's children should be Tab components. By default the Tab control uses all available space, but set grow=false to only use the minimally required space",
props: [
[
'grow (Tabs)',
'boolean (true)',
'If true, the tab control will grow all tabs to the maximum available vertical space. If false, only the minimal required (natural) vertical space will be used',
],
[
'pad / gap (Tab)',
'boolean / number (false)',
'See the pad property of Layout.Container, determines whether the pane contents will have some padding and space between the items. By default no padding / gap is applied.',
],
[
'other props',
'',
'This component wraps Tabs from ant design, see https://ant.design/components/tabs/ for more details',
],
],
demos: {
'Two tabs': (
<Layout.Container height={200}>
<Tabs>
<Tab tab="Pane 1">{aDynamicBox}</Tab>
<Tab tab="Pane 2 pad gap" pad gap>
{aFixedHeightBox}
{aFixedHeightBox}
</Tab>
</Tabs>
</Layout.Container>
),
'Two tabs (no grow)': (
<Layout.Container grow={false}>
<Tabs>
<Tab tab="Pane 1">{aDynamicBox}</Tab>
<Tab tab="Pane 2 pad gap" pad gap>
{aFixedHeightBox}
{aFixedHeightBox}
</Tab>
</Tabs>
</Layout.Container>
),
},
},
{ {
title: 'NUX', title: 'NUX',
description: description:
@@ -420,7 +474,7 @@ function ComponentPreview({title, demos, description, props}: PreviewProps) {
<Layout.Container gap="large"> <Layout.Container gap="large">
{Object.entries(demos).map(([name, children]) => ( {Object.entries(demos).map(([name, children]) => (
<Tabs type="line" key={name}> <Tabs type="line" key={name}>
<Tabs.TabPane tab={name} key="1"> <Tab tab={name} key="1">
<div <div
style={{ style={{
background: theme.backgroundWash, background: theme.backgroundWash,
@@ -428,8 +482,8 @@ function ComponentPreview({title, demos, description, props}: PreviewProps) {
}}> }}>
{children} {children}
</div> </div>
</Tabs.TabPane> </Tab>
<Tabs.TabPane tab={<CodeOutlined />} key="2"> <Tab tab={<CodeOutlined />} key="2">
<div <div
style={{ style={{
background: theme.backgroundWash, background: theme.backgroundWash,
@@ -438,7 +492,7 @@ function ComponentPreview({title, demos, description, props}: PreviewProps) {
}}> }}>
<pre>{reactElementToJSXString(children)}</pre> <pre>{reactElementToJSXString(children)}</pre>
</div> </div>
</Tabs.TabPane> </Tab>
</Tabs> </Tabs>
))} ))}
</Layout.Container> </Layout.Container>

View File

@@ -40,6 +40,8 @@ test('Correct top level API exposed', () => {
"MarkerTimeline", "MarkerTimeline",
"NUX", "NUX",
"Panel", "Panel",
"Tab",
"Tabs",
"TestUtils", "TestUtils",
"Tracked", "Tracked",
"TrackingScope", "TrackingScope",

View File

@@ -91,6 +91,7 @@ export {
InteractiveProps as _InteractiveProps, InteractiveProps as _InteractiveProps,
} from './ui/Interactive'; } from './ui/Interactive';
export {Panel} from './ui/Panel'; export {Panel} from './ui/Panel';
export {Tabs, Tab} from './ui/Tabs';
export {useLocalStorageState} from './utils/useLocalStorageState'; export {useLocalStorageState} from './utils/useLocalStorageState';
export {HighlightManager} from './ui/Highlight'; export {HighlightManager} from './ui/Highlight';

View File

@@ -0,0 +1,89 @@
/**
* 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 * as React from 'react';
import {Children} from 'react';
import {Tabs as AntdTabs, TabsProps, TabPaneProps} from 'antd';
import {css, cx} from '@emotion/css';
import {Layout} from './Layout';
import {Spacing} from './theme';
import {useLocalStorageState} from '../utils/useLocalStorageState';
/**
* A Tabs component.
*/
export function Tabs({
grow,
children,
className,
...baseProps
}: {grow?: boolean} & TabsProps) {
const keys: string[] = [];
const keyedChildren = Children.map(children, (child: any, idx) => {
if (!child || typeof child !== 'object') {
return;
}
const tabKey =
(typeof child.props.tab === 'string' && child.props.tab) || `tab_${idx}`;
keys.push(tabKey);
return {
...child,
props: {
...child.props,
key: tabKey,
tabKey,
},
};
});
const [activeTab, setActiveTab] = useLocalStorageState<string | undefined>(
'Tabs:' + keys.join(','),
undefined,
);
return (
<AntdTabs
activeKey={activeTab}
onChange={(key) => {
setActiveTab(key);
}}
{...baseProps}
className={cx(className, grow !== false ? growingTabs : undefined)}>
{keyedChildren}
</AntdTabs>
);
}
export const Tab: React.FC<
TabPaneProps & {
pad?: Spacing;
gap?: Spacing;
}
> = function Tab({pad, gap, children, ...baseProps}) {
return (
<AntdTabs.TabPane {...baseProps}>
<Layout.Container gap={gap} pad={pad} grow>
{children}
</Layout.Container>
</AntdTabs.TabPane>
);
};
const growingTabs = css`
flex: 1;
& .tabpanel {
display: flex;
}
& .ant-tabs-content {
height: 100%;
}
& .ant-tabs-tabpane {
display: flex;
}
`;

View File

@@ -16,7 +16,7 @@ index ac93b76..edae9be 100644
+ 'Tabs', + 'Tabs',
+ 'onTabClick', + 'onTabClick',
+ scope, + scope,
+ 'tab:' + key + ':' + tab, + 'tab:' + key,
+ onClick, + onClick,
+ e + e
+ ); + );

View File

@@ -150,7 +150,7 @@ For conversion, the following table maps the old components to the new ones:
| `ManagedDataInspector` / `DataInspector` | `DataInspector` | `flipper-plugin` || | `ManagedDataInspector` / `DataInspector` | `DataInspector` | `flipper-plugin` ||
| `ManagedElementInspector` / `ElementInspector` | `ElementInspector` | `flipper-plugin` || | `ManagedElementInspector` / `ElementInspector` | `ElementInspector` | `flipper-plugin` ||
| `Panel` | `Panel` | `flipper-plugin` || | `Panel` | `Panel` | `flipper-plugin` ||
| `Tabs` / `Tab` | `Tabs` / `Tab` | `flipper-plugin || | `Tabs` / `Tab` | `Tabs` / `Tab` | `flipper-plugin | Note that `Tab`'s `title` property is now called `tab`. |
Most other components, like `select` elements, tabs, date-pickers, etc etc can all be found in the Ant documentaiton. Most other components, like `select` elements, tabs, date-pickers, etc etc can all be found in the Ant documentaiton.