diff --git a/desktop/app/src/sandy-chrome/DesignComponentDemos.tsx b/desktop/app/src/sandy-chrome/DesignComponentDemos.tsx index 9612fe198..d092c098d 100644 --- a/desktop/app/src/sandy-chrome/DesignComponentDemos.tsx +++ b/desktop/app/src/sandy-chrome/DesignComponentDemos.tsx @@ -8,9 +8,17 @@ */ 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 {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 {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': ( + + + {aDynamicBox} + + {aFixedHeightBox} + {aFixedHeightBox} + + + + ), + 'Two tabs (no grow)': ( + + + {aDynamicBox} + + {aFixedHeightBox} + {aFixedHeightBox} + + + + ), + }, + }, { title: 'NUX', description: @@ -420,7 +474,7 @@ function ComponentPreview({title, demos, description, props}: PreviewProps) { {Object.entries(demos).map(([name, children]) => ( - +
{children}
-
- } key="2"> + + } key="2">
{reactElementToJSXString(children)}
-
+
))}
diff --git a/desktop/flipper-plugin/src/__tests__/api.node.tsx b/desktop/flipper-plugin/src/__tests__/api.node.tsx index cab2fc4bf..7b85ce0fd 100644 --- a/desktop/flipper-plugin/src/__tests__/api.node.tsx +++ b/desktop/flipper-plugin/src/__tests__/api.node.tsx @@ -40,6 +40,8 @@ test('Correct top level API exposed', () => { "MarkerTimeline", "NUX", "Panel", + "Tab", + "Tabs", "TestUtils", "Tracked", "TrackingScope", diff --git a/desktop/flipper-plugin/src/index.ts b/desktop/flipper-plugin/src/index.ts index befb208c7..8aa757720 100644 --- a/desktop/flipper-plugin/src/index.ts +++ b/desktop/flipper-plugin/src/index.ts @@ -91,6 +91,7 @@ export { InteractiveProps as _InteractiveProps, } from './ui/Interactive'; export {Panel} from './ui/Panel'; +export {Tabs, Tab} from './ui/Tabs'; export {useLocalStorageState} from './utils/useLocalStorageState'; export {HighlightManager} from './ui/Highlight'; diff --git a/desktop/flipper-plugin/src/ui/Tabs.tsx b/desktop/flipper-plugin/src/ui/Tabs.tsx new file mode 100644 index 000000000..e04ba25c8 --- /dev/null +++ b/desktop/flipper-plugin/src/ui/Tabs.tsx @@ -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( + 'Tabs:' + keys.join(','), + undefined, + ); + + return ( + { + setActiveTab(key); + }} + {...baseProps} + className={cx(className, grow !== false ? growingTabs : undefined)}> + {keyedChildren} + + ); +} + +export const Tab: React.FC< + TabPaneProps & { + pad?: Spacing; + gap?: Spacing; + } +> = function Tab({pad, gap, children, ...baseProps}) { + return ( + + + {children} + + + ); +}; + +const growingTabs = css` + flex: 1; + & .tabpanel { + display: flex; + } + & .ant-tabs-content { + height: 100%; + } + & .ant-tabs-tabpane { + display: flex; + } +`; diff --git a/desktop/patches/rc-tabs+11.7.2.patch b/desktop/patches/rc-tabs+11.7.2.patch index af8ab2e35..51942fe6a 100644 --- a/desktop/patches/rc-tabs+11.7.2.patch +++ b/desktop/patches/rc-tabs+11.7.2.patch @@ -16,7 +16,7 @@ index ac93b76..edae9be 100644 + 'Tabs', + 'onTabClick', + scope, -+ 'tab:' + key + ':' + tab, ++ 'tab:' + key, + onClick, + e + ); diff --git a/docs/extending/sandy-migration.mdx b/docs/extending/sandy-migration.mdx index a27a7f043..bf84dbaf3 100644 --- a/docs/extending/sandy-migration.mdx +++ b/docs/extending/sandy-migration.mdx @@ -150,7 +150,7 @@ For conversion, the following table maps the old components to the new ones: | `ManagedDataInspector` / `DataInspector` | `DataInspector` | `flipper-plugin` || | `ManagedElementInspector` / `ElementInspector` | `ElementInspector` | `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.