Storyteller
Storyteller is a library for the discovery and rendering of UI stories and powers our storybook plugin Flipbook.
The API for this package focuses around the following areas:
- Validation for the Story format and Storybook format
- Discovery of valid ModuleScripts with
.storyand.storybookextensions - Loading of Stories and Storybooks into a sandbox with cacheless module requiring
- Rendering stories into a container with lifecycle callbacks for updating and unmounting
There also exist React hooks for ease of integration into storybook apps.
Functions
findOrphanedStoryModules
StorybookStoryDiscoveryDiscovers all Story modules that do not have a Storybook.
These are all of the Story modules that are not descendants of an instance
in the storyRoots array of the given storybooks.
render
RenderingRender a Story to an Instance in the DataModel.
After discovering, validating, and loading Story modules, rendering them is the final step to getting stories visually presented to the user.
This function handles the lifecycle of mounting, updating, and unmounting the Story. On each update, controls can be passed down to the story for live-reloading from user interaction.
Usage:
local ModuleLoader = require("@pkg/ModuleLoader")
local Storyteller = require("@pkg/Storyteller")
-- At least one `.storybook` module must be present
local storybookModules = Storyteller.findStorybookModules(game)
assert(#storybookModules > 0, "no Storybook modules found")
local storybook
pcall(function()
storybook = Storyteller.loadStorybookModule(storybookModules[1])
end)
if storybook then
-- At least one `.story` module must be a descendant of the Instances in
-- a Storybook's `storyRoots` array
local storyModules = Storyteller.findStoryModulesForStorybook(storybook)
assert(#storyModules > 0, "no Story modules found")
local story
pcall(function()
story = Storyteller.loadStoryModule(storyModules[1], storybook)
end)
if story then
-- Finally, render the story to a container of your choosing
local lifecycle = Storyteller.render(container, story)
print(container:GetChildren())
lifecycle.unmount()
print(container:GetChildren())
end
end
useStory
ReactStoryStoryteller.useStory(storybook: types.LoadedStorybook) → (types.LoadedStory<unknown>?,string?)
This hook triggers a rerender when the Story module or any of its required
modules change. For example, updating the story property or updating a
React component’s source will trigger useStory to rerender with the new
content.
info
In the future version hooks may be migrated to a new package to remove the React dependency from Storyteller.
Usage:
local React = require("@pkg/React")
local Storyteller = require("@pkg/Storyteller")
local useEffect = React.useEffect
local useRef = React.useRef
local e = React.createElement
local function StoryView(props: {
parent: Instance,
storyModule: ModuleScript,
storybook: Storybook,
})
local ref = useRef(nil :: Frame?)
local story = Storyteller.useStory(props.storyModule, props.storybook)
useEffect(function()
if ref.current then
local renderer = Storyteller.createRendererForStory(story)
Storyteller.render(renderer, ref.current, story)
end
end, { story })
return e("Frame", {
Size = UDim2.fromScale(1, 1),
BackgroundTransparency = 1,
ref = ref,
})
end
return StoryView
useStorybooks
ReactStorybookStoryteller.useStorybooks(parent: Instance) → {available: {LoadedStorybook},unavailable: {UnavailableStorybook},}Performs all the discovery and loading of Storybook modules that would normally be done via individual API members.
This hook makes it possible to conveniently load (and reload) Storybooks for use in React UI.
info
In the future version hooks may be migrated to a new package to remove the React dependency from Storyteller.
Usage:
local React = require("@pkg/React")
local Storyteller = require("@pkg/Storyteller")
local e = React.createElement
local function StorybookList(props: {
parent: Instance,
})
local storybooks = Storyteller.useStorybooks(props.parent)
local children = {}
for index, storybook in storybooks do
children[storybook.name] = e("TextLabel", {
Text = storybook.name,
LayoutOrder = index,
}),
end
return e("Frame", {
Size = UDim2.fromScale(1, 1),
BackgroundTransparency = 1,
}, {
Layout = e("UIListLayout", {
SortOrder = Enum.SortOrder.LayoutOrder
}),
}, children)
end
return StorybookList
This hook triggers a rerender when a Storybook module changes. For example,
updating the storyRoots of a Storybook will trigger a rerender, and when
paired with useStory you can get live updates to which Stories a Storybook
manages.
loadStorybookModule
StorybookModule LoadingStoryteller.loadStorybookModule(providedLoader: ModuleLoader?) → LoadedStorybookLoads the source of a Storybook module.
A ModuleLoader instance is required for handling the requiring of the module.
This function will throw if the return value of storybookModule does not
conform to Storybook format, or if the
source has a syntax error that require would normally fail for.
findStorybookModules
StorybookDiscoveryDiscovers all Storybook modules that are descendants of parent.
This is the first step in the discovery of Stories. Once you load a
Storybook, you can then use its storyRoots array to discover all the
Stories it manages.
loadStoryModule
StoryModule LoadingLoads the source of a Story module.
A ModuleLoader instance is required for handling the requiring of the module.
This function will throw if the return value of storyModule does not
conform to the Story format, or if the source
has a syntax error that require would normally fail for.
For legacy compatibility this function also loads Hoarcekat
stories. Instead of a usual table-based Story definition, it takes the
returned function and wraps it in a Story, making the story field the
function body.
findStoryModulesForStorybook
StorybookStoryDiscoveryDiscovers all Story modules that are managed by the given Storybook.
isStoryModule
ValidationStoryValidates a given Instance is a Story.
No validation is performed on the internals of the module. Only name and class are checked.
See Storyteller.loadStoryModule for validation of the module source.
isStorybookModule
ValidationStorybookValidates a given Instance is a Storybook module.
No validation is performed on the internals of the module. Only name and class are checked.
See Storyteller.loadStorybookModule for validation of the module source.