Customizing Output
Components
Zipper ships with a handful of pre-built UI components that allow you to customize the output of your applet. These components should be described as JSON in the output of your handler functions. Fortunately, Zipper provides a handy method that generates the necessary JSON for you: Zipper.Component.create()
.
Stacks
Based on flexbox containers, stacks let you easily control the layout of your output by applying rows or columns with evenly spaced elements.
Zipper.Component.create({
/**
* The type of component you want to create
*/
type: 'stack';
/**
* The props of the stack component:
* - direction: the flex direction of
* - divider: show a divider between elements in the stack
* - align: shorthand for the align-items CSS property
*/
props: {
direction: 'row' | 'column';
divider?: boolean;
align?: string;
};
/**
* You can pass through an array of elements that will be
* rendered within the stack. Children can be any JSON that
* Zipper would typically render, including other Components
* and Actions
*/
children: Array<Serializable | Component>;
})
Links
The link component simply renders an a
tag.
Zipper.Component.create({
/**
* The type of component you want to create
*/
type: 'link';
/**
* The props of the stack component:
* - href: the url you want to link to
* - target: whether the link should open in a new tab
*/
props: {
href: string;
target?: '_blank' | '_self';
};
/**
* The text of the link
*/
text: string;
})
Sections
An applet is able to render its output in two places:
- The main section (where output is rendered when the app is run)
- In a modal (which must be triggered via an Action.
Each of these places has two sections: primary and expanded. The expanded section opens below the primary section and must be triggered via an Action. The expanded section should be thought of as a way to peek into information in the primary section.
If you output to the same place and section multiple times, Zipper will replace the content but allow the user to navigate back through the stack.
Rendering UI from JSON
Zipper will automatically convert the JSON output of your handler function into rendered UI.
Arrays
Arrays will be converted to tables with sortable columns. If your arrays contain only primitives, then they can also be viewable as cards. If your array has more complex objects and arrays, those will be rendered within the table.
Objects
Objects will be rendered in Zipper’s object explorer, allowing you to expand and collapse keys. Zipper can also render nested objects and arrays.
HTML
If you return an HTML string, Zipper will simply render HTML.
Markdown
By default, returning a string will render your string in Markdown. You can also explicitly send Markdown as text to any part of your applet by using the md
tag. For example:
const plaintext = 'regular text';
const markdown = md`
# Heading
## Subheading
Some _rich_ _text_ (here)[https://zipper.works]
1. one
2. two
3. three
`;
return { plaintext, markdown };
The above code will render an object with both plaintext and markdown text.
Advanced: JSX Output for Custom UI
Zipper also supports JSX for rendering basic HTML and special Zipper Components. At this time, Zipper does not support full client-side React/Preact. However, you can compose Actions, Components, and HTML layout together.
For example, you can use the Stack Components (as Row
and Column
) to build out a layout with text, HTML, and Actions (as Button
and Dropdown
).
return (
<>
<h1>Title</h1>
<h2>Subtitle</h2>
<Markdown>Some **rich** _text_ here</Markdown>
<Stack>
<Row>
<Column>
<Button
showAs="modal"
path="greeting"
run={true}
inputs={{ world: 'Earth!!!' }}
>
earth
</Button>
</Column>
<Column>
<Dropdown
options={{
world: [
{
label: 'mars',
value: 'Mars!!!',
},
{
label: 'venus',
value: 'Venus!!!',
},
],
}}
path="greeting"
showAs="modal"
text="OK"
/>
</Column>
</Row>
<Row>
<Column>three</Column>
<Column>
<Link href="https://google.com/">Go to Google</Link>
</Column>
</Row>
</Stack>
</>
);
This will return a UI that looks like this:
This would save you from writing all this JSON by hand:
[
{
"$zipperType": "Zipper.Component",
"type": "html.h1",
"props": null,
"children": ["Title"]
},
{
"$zipperType": "Zipper.Component",
"type": "html.h2",
"props": null,
"children": ["Subtitle"]
},
{
"$zipperType": "Zipper.Component",
"type": "markdown",
"props": {},
"children": ["Some **rich** _text_ here"]
},
{
"$zipperType": "Zipper.Component",
"type": "stack",
"props": {},
"children": [
{
"$zipperType": "Zipper.Component",
"type": "stack",
"props": {
"direction": "row"
},
"children": [
{
"$zipperType": "Zipper.Component",
"type": "stack",
"props": {
"direction": "column"
},
"children": [
{
"$zipperType": "Zipper.Action",
"actionType": "button",
"showAs": "modal",
"path": "greeting",
"run": true,
"inputs": {
"world": "Earth!!!"
},
"text": "earth"
}
]
},
{
"$zipperType": "Zipper.Component",
"type": "stack",
"props": {
"direction": "column"
},
"children": [
{
"$zipperType": "Zipper.Action",
"actionType": "dropdown",
"options": {
"world": [
{
"label": "mars",
"value": "Mars!!!"
},
{
"label": "venus",
"value": "Venus!!!"
}
]
},
"path": "greeting",
"showAs": "modal",
"text": "OK",
"children": []
}
]
}
]
},
{
"$zipperType": "Zipper.Component",
"type": "stack",
"props": {
"direction": "row"
},
"children": [
{
"$zipperType": "Zipper.Component",
"type": "stack",
"props": {
"direction": "column"
},
"children": ["three"]
},
{
"$zipperType": "Zipper.Component",
"type": "stack",
"props": {
"direction": "column"
},
"children": [
{
"$zipperType": "Zipper.Component",
"type": "link",
"props": {
"href": "https://google.com/"
},
"children": ["Go to Google"]
}
]
}
]
}
]
}
]
Composing components
With JSX and React-style components, you can make incredibly powerful and custom UIs with very little code. Try composing different patterns into components to build truly custom UIs.
type Props = { title: string; subtitle: string; };
const MyHeaderComponent = (props: Props) => (
<Row>
<h1>{title}</h1>
<h3>{subtitle}<h3>
</Row>
);
export function handler() {
return <MyHeaderComponent title="Hello" subtitle="world" />;
}