The ability to utilize Chrome DevTools with NativeScript projects is quite delightful however sometimes we want to drill into a specific component to develop and design it outside the scope of the entire app -- or even document the UI in a way that allows our team to investigate components in isolation either right now or in the future.
Note
Imagine your team working on a purchase screen that's on the fourth tab (not the default tab) of your app which is only seen after viewing an item's detail screen, selecting options, filling out a form and then choosing purchase. That purchase component is something we can isolate using Storybook to explore it's props or inputs to not only develop and design it more efficiently but also allow interactive documentation to take shape to inform our team better.
You can even visually develop SwiftUI or Jetpack Compose user interface elements.
Setup Storybook and Init the Config
Adding Storybook to our project starts with installing the 2 following dependencies:
npm install @nativescript/storybook@beta @valor/nativescript-websockets
The first dependency, @nativescript/storybook
, handles Storybook and the other, @valor/nativescript-websockets
, helps faciliate the WebSocket communication between the web browser controls and the iOS/Android simulators/devices.
We can now initialize our config:
npx @nativescript/storybook init
We're now ready to create a story for any component. For demonstration purposes, let's use an Angular component which provides Input's to adjust a neat SwiftUI view of foggy color effects used in one of our app's flows -- credit to Aleksandr Dremov.
This is what our initial component composite looks like:
The foggy colors morph while slowly moving in the background for a nice effect but it's almost too subtle. Let's use Storybook to allow our developers to experiment with the effect to find just the right touch that our product owners like.
The foggy-colors.component.html
can be expressed by way of @nativescript/swift-ui like this:
<SwiftUI
swiftId="foggyColors"
[data]="data"
iosOverflowSafeArea="true"
class="h-full w-full"
/>
With initial data bindings setup as follows:
const data = {
blurRadius: 30,
globalOpacity: 0.8,
elementOpacity: 0.4,
animated: true,
numberShapes: 15,
differentShapes: 4,
colors: [
[new Color('blue').ios, new Color('red').ios],
[new Color('pink').ios, new Color('green').ios],
[new Color('blue').ios, new Color('red').ios],
[new Color('orange').ios, new Color('red').ios],
[new Color('blue').ios, new Color('blue').ios],
[new Color('yellow').ios, new Color('purple').ios],
[new Color('pink').ios, new Color('green').ios],
],
}
Create a Story
Let's create foggy-colors.stories.ts
right next to our foggy-colors.component
and setup the initial story.
import { Meta, StoryFn } from '@storybook/angular'
import { FoggyColorsComponent } from './foggy-colors.component'
export default {
title: 'Foggy Colors',
component: FoggyColorsComponent,
argTypes: {},
} as Meta
const Template: StoryFn<FoggyColorsComponent> = (
args: FoggyColorsComponent
) => ({
props: args,
})
export const Primary = Template.bind({})
Primary.args = {}
export const Secondary = Template.bind({})
Secondary.args = {}
You can learn more about story configuration option here.
Setup Inputs for the Story
We first want to make our component more customizable by opening up several Input
's to allow our developers to not only customize this component better but also allow Storybook to control it too!
Given our single <SwiftUI [data]="data"
binding we can arrange several Input's to modify all it's various properties, for example:
@Input() set blurRadius(value: number) {
this.data = {
...this.data,
blurRadius: value
};
}
We can now insert a Storybook control for blurRadius
into our story and provide perhaps two different settings for Primary and Secondary looks:
export default {
title: 'Foggy Colors',
component: FoggyColorsComponent,
argTypes: {
// define any Component Input as a Storybook control
blurRadius: { control: { type: 'range', min: 1, max: 50, step: 1 } },
},
} as Meta
// define initial primary & secondary looks with default values
export const Primary = Template.bind({})
Primary.args = {
blurRadius: 30,
}
export const Secondary = Template.bind({})
Secondary.args = {
blurRadius: 8,
}
Run our Story!
We can use the convenient npm script the dependency added for us on init
:
npm run storybook
> nativescript-storybook dev
? Select a platform to run Storybook: › - Use arrow-keys. Return to submit.
Android
❯ iOS
✨ Both
Our developers can now experiment with different looks on the blurRadius
setting:
Note
When combining custom registered elements (like SwiftUI) with Storybook stories, be sure you setup a common place to register them so your stories can pick them up as well. For example, with NativeScript for Angular, the polyfills.ts
is a great spot to do so since it's loaded in the app normally and also included in stories by default.
You can create a register-elements.ts
which has any custom element registrations:
import { registerElement } from '@nativescript/angular'
import { registerSwiftUI, SwiftUI, UIDataDriver } from '@nativescript/swift-ui'
registerElement('SwiftUI', () => SwiftUI)
registerSwiftUI(
'foggyColors',
(view) => new UIDataDriver(FoggyColorsViewProvider.alloc().init(), view)
)
You can then include that at the bottom of the polyfills.ts
like this:
import './register-elements'
They will now get included in all your stories!
Finish your Story
We can add as many controls as desired to bring our Storybook to life for our team. This can not only help document all the rich UI developed within an app but also allow teams to experiment with different looks and behavior faster without having to dive through entire app flows.
An example of more controls exposed to our Foggy Colors component story:
export default {
title: 'Foggy Colors',
component: FoggyColorsComponent,
argTypes: {
blurRadius: { control: { type: 'range', min: 1, max: 50, step: 1 } },
globalOpacity: { control: { type: 'range', min: 0.1, max: 1, step: 0.1 } },
elementOpacity: { control: { type: 'range', min: 0.1, max: 1, step: 0.1 } },
numberShapes: { control: { type: 'range', min: 1, max: 30, step: 1 } },
color1Top: { control: 'color' },
color1Bottom: { control: 'color' },
color2Top: { control: 'color' },
color2Bottom: { control: 'color' },
color3Top: { control: 'color' },
color3Bottom: { control: 'color' },
color4Top: { control: 'color' },
color4Bottom: { control: 'color' },
color5Top: { control: 'color' },
color5Bottom: { control: 'color' },
color6Top: { control: 'color' },
color6Bottom: { control: 'color' },
color7Top: { control: 'color' },
color7Bottom: { control: 'color' },
},
} as Meta