name storybook-creation description Create or update Storybook stories for Daedalus React components. Use this when asked to write stories, visual tests, storybook entries, or component demos for new or existing features.
Storybook Creation Skill
Create or update Storybook stories for the Daedalus wallet application. This skill covers writing .stories.tsx files, domain wrappers, mock data, and registering stories — following existing project conventions.
Storybook Stack
Component Technology Storybook 6.4 with webpack5 builder Story API storiesOf() (legacy API — NOT CSF)Knobs @storybook/addon-knobsActions @storybook/addon-actionsState Management @dump247/storybook-state (withState)Custom Addon DaedalusMenu (theme/locale/OS switcher)Styling SCSS Modules (co-located with components) i18n react-intl (en-US, ja-JP)Component Library react-polymorph (via StoryDecorator)
Important: Daedalus uses the storiesOf() API, NOT Component Story Format (CSF). Do NOT use export default { title: ... } or named exports.
Directory Structure
Centralized Stories (default)
Stories are organized by domain under storybook/stories/:
storybook/stories/
├── index.ts # Master entry — imports all story files
├── _support/ # Shared infrastructure
│ ├── StoryWrapper.tsx # Top-level wrapper (theme/locale/OS)
│ ├── StoryDecorator.tsx # react-polymorph ThemeProvider
│ ├── StoryProvider.tsx # MobX provider + mock data
│ ├── StoryLayout.tsx # Full app UI shell (sidebar/topbar)
│ ├── config.ts # Theme/locale/OS constants
│ ├── utils.ts # Mock data generators
│ └── environment.ts # Environment stubs
├── common/ # Shared widgets, dropdowns
├── wallets/ # Wallet features (send, receive, tokens...)
│ ├── _utils/ # WalletsWrapper domain decorator
│ ├── index.ts # Domain barrel import
│ ├── send/
│ │ └── WalletSend.stories.tsx
│ └── ...
├── staking/ # Delegation, stake pools
├── voting/ # Voting features
├── settings/ # Profile, preferences
├── assets/ # Native tokens, NFTs
├── news/ # Newsfeed, alerts, overlays
├── navigation/ # Sidebar, categories
├── notifications/ # System notifications
├── dapps/ # dApp connector
└── nodes/ # Node status
Co-located Stories (feature modules)
For self-contained feature modules, stories can live next to the component:
source/renderer/app/features/{feature}/ui/
├── MyComponent.tsx
├── MyComponent.scss
└── MyComponent.story.tsx # Co-located story
When to Use Which Pattern
Pattern When to Use Centralized Default for all domain-level features (wallets, staking, etc.) Co-located Self-contained feature modules under source/renderer/app/features/
Step 1: Determine Story Location
New domain in storybook/stories/
Create a new directory and optionally a domain barrel file:
storybook/stories/{domain}/
├── index.ts # Optional barrel import
├── {FeatureName}.stories.tsx # Story file
└── _utils/ # Optional domain wrapper
└── {Domain}Wrapper.tsx
Existing domain
Add the story file to the appropriate subdirectory:
storybook/stories/{domain}/{sub-feature}/
└── {FeatureName}.stories.tsx
Co-located feature story
source/renderer/app/features/{feature}/ui/
└── {ComponentName}.story.tsx
Step 2: Write the Story File
File Naming
Pattern Convention Example Centralized {ComponentName}.stories.tsxWalletSend.stories.tsxCo-located {ComponentName}.story.tsxDiscreetModeToggle.story.tsx
Story Naming Convention
Use storiesOf('Domain / Sub-feature', module) with / as separator:
storiesOf ('Wallets / Send' , module )
storiesOf ('Common / Widgets' , module )
storiesOf ('Staking' , module )
storiesOf ('Discreet Mode / Discreet Mode Toggle' , module )
Minimal Story Template
import React from 'react' ;
import { storiesOf } from '@storybook/react' ;
import { action } from '@storybook/addon-actions' ;
import { withKnobs, boolean , text, number } from '@storybook/addon-knobs' ;
import MyComponent from '../../../../source/renderer/app/components/{domain}/MyComponent' ;
storiesOf ('{Domain} / {Feature}' , module )
.addDecorator (withKnobs)
.add ('default' , () => (
<MyComponent
label ={text( 'Label ', 'Default ')}
disabled ={boolean( 'Disabled ', false )}
onClick ={action( 'onClick ')}
/>
));
Full Story Template (with decorators + state)
import React from 'react' ;
import { storiesOf } from '@storybook/react' ;
import { action } from '@storybook/addon-actions' ;
import { observable, action as mobxAction } from 'mobx' ;
import {
withKnobs,
boolean ,
text,
number ,
select,
} from '@storybook/addon-knobs' ;
import StoryDecorator from '../_support/StoryDecorator' ;
import StoryProvider from '../_support/StoryProvider' ;
import StoryLayout from '../_support/StoryLayout' ;
import MyComponent from '../../../../source/renderer/app/components/{domain}/MyComponent' ;
storiesOf ('{Domain} / {Feature}' , module )
.addDecorator ((story : any , context : any ) => {
const onChangeAction = action ('onChange' );
const state = observable ({
: ,
: ( {
state. = newValue;
(newValue);
}),
});
(
);
})
. (withKnobs)
. ( , (
))
. ( , (
))
. ( , (
));
Story with withState (for interactive two-way binding)
import { withState } from '@dump247/storybook-state' ;
storiesOf ('{Domain} / {Feature}' , module )
.addDecorator (withKnobs)
.add (
'interactive' ,
withState ({ value : '' }, (store ) => (
<MyInput
value ={store.state.value}
onChange ={(value) => store.set({ value })}
/>
))
);
Step 3: Use Support Infrastructure
Decorator Stack
The standard decorator stack wraps stories with theme, intl, and layout:
Decorator Purpose Import Path StoryWrapperTop-level: theme, locale, OS selection (auto-applied) ../_support/StoryWrapperStoryDecoratorreact-polymorph ThemeProvider + skin ../_support/StoryDecoratorStoryProviderMobX store provider + mock data ../_support/StoryProviderStoryLayoutFull app shell (sidebar, topbar, content area) ../_support/StoryLayout
StoryWrapper is automatically applied via storybook/preview.tsx — do NOT add it manually.
When to use each:
Simple component (no app context needed): Just withKnobs decorator — no StoryDecorator/Provider/Layout.
Component needing theme : Add StoryDecorator.
Component needing MobX stores : Add StoryDecorator + StoryProvider.
Full page/screen component : Add all three: StoryDecorator + StoryProvider + StoryLayout.
Domain Wrappers
For domains with shared layout (e.g., wallets with navigation tabs), create a domain wrapper:
import React from 'react' ;
import { withKnobs } from '@storybook/addon-knobs' ;
import StoryLayout from '../../_support/StoryLayout' ;
import StoryProvider from '../../_support/StoryProvider' ;
import StoryDecorator from '../../_support/StoryDecorator' ;
export default function (story : any , context : any ) {
const storyWithKnobs = withKnobs (story, context);
return (
<StoryDecorator >
<StoryProvider >
<StoryLayout activeSidebarCategory ="/{domain}" {...context }>
{storyWithKnobs}
</StoryLayout >
</StoryProvider >
</StoryDecorator >
);
}
Use it in stories:
import DomainWrapper from '../_utils/DomainWrapper' ;
storiesOf ('{Domain} / {Feature}' , module )
.addDecorator (DomainWrapper )
.add ('default' , () => <MyComponent /> );
activeSidebarCategory Values
Category Value Wallets /walletsStaking /stakingSettings /settingsVoting /votingNone/Other null
Step 4: Mock Data
Available Mock Data Generators
Import from storybook/stories/_support/utils.ts:
import {
generateHash,
generatePolicyIdHash,
generateWallet,
generateTransaction,
generateAssetToken,
generateAssetDomain,
generateRewardForWallet,
EXAMPLE_METADATA ,
} from '../_support/utils' ;
Generator Returns Key Parameters generateHash()SHA-512 hex string (none) generatePolicyIdHash()SHA-224 hex string (none) generateWallet()Wallet instancename, amount, assets, reward, delegatedStakePool, hasPassword, status, isHardwareWallet, idgenerateTransaction()WalletTransactiontype, date, amount, deposit, state, ...generateAssetToken()AssetToken objectpolicyId, assetName, fingerprint, quantity, metadatagenerateAssetDomain()Asset instancepolicyId, assetName, fingerprint, metadatagenerateRewardForWallet()Reward objectwallet, unspent
Example mock data setup
import BigNumber from 'bignumber.js' ;
import { generateWallet, generateAssetToken, generateHash } from '../_support/utils' ;
import Wallet , { WalletSyncStateStatuses } from '../../../../source/renderer/app/domains/Wallet' ;
const wallet = generateWallet ('Test Wallet' , '500000000' );
const syncingWallet = generateWallet (
'Syncing Wallet' ,
'100000000' ,
{ available : [], total : [] },
0 ,
null ,
false ,
WalletSyncStateStatuses .SYNCING
);
const asset = generateAssetToken (
'65bc72542b0ca20391caaf66a4d4e7897d282f9c136cd3513136945c' ,
'' ,
'token_fingerprint_here' ,
100 ,
{ name : 'TestToken' , ticker : 'TT' , description : 'A test token' , url : '' , logo : '' }
);
Step 5: Register the Story
Centralized stories
Add the import to the appropriate barrel file:
If a domain index.ts exists (e.g., storybook/stories/wallets/index.ts):
import './{sub-feature}/{FeatureName}.stories' ;
If no domain index.ts exists , add the import to storybook/stories/index.ts:
import './{domain}/{FeatureName}.stories' ;
Co-located stories
Add the import to storybook/stories/index.ts with the full relative path:
import '../../source/renderer/app/features/{feature}/ui/{ComponentName}.story' ;
Registration Checklist
Step 6: Cover Component States
Every story file should demonstrate the component's key visual states:
Required States
storiesOf ('{Domain} / {Feature}' , module )
.addDecorator ()
.add ('default' , () => <Component /* happy path props */ /> )
.add ('loading' , () => <Component isLoading /> )
.add ('empty' , () => <Component items ={[]} /> )
.add ('error' , () => <Component error ="Something went wrong" /> );
Recommended Additional States
State When to Include disabledComponent can be disabled with dataComponent needs populated mock data hardware walletComponent behaves differently for HW wallets syncingComponent shows sync progress restoringComponent shows restore progress legacy walletComponent has legacy wallet variant with knobsInteractive prop editing for complex components
Knobs Reference
Use @storybook/addon-knobs for interactive props:
import {
withKnobs,
text,
boolean ,
number ,
select,
date,
object ,
array,
} from '@storybook/addon-knobs' ;
Updating Existing Stories
When updating stories for an existing feature:
Find the existing story file — search storybook/stories/ or source/renderer/app/features/ for .stories.tsx or .story.tsx files.
Add new story entries — append .add() calls for new states/variants.
Update mock data — if the component's props changed, update mock data to match.
Update imports — if new dependencies are needed, add them.
Do NOT change the storiesOf() title unless the feature was renamed.
Adding a new state to an existing story
.add ('new state name' , () => (
<ExistingComponent
newProp ={text( 'New Prop ', 'value ')}
/* existing required props */
/>
));
Internationalization in Stories
For components using react-intl messages:
import { defineMessages, IntlProvider } from 'react-intl' ;
import enMessages from '../../../../source/renderer/app/i18n/locales/en-US.json' ;
import jpMessages from '../../../../source/renderer/app/i18n/locales/ja-JP.json' ;
const { intl : enIntl } = new IntlProvider ({
locale : 'en-US' ,
messages : enMessages,
}).getChildContext ();
.add ('with i18n' , (_, props ) => (
<Component
label ={enIntl.formatMessage(messages.myLabel)}
/>
))
Note: StoryWrapper already provides IntlProvider globally. You only need manual intl setup when you need to access intl.formatMessage() directly in story code (not in the component).
Common Pitfalls
Pitfall Solution Story not appearing in Storybook Ensure import is added to index.ts barrel file Blank/white story Missing StoryDecorator (theme provider) — add it as a decorator MobX errors Component needs stores — add StoryProvider decorator Missing sidebar/layout Add StoryLayout decorator with correct activeSidebarCategory Styles not loading SCSS modules are auto-loaded; check component imports storiesOf title not groupingUse / separator (with spaces): 'Domain / Feature' Using CSF exports Daedalus uses storiesOf() API — do NOT use export default Arrow functions in decorators Use function keyword if you need this context
Validation
After creating or updating a story:
Build check : yarn storybook:build — story must compile without errors
Visual check : yarn storybook — story renders correctly at http://localhost:6006
Theme check : Switch themes in the DaedalusMenu addon — verify all themes render properly
Locale check : Switch to Japanese locale — verify i18n strings render
State coverage : Verify all visual states are represented (default, loading, error, empty, etc.)
Quick Reference
New Story Checklist
Determine location (centralized vs co-located)
Create story file with correct naming (*.stories.tsx or *.story.tsx)
Use storiesOf() API with correct domain/feature title
Add appropriate decorators (StoryDecorator, StoryProvider, StoryLayout)
Add withKnobs for interactive props
Use action() for event handler props
Cover all key visual states (default, loading, error, empty)
Use mock data generators from _support/utils.ts
Register story in barrel index.ts
Verify story renders in Storybook