diff --git a/docs/assets/android-tutorial-app.png b/docs/assets/android-tutorial-app.png index 3e880c2bc..bcd61c778 100644 Binary files a/docs/assets/android-tutorial-app.png and b/docs/assets/android-tutorial-app.png differ diff --git a/docs/assets/android-tutorial-desktop.png b/docs/assets/android-tutorial-desktop.png index 10c162e5f..1e1a3db42 100644 Binary files a/docs/assets/android-tutorial-desktop.png and b/docs/assets/android-tutorial-desktop.png differ diff --git a/docs/assets/android-tutorial.png b/docs/assets/android-tutorial.png index d80f52113..eeebbddf4 100644 Binary files a/docs/assets/android-tutorial.png and b/docs/assets/android-tutorial.png differ diff --git a/docs/assets/js-custom.png b/docs/assets/js-custom.png new file mode 100644 index 000000000..1115b0ecb Binary files /dev/null and b/docs/assets/js-custom.png differ diff --git a/docs/extending/search-and-filter.md b/docs/extending/search-and-filter.md index 4476825da..9f2880125 100644 --- a/docs/extending/search-and-filter.md +++ b/docs/extending/search-and-filter.md @@ -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 diff --git a/docs/tutorial/js-custom.md b/docs/tutorial/js-custom.md new file mode 100644 index 000000000..83470adc3 --- /dev/null +++ b/docs/tutorial/js-custom.md @@ -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 ( + + Hello custom plugin! + + ); + } +} +``` + +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 ( + + {data.map((row, i) => this.setState({selectedIndex: i})} + selected={i === selectedIndex} + />)} + + ); +} +``` + +## 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 + + {this.state.selectedIndex > -1 && + renderSidebar(this.props.persistedState.data[selectedIndex])} + +``` + + +## 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 ( + + + {this.props.title} + + ); + } +} +``` diff --git a/docs/tutorial/js-table.md b/docs/tutorial/js-table.md index c97bb9f29..60e7f1902 100644 --- a/docs/tutorial/js-table.md +++ b/docs/tutorial/js-table.md @@ -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. -Android Tutorial App +![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. \ No newline at end of file +we're going to do in the next part of our tutorial. diff --git a/website/i18n/en.json b/website/i18n/en.json index 569ecf180..f8e594930 100644 --- a/website/i18n/en.json +++ b/website/i18n/en.json @@ -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" } diff --git a/website/sidebars.json b/website/sidebars.json index 7a003d56e..36361c294 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -46,7 +46,8 @@ "Tutorial": [ "tutorial/intro", "tutorial/android", - "tutorial/js-table" + "tutorial/js-table", + "tutorial/js-custom" ], "Other Platforms": [ "extending/new-clients", diff --git a/website/static/css/custom.css b/website/static/css/custom.css index 9cb083f1e..7cf21415a 100644 --- a/website/static/css/custom.css +++ b/website/static/css/custom.css @@ -375,8 +375,8 @@ footer iframe { flex-direction: column; } - .splash .splashScreen { - margin-top: 20px; + .splash .slideshow { + margin-top: 80px; } .content.row {