JS custom UI tutorial

Summary:
- Adding tutorial for custom JS UI
- optimizing screenshots
- fixing overlapping screenshots on landing page

Reviewed By: jknoxville

Differential Revision: D15198105

fbshipit-source-id: db53403b84a2c422650a4f80e959dad6e984d274
This commit is contained in:
Pascal Hartig
2019-05-03 10:15:12 -07:00
committed by Facebook Github Bot
parent 1afece19e7
commit 531b5e850c
10 changed files with 168 additions and 16 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

After

Width:  |  Height:  |  Size: 345 KiB

BIN
docs/assets/js-custom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@@ -3,21 +3,18 @@ id: search-and-filter
title: Searching and Filtering
---
## Adding Search and Filter capabilities to your plugin
Many plugins need the ability to make their content search and filterable. Flipper provides a component for this use-case called `Searchable`. This is a higher-order-component that can be used to wrap any table, list, generic react-component and adds Flippers search bar on top of it.
We differentiate between search and filter, but both functionalities are provided by the `Searchable` component. Search is a custom string entered by the user. Filters can not be added by the user directly, but are added programmatically from within your plugin.
### Filters
## Filters
Every filter has a key and a value. The key represents an attribute of the items you are filtering, and the value is the value that is compared with the items to see if the attribute matches. As an example, if you were filtering rows of a table, the a filter key would be the id of a column.
There are two different types of filters:
- **include/exclude filters**: An arbitrary string that must (or must not) be included in the filterable item.
- **enum**: Allows the user to select one or more from a set of values, using a dropdown.
**include/exclude filters**: An arbitrary string that must (or must not) be included in the filterable item.
**enum**: Allows the user to select one or more from a set of values, using a dropdown.
### Higher Order Component
## Higher Order Component
The `Searchable()` function adds three props to a React component:
`addFilter: (filter: Filter) => void`
@@ -33,10 +30,10 @@ The search term entered into the searchbar by the user.
The list of filters that are currently applied.
#### Example
### Example
```
import type {SearchableProps} from 'sonar';
import {Searchable} from 'sonar';
import type {SearchableProps} from 'flipper';
import {Searchable} from 'flipper';
class MyPluginTable extends Component<{
...SearchableProps

150
docs/tutorial/js-custom.md Normal file
View File

@@ -0,0 +1,150 @@
---
id: js-custom
title: Building Custom UI
sidebar_label: Custom UI
---
Displaying your data in a table might work for many use-cases. However, depending on your plugin and data it might make sense to customize the way your data is visualized. Flipper uses React to render the plugins and provides a variety of ready-to-use UI components that can be used to build custom plugin UIs.
## Replacing the table
For our sea mammals app, we might not only want to see them listed as image URLs in a table but render the actual images in nice little cards. When selecting one of the cards we still want to display all details in the sidebar.
![Custom cards UI for our sea mammals plugin](/docs/assets/js-custom.png)
Currently, the default export in our `index.js` is from `createTablePlugin`. Now we are going to replace this with a custom React component extending `FlipperPlugin`.
```js
export default class SeaMammals extends FlipperPlugin {
static Container = styled(FlexRow)({
backgroundColor: colors.macOSTitleBarBackgroundBlur,
flexWrap: 'wrap',
alignItems: 'flex-start',
alignContent: 'flex-start',
flexGrow: 1,
});
render() {
return (
<SeaMammals.Container>
Hello custom plugin!
</SeaMammals.Container>
);
}
}
```
You can see how we are styling our components using [emotion](https://emotion.sh/). To learn more about this, make sure to read our guide on [styling components](extending/styling-components.md).
## Adding data handling
The plugin is quite useless when we don't display any actual data. We are adding two static properties to our plugin class for data handling. `defaultPersistedState` defines the default state before we received any data. In `persistedStateReducer` we define how new data is merged with the existing data.
For the default state we define an empty array because we don't have any data, yet. When receiving data, we simply add it to the existing array. Learn more about [persistedState](extending/js-plugin-api.md#persistedstate) in our guide.
```js
static defaultPersistedState = {
data: [],
};
static persistedStateReducer = (
persistedState: PersistedState,
method: string,
payload: Row,
) => {
if (method === 'newRow') {
return {
data: [...persistedState.data, payload],
};
}
return persistedState;
};
```
Note: The method name `newRow` is still the same that we defined on the native side.
## Rendering the data
Now we can access the data from `this.props.persistedState.data` and render it. So let's update our `render` method using a custom `Card` component, which we will implement in a bit.
```js
render() {
const {selectedIndex} = this.state;
const {data} = this.props.persistedState;
return (
<SeaMammals.Container>
{data.map((row, i) => <Card
{...row}
key={i}
onSelect={() => this.setState({selectedIndex: i})}
selected={i === selectedIndex}
/>)}
</SeaMammals.Container>
);
}
```
## Adding the sidebar
When clicking on a Card, we want to show all details in the sidebar as we did with the table before. We are using React's state to store the selected index in our array. Flipper provides a `DetailSidebar` component which we can use to add information to the sidebar. It doesn't matter where this component is placed as long as it is returned somewhere in our `render` method. The `renderSidebar` method returning the sidebar's content is still the same we used with `createTablePlugin`.
```js
<DetailSidebar>
{this.state.selectedIndex > -1 &&
renderSidebar(this.props.persistedState.data[selectedIndex])}
</DetailSidebar>
```
## Creating a custom component
The `Card` component is responsible for rendering the actual image and title. This is not very specific to Flipper, but is using plain React. Note the usage of `styled` to adjust the style of existing UI components and `colors` which provides a library of colors used throughout the app.
```js
class Card extends React.Component<{
...Row,
onSelect: () => void,
selected: boolean,
}> {
static Container = styled(FlexColumn)(props => ({
margin: 10,
borderRadius: 5,
border: '2px solid black',
backgroundColor: colors.white,
borderColor: props.selected
? colors.macOSTitleBarIconSelected
: colors.white,
padding: 0,
width: 150,
overflow: 'hidden',
boxShadow: '1px 1px 4px rgba(0,0,0,0.1)',
cursor: 'pointer',
}));
static Image = styled('div')({
backgroundSize: 'cover',
width: '100%',
paddingTop: '100%',
});
static Title = styled(Text)({
fontSize: 14,
fontWeight: 'bold',
padding: '10px 5px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
});
render() {
return (
<Card.Container
onClick={this.props.onSelect}
selected={this.props.selected}>
<Card.Image style={{backgroundImage: `url(${this.props.url || ''})`}} />
<Card.Title>{this.props.title}</Card.Title>
</Card.Container>
);
}
}
```

View File

@@ -4,9 +4,9 @@ title: Showing a table
---
Now that we have the native side covered, let's display the data we're sending
on the desktop side.
on the desktop side.
<img align="right" src="/docs/assets/android-tutorial-desktop.png" alt="Android Tutorial App" width="400">
![Android Tutorial App](assets/android-tutorial-desktop.png)
## Dynamic Plugin loading
@@ -201,4 +201,4 @@ the stuff you see on screen.
For many cases, this is already all you need. However,
sometimes you want to go the extra mile and want
to build something a bit more custom. That's what
we're going to do in the next part of our tutorial.
we're going to do in the next part of our tutorial.

View File

@@ -128,6 +128,10 @@
"tutorial/intro": {
"title": "Intro"
},
"tutorial/js-custom": {
"title": "Building Custom UI",
"sidebar_label": "Custom UI"
},
"tutorial/js-table": {
"title": "Showing a table"
}

View File

@@ -46,7 +46,8 @@
"Tutorial": [
"tutorial/intro",
"tutorial/android",
"tutorial/js-table"
"tutorial/js-table",
"tutorial/js-custom"
],
"Other Platforms": [
"extending/new-clients",

View File

@@ -375,8 +375,8 @@ footer iframe {
flex-direction: column;
}
.splash .splashScreen {
margin-top: 20px;
.splash .slideshow {
margin-top: 80px;
}
.content.row {