Introduce NUX element

Summary:
allow-large-files

This diff introces the `NUX` element that can be wrapped around any other element to give a first-time usage hint.

Hint dismissal is stored by taking a hash of the hint contents, and scoped per plugin.

Users can reset the 'read' status in the settings page

Example usage:

```
<NUX
  title="Use bookmarks to directly navigate to a location in the app."
  placement="right">
  <Input addonAfter={<SettingOutlined />} defaultValue="mysite" />
</NUX>
```

Reviewed By: nikoant

Differential Revision: D24622276

fbshipit-source-id: 0265634f9ab50c32214b74f033f59482cd986f23
This commit is contained in:
Michel Weststrate
2020-11-06 07:31:19 -08:00
committed by Facebook GitHub Bot
parent b8b9c4296a
commit 2b0e93a063
12 changed files with 325 additions and 28 deletions

View File

@@ -8,7 +8,7 @@
*/
import {FlexColumn, Button, styled, Text, FlexRow, Spacer} from '../ui';
import React, {Component} from 'react';
import React, {Component, useContext} from 'react';
import {updateSettings, Action} from '../reducers/settings';
import {
Action as LauncherAction,
@@ -28,6 +28,7 @@ import LauncherSettingsPanel from '../fb-stubs/LauncherSettingsPanel';
import SandySettingsPanel from '../fb-stubs/SandySettingsPanel';
import {reportUsage} from '../utils/metrics';
import {Modal} from 'antd';
import {Layout, NuxManagerContext} from 'flipper-plugin';
const Container = styled(FlexColumn)({
padding: 20,
@@ -311,6 +312,10 @@ class SettingsSheet extends Component<Props, State> {
}}
/>
</ToggledSection>
<Layout.Right center>
<span>Reset all new user tooltips</span>
<ResetTooltips />
</Layout.Right>
</>
);
@@ -351,3 +356,16 @@ export default connect<StateFromProps, DispatchFromProps, OwnProps, Store>(
}),
{updateSettings, updateLauncherSettings},
)(SettingsSheet);
function ResetTooltips() {
const nuxManager = useContext(NuxManagerContext);
return (
<Button
onClick={() => {
nuxManager.resetHints();
}}>
Reset
</Button>
);
}

View File

@@ -34,6 +34,7 @@ import {PopoverProvider} from './ui/components/PopoverProvider';
import {initializeFlipperLibImplementation} from './utils/flipperLibImplementation';
import {enableConsoleHook} from './chrome/ConsoleLogs';
import {sideEffect} from './utils/sideEffect';
import {NuxManagerContext, createNuxManager} from 'flipper-plugin';
if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
// By default Node.JS has its internal certificate storage and doesn't use
@@ -56,7 +57,9 @@ const AppFrame = () => (
<ContextMenuProvider>
<Provider store={store}>
<CacheProvider value={cache}>
<App logger={logger} />
<NuxManagerContext.Provider value={createNuxManager()}>
<App logger={logger} />
</NuxManagerContext.Provider>
</CacheProvider>
</Provider>
</ContextMenuProvider>

View File

@@ -9,8 +9,8 @@
import React from 'react';
import {Typography, Card, Table, Collapse, Button, Tabs} from 'antd';
import {Layout} from '../ui';
import {theme} from 'flipper-plugin';
import {Layout, Link} from '../ui';
import {NUX, theme} from 'flipper-plugin';
import reactElementToJSXString from 'react-element-to-jsx-string';
import {CodeOutlined} from '@ant-design/icons';
@@ -29,8 +29,8 @@ const demoStyle: Record<string, React.CSSProperties> = {
type PreviewProps = {
title: string;
description?: string;
props: [string, string, string][];
description?: React.ReactNode;
props: [string, React.ReactNode, React.ReactNode][];
demos: Record<string, React.ReactNode>;
};
@@ -285,6 +285,31 @@ const demos: PreviewProps[] = [
),
},
},
{
title: 'NUX',
description:
'A component to provide a New-User-eXperience: Highlight new features to first time users. For tooltips that should stay available, use ToolTip from ANT design',
props: [
['title', 'string / React element', 'The tooltip contents'],
[
'placement',
<>
See{' '}
<Link href="https://ant.design/components/tooltip/#components-tooltip-demo-placement">
docs
</Link>
</>,
'(optional) on which side to place the tooltip',
],
],
demos: {
'NUX example': (
<NUX title="This button does something cool" placement="right">
<Button>Hello world</Button>
</NUX>
),
},
},
];
function ComponentPreview({title, demos, description, props}: PreviewProps) {

View File

@@ -12,7 +12,7 @@ import {Alert, Input} from 'antd';
import {LeftSidebar, SidebarTitle, InfoIcon} from '../LeftSidebar';
import {SettingOutlined} from '@ant-design/icons';
import {Layout, Link, styled} from '../../ui';
import {theme} from 'flipper-plugin';
import {NUX, theme} from 'flipper-plugin';
import {AppSelector} from './AppSelector';
import {useStore} from '../../utils/useStore';
import {PluginList} from './PluginList';
@@ -49,7 +49,11 @@ export function AppInspect() {
type="info"
/>
) : (
<Input addonAfter={<SettingOutlined />} defaultValue="mysite" />
<NUX
title="Use bookmarks to directly navigate to a location in the app."
placement="right">
<Input addonAfter={<SettingOutlined />} defaultValue="mysite" />
</NUX>
)}
{!isArchived && (
<Toolbar gap>

View File

@@ -12,7 +12,7 @@ import {Badge, Button, Menu, Tooltip, Typography} from 'antd';
import {InfoIcon, SidebarTitle} from '../LeftSidebar';
import {PlusOutlined, MinusOutlined} from '@ant-design/icons';
import {Glyph, Layout, styled} from '../../ui';
import {theme} from 'flipper-plugin';
import {theme, NUX} from 'flipper-plugin';
import {useDispatch, useStore} from '../../utils/useStore';
import {getPluginTitle, sortPluginsByName} from '../../utils/pluginUtils';
import {ClientPluginDefinition, DevicePluginDefinition} from '../../plugin';
@@ -124,7 +124,10 @@ export const PluginList = memo(function PluginList() {
</PluginGroup>
{!isArchived && (
<PluginGroup key="metro" title="React Native">
<PluginGroup
key="metro"
title="React Native"
hint="The following plugins are exposed by the currently running Metro instance. Note that Metro might currently be connected to a different application or device than selected above.">
{metroPlugins.map((plugin) => (
<PluginEntry
key={'metro' + plugin.id}
@@ -163,7 +166,10 @@ export const PluginList = memo(function PluginList() {
))}
</PluginGroup>
{!isArchived && (
<PluginGroup key="disabled" title="Disabled">
<PluginGroup
key="disabled"
title="Disabled"
hint="This section shows the plugins that are currently disabled. If a pluign is enabled, you will be able to interact with it. If a plugin is disabled it won't consume resources in Flipper or in the connected application.">
{disabledPlugins.map((plugin) => (
<PluginEntry
key={plugin.id}
@@ -184,7 +190,10 @@ export const PluginList = memo(function PluginList() {
</PluginGroup>
)}
{!isArchived && (
<PluginGroup key="unavailable" title="Unavailable plugins">
<PluginGroup
key="unavailable"
title="Unavailable plugins"
hint="The plugins below are installed in Flipper, but not available for the selected device / application. Hover the plugin info box to find out why.">
{unavailablePlugins.map(([plugin, reason]) => (
<PluginEntry
key={plugin.id}
@@ -295,23 +304,40 @@ const PluginEntry = memo(function PluginEntry({
const PluginGroup = memo(function PluginGroup({
title,
children,
hint,
...rest
}: {title: string; children: React.ReactElement[]} & Record<string, any>) {
}: {title: string; children: React.ReactElement[]; hint?: string} & Record<
string,
any
>) {
if (children.length === 0) {
return null;
}
let badge = (
<Badge
count={children.length}
style={{
marginRight: 20,
}}
/>
);
if (hint) {
badge = (
<NUX title={hint} placement="right">
{badge}
</NUX>
);
}
return (
<SubMenu
{...rest}
title={
<Layout.Right center>
<Text strong>{title}</Text>
<Badge
count={children.length}
style={{
marginRight: 20,
}}
/>
{badge}
</Layout.Right>
}>
{children}