From f5e7cc7c56c3d0ef3253e62b87a39d46af28f2a7 Mon Sep 17 00:00:00 2001 From: Thompson <140930314+Godbrand0@users.noreply.github.com> Date: Wed, 1 Jul 2026 03:58:49 -0400 Subject: [PATCH 1/7] feat(storybook): wire defaultTheme/brandPreset controls into widget stories Adds interactive Storybook controls for defaultTheme and themeOverrides across all four widget showcase stories (StreamingWidget, CitizenClaimWidget, StakingMigrationWidget, ClaimWidget) plus the Card/GlowCard/Stepper/Drawer design-system primitives, so integrators can experiment with theming live in the Controls panel instead of editing story code. Extracts the cobalt/teal brand override presets into a shared helper reused across widgets. --- .../CitizenClaimWidgetShowcase.stories.tsx | 30 +++++++++- .../claim-widget/ClaimWidget.stories.tsx | 58 ++++++++++++++----- .../stories/design-system/Card.stories.tsx | 20 +++++++ .../stories/design-system/Drawer.stories.tsx | 35 +++++++++++ .../design-system/GlowCard.stories.tsx | 18 ++++++ .../stories/design-system/Stepper.stories.tsx | 43 +++++++++++--- .../helpers/citizenClaimWidgetStories.tsx | 23 +++++++- .../stories/helpers/claimWidgetStories.tsx | 34 +---------- .../helpers/stakingMigrationWidgetStories.tsx | 11 +++- .../helpers/streamingWidgetStories.tsx | 12 +++- .../stories/helpers/themeOverridePresets.ts | 54 +++++++++++++++++ ...StakingMigrationWidgetShowcase.stories.tsx | 30 +++++++++- .../StreamingWidget.stories.tsx | 33 +++++++++-- 13 files changed, 330 insertions(+), 71 deletions(-) create mode 100644 examples/storybook/src/stories/helpers/themeOverridePresets.ts diff --git a/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidgetShowcase.stories.tsx b/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidgetShowcase.stories.tsx index 2f150a0..ea1475e 100644 --- a/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidgetShowcase.stories.tsx +++ b/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidgetShowcase.stories.tsx @@ -1,17 +1,41 @@ import type { Meta, StoryObj } from '@storybook/react' import { CitizenClaimWidget } from '@goodwidget/citizen-claim-widget' import { InjectedWalletStory } from '../helpers/citizenClaimWidgetStories' +import { BRAND_PRESET_OPTIONS, brandPresetOverrides, type BrandPreset } from '../helpers/themeOverridePresets' -const meta: Meta = { +interface CitizenClaimWidgetStoryArgs { + defaultTheme: 'light' | 'dark' + brandPreset: BrandPreset +} + +const meta: Meta = { title: 'Widgets/CitizenClaimWidget/Showcase', component: CitizenClaimWidget, tags: ['integrator', 'manual', 'showcase'], parameters: { layout: 'padded' }, + argTypes: { + defaultTheme: { + control: 'radio', + options: ['dark', 'light'], + description: 'Base theme applied via the widget’s own defaultTheme prop.', + }, + brandPreset: { + control: 'select', + options: BRAND_PRESET_OPTIONS, + description: 'Sample host-branding themeOverrides preset.', + }, + }, + args: { + defaultTheme: 'dark', + brandPreset: 'None', + }, } export default meta -type Story = StoryObj +type Story = StoryObj export const InjectedWallet: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } diff --git a/examples/storybook/src/stories/claim-widget/ClaimWidget.stories.tsx b/examples/storybook/src/stories/claim-widget/ClaimWidget.stories.tsx index cf218fd..bd61974 100644 --- a/examples/storybook/src/stories/claim-widget/ClaimWidget.stories.tsx +++ b/examples/storybook/src/stories/claim-widget/ClaimWidget.stories.tsx @@ -7,13 +7,15 @@ */ import type { Meta, StoryObj } from '@storybook/react' import { ClaimWidget } from '@goodwidget/claim-widget-theme-demo' -import { - ClaimWidgetStoryCanvas, - cobaltOverrides, - tealOverrides, -} from '../helpers/claimWidgetStories' +import { ClaimWidgetStoryCanvas } from '../helpers/claimWidgetStories' +import { BRAND_PRESET_OPTIONS, brandPresetOverrides, type BrandPreset } from '../helpers/themeOverridePresets' -const meta: Meta = { +interface ClaimWidgetStoryArgs { + defaultTheme: 'light' | 'dark' + brandPreset: BrandPreset +} + +const meta: Meta = { title: 'Widgets/ClaimWidget Theme Demo/Showcase', component: ClaimWidget, tags: ['integrator', 'showcase'], @@ -22,33 +24,63 @@ const meta: Meta = { disableProvider: true, useShell: false, }, + argTypes: { + defaultTheme: { + control: 'radio', + options: ['dark', 'light'], + description: 'Base theme applied via the widget’s own defaultTheme prop.', + }, + brandPreset: { + control: 'select', + options: BRAND_PRESET_OPTIONS, + description: 'Sample host-branding themeOverrides preset.', + }, + }, } export default meta -type Story = StoryObj +type Story = StoryObj export const Default: Story = { - render: () => , + args: { defaultTheme: 'dark', brandPreset: 'None' }, + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const LightTheme: Story = { - render: () => , + args: { defaultTheme: 'light', brandPreset: 'None' }, + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const CobaltBrand: Story = { - render: () => ( + args: { defaultTheme: 'dark', brandPreset: 'Cobalt' }, + render: ({ defaultTheme, brandPreset }) => ( ), } export const TealBrand: Story = { - render: () => ( + args: { defaultTheme: 'dark', brandPreset: 'Teal' }, + render: ({ defaultTheme, brandPreset }) => ( ), } diff --git a/examples/storybook/src/stories/design-system/Card.stories.tsx b/examples/storybook/src/stories/design-system/Card.stories.tsx index 23b9bef..aae81ac 100644 --- a/examples/storybook/src/stories/design-system/Card.stories.tsx +++ b/examples/storybook/src/stories/design-system/Card.stories.tsx @@ -13,6 +13,12 @@ const meta: Meta = { component: Card, tags: ['autodocs', 'showcase'], parameters: { layout: 'padded' }, + argTypes: { + elevated: { control: 'boolean', description: 'Applies the elevated shadow variant' }, + outlined: { control: 'boolean', description: 'Applies the outlined border variant' }, + backgroundColor: { control: 'color', description: 'Inline background color override' }, + borderColor: { control: 'color', description: 'Inline border color override' }, + }, } export default meta type Story = StoryObj @@ -59,3 +65,17 @@ export const InlineStyled: Story = { ), } + +/** Controllable instance — edit args in the Controls panel. */ +export const Controllable: Story = { + args: { + elevated: true, + outlined: false, + }, + render: (args) => ( + + Controllable Card + Use the Controls panel to toggle variants and colors. + + ), +} diff --git a/examples/storybook/src/stories/design-system/Drawer.stories.tsx b/examples/storybook/src/stories/design-system/Drawer.stories.tsx index 57c73af..7142fa2 100644 --- a/examples/storybook/src/stories/design-system/Drawer.stories.tsx +++ b/examples/storybook/src/stories/design-system/Drawer.stories.tsx @@ -13,6 +13,13 @@ const meta: Meta = { title: 'Design System/Primitives/Drawer', tags: ['autodocs', 'showcase'], parameters: { layout: 'padded' }, + argTypes: { + height: { + control: 'radio', + options: ['half', 'full'], + description: 'How much of the viewport the Drawer covers when open', + }, + }, } export default meta type Story = StoryObj @@ -49,3 +56,31 @@ export const Default: Story = { await expect(await screen.findByRole('button', { name: /close/i })).toBeVisible() }, } + +/** Controllable instance — edit the `height` arg, then click "Open Drawer". */ +export const Controllable: Story = { + args: { + height: 'half', + }, + render: ({ height }: { height?: 'half' | 'full' }) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [open, setOpen] = useState(false) + return ( + + Trigger + A Drawer slides up from the bottom and overlays the content. + + setOpen(false)} height={height}> + + Drawer content. Close via the button below or tap outside. + + + + + ) + }, +} diff --git a/examples/storybook/src/stories/design-system/GlowCard.stories.tsx b/examples/storybook/src/stories/design-system/GlowCard.stories.tsx index 71866d3..e1e5458 100644 --- a/examples/storybook/src/stories/design-system/GlowCard.stories.tsx +++ b/examples/storybook/src/stories/design-system/GlowCard.stories.tsx @@ -13,6 +13,10 @@ const meta: Meta = { component: GlowCard, tags: ['autodocs', 'showcase'], parameters: { layout: 'padded' }, + argTypes: { + elevated: { control: 'boolean', description: 'Applies the elevated shadow variant' }, + outlined: { control: 'boolean', description: 'Applies the outlined border variant' }, + }, } export default meta type Story = StoryObj @@ -29,3 +33,17 @@ export const Default: Story = { ), } + +/** Controllable instance — edit args in the Controls panel. */ +export const Controllable: Story = { + args: { + elevated: true, + outlined: false, + }, + render: (args) => ( + + Controllable GlowCard + Use the Controls panel to toggle variants. + + ), +} diff --git a/examples/storybook/src/stories/design-system/Stepper.stories.tsx b/examples/storybook/src/stories/design-system/Stepper.stories.tsx index f1d4de7..995d4a8 100644 --- a/examples/storybook/src/stories/design-system/Stepper.stories.tsx +++ b/examples/storybook/src/stories/design-system/Stepper.stories.tsx @@ -2,25 +2,33 @@ import React from 'react' import type { Meta, StoryObj } from '@storybook/react' import { Stepper, Text, YStack, type StepperStepItem } from '@goodwidget/ui' +const STEPS: StepperStepItem[] = [ + { id: 'connect', title: 'Connect wallet', status: 'completed' }, + { id: 'approve', title: 'Approve transaction', status: 'completed' }, + { id: 'submit', title: 'Submit migration', status: 'active', description: 'Waiting for wallet confirmation.' }, + { id: 'bridge', title: 'Bridge to Celo', status: 'pending' }, + { id: 'stake', title: 'Stake on Celo', status: 'pending' }, + { id: 'confirm', title: 'Confirm receipt', status: 'pending' }, +] + const meta: Meta = { title: 'Design System/Primitives/Stepper', component: Stepper, tags: ['autodocs', 'showcase'], parameters: { layout: 'padded' }, + argTypes: { + activeStepId: { + control: 'select', + options: STEPS.map((step) => step.id), + description: 'Which step is highlighted as active', + }, + maxHeight: { control: 'number', description: 'Max height of the scrollable step list' }, + }, } export default meta type Story = StoryObj -const STEPS: StepperStepItem[] = [ - { id: 'connect', title: 'Connect wallet', status: 'completed' }, - { id: 'approve', title: 'Approve transaction', status: 'completed' }, - { id: 'submit', title: 'Submit migration', status: 'active', description: 'Waiting for wallet confirmation.' }, - { id: 'bridge', title: 'Bridge to Celo', status: 'pending' }, - { id: 'stake', title: 'Stake on Celo', status: 'pending' }, - { id: 'confirm', title: 'Confirm receipt', status: 'pending' }, -] - export const Default: Story = { render: () => ( @@ -33,3 +41,20 @@ export const Default: Story = { ), } + +/** Controllable instance — edit args in the Controls panel. */ +export const Controllable: Story = { + args: { + activeStepId: 'submit', + maxHeight: 280, + }, + render: (args) => ( + + Transaction steps} + {...args} + /> + + ), +} diff --git a/examples/storybook/src/stories/helpers/citizenClaimWidgetStories.tsx b/examples/storybook/src/stories/helpers/citizenClaimWidgetStories.tsx index 196603d..fb6b2f0 100644 --- a/examples/storybook/src/stories/helpers/citizenClaimWidgetStories.tsx +++ b/examples/storybook/src/stories/helpers/citizenClaimWidgetStories.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react' import { Card, Text, WidgetTabs, YStack } from '@goodwidget/ui' -import { CitizenClaimWidget } from '@goodwidget/citizen-claim-widget' +import { CitizenClaimWidget, type CitizenClaimWidgetProps } from '@goodwidget/citizen-claim-widget' import { getInjectedEip1193Provider, isInjectedProviderUsable, @@ -12,9 +12,13 @@ type CitizenClaimTab = 'claim' | 'invite-rewards' | 'news-feed' function CitizenClaimWidgetStoryShell({ provider, dataTestId, + defaultTheme, + themeOverrides, }: { provider: unknown dataTestId: string + defaultTheme?: 'light' | 'dark' + themeOverrides?: CitizenClaimWidgetProps['themeOverrides'] }) { const [activeTab, setActiveTab] = useState('claim') const [activeChainId, setActiveChainId] = useState(null) @@ -56,7 +60,12 @@ function CitizenClaimWidgetStoryShell({ /> {activeTab === 'claim' ? ( - + ) : ( @@ -68,7 +77,13 @@ function CitizenClaimWidgetStoryShell({ ) } -export function InjectedWalletStory() { +export function InjectedWalletStory({ + defaultTheme, + themeOverrides, +}: { + defaultTheme?: 'light' | 'dark' + themeOverrides?: CitizenClaimWidgetProps['themeOverrides'] +} = {}) { const injectedProvider = getInjectedEip1193Provider() const usableProvider = isInjectedProviderUsable(injectedProvider) @@ -88,6 +103,8 @@ export function InjectedWalletStory() { ) } diff --git a/examples/storybook/src/stories/helpers/claimWidgetStories.tsx b/examples/storybook/src/stories/helpers/claimWidgetStories.tsx index 8f92ab6..ba692bf 100644 --- a/examples/storybook/src/stories/helpers/claimWidgetStories.tsx +++ b/examples/storybook/src/stories/helpers/claimWidgetStories.tsx @@ -6,39 +6,7 @@ import { createMockEip1193Provider } from '../../fixtures/mockEip1193' export const mockProvider = createMockEip1193Provider() -export const cobaltOverrides = { - tokens: { - color: { - primary: '#2E5DE8', - primaryDark: '#1D3EB2', - primaryLight: '#6E8DFF', - }, - }, - themes: { - dark_ClaimCard: { borderColor: '#2E5DE8', shadowColor: 'rgba(46,93,232,0.7)' }, - dark_ClaimActionGlow: { primary: '#4F7DFF', primaryLight: '#9DB4FF' }, - dark_ClaimActionRing: { primary: '#2E5DE8', primaryLight: '#6E8DFF' }, - dark_ClaimActionInner: { backgroundDark: '#0E1A3A', backgroundDarkHover: '#172B60' }, - dark_TokenAmountText: { color: '#BBD0FF', secondaryColor: '#7FA2FF' }, - }, -} - -export const tealOverrides = { - tokens: { - color: { - primary: '#00A884', - primaryDark: '#007A61', - primaryLight: '#33C9AA', - }, - }, - themes: { - dark_ClaimCard: { borderColor: '#00A884', shadowColor: 'rgba(0,168,132,0.65)' }, - dark_ClaimActionGlow: { primary: '#33C9AA', primaryLight: '#78E0CB' }, - dark_ClaimActionRing: { primary: '#00A884', primaryLight: '#33C9AA' }, - dark_ClaimActionInner: { backgroundDark: '#062A23', backgroundDarkHover: '#0B3B31' }, - dark_TokenAmountText: { color: '#BFF5E7', secondaryColor: '#66D5BB' }, - }, -} +export { cobaltOverrides, tealOverrides } from './themeOverridePresets' interface ClaimWidgetStoryCanvasProps { config?: React.ComponentProps['config'] diff --git a/examples/storybook/src/stories/helpers/stakingMigrationWidgetStories.tsx b/examples/storybook/src/stories/helpers/stakingMigrationWidgetStories.tsx index b8c000f..d925fc8 100644 --- a/examples/storybook/src/stories/helpers/stakingMigrationWidgetStories.tsx +++ b/examples/storybook/src/stories/helpers/stakingMigrationWidgetStories.tsx @@ -7,6 +7,7 @@ import { derivePrimaryLabel, type MigrationStep, type StakingMigrationWidgetAdapterFactory, + type StakingMigrationWidgetProps, type StakingMigrationWidgetState, type StakingMigrationWidgetStatus, } from '@goodwidget/staking-migration-widget' @@ -99,7 +100,13 @@ function MockStoryShell({ } } -export function InjectedWalletStory() { +export function InjectedWalletStory({ + defaultTheme, + themeOverrides, +}: { + defaultTheme?: 'light' | 'dark' + themeOverrides?: StakingMigrationWidgetProps['themeOverrides'] +} = {}) { const injectedProvider = getInjectedEip1193Provider() const migrationApiBaseUrl = import.meta.env.VITE_MIGRATION_API_BASE_URL @@ -120,6 +127,8 @@ export function InjectedWalletStory() { {!migrationApiBaseUrl && ( diff --git a/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx b/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx index 21dfb3b..e1761b3 100644 --- a/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx +++ b/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx @@ -234,11 +234,13 @@ function StreamingWidgetStoryShell({ dataTestId, apiKey, defaultTheme, + themeOverrides, }: { provider: unknown dataTestId: string apiKey?: string defaultTheme?: 'light' | 'dark' + themeOverrides?: StreamingWidgetProps['themeOverrides'] }) { const trimmedApiKey = apiKey?.trim() @@ -249,12 +251,17 @@ function StreamingWidgetStoryShell({ environment="production" apiKey={trimmedApiKey || undefined} defaultTheme={defaultTheme} + themeOverrides={themeOverrides} /> ) } -export function InjectedWalletStory({ apiKey }: Pick) { +export function InjectedWalletStory({ + apiKey, + defaultTheme, + themeOverrides, +}: Pick) { const injectedProvider = getInjectedEip1193Provider() const usableProvider = isInjectedProviderUsable(injectedProvider) @@ -272,6 +279,7 @@ export function InjectedWalletStory({ apiKey }: Pick ) } @@ -281,6 +289,8 @@ export function InjectedWalletStory({ apiKey }: Pick ) } diff --git a/examples/storybook/src/stories/helpers/themeOverridePresets.ts b/examples/storybook/src/stories/helpers/themeOverridePresets.ts new file mode 100644 index 0000000..5d7deef --- /dev/null +++ b/examples/storybook/src/stories/helpers/themeOverridePresets.ts @@ -0,0 +1,54 @@ +import type { GoodWidgetThemeOverrides } from '@goodwidget/ui' + +/** + * Shared "brand preset" overrides used to drive the `brandPreset` Storybook + * control across widget showcase stories, demonstrating the host override + * surface with a couple of concrete brand colors. + */ +export const cobaltOverrides: GoodWidgetThemeOverrides = { + tokens: { + color: { + primary: '#2E5DE8', + primaryDark: '#1D3EB2', + primaryLight: '#6E8DFF', + }, + }, + themes: { + dark_ClaimCard: { borderColor: '#2E5DE8', shadowColor: 'rgba(46,93,232,0.7)' }, + dark_ClaimActionGlow: { primary: '#4F7DFF', primaryLight: '#9DB4FF' }, + dark_ClaimActionRing: { primary: '#2E5DE8', primaryLight: '#6E8DFF' }, + dark_ClaimActionInner: { backgroundDark: '#0E1A3A', backgroundDarkHover: '#172B60' }, + dark_TokenAmountText: { color: '#BBD0FF', secondaryColor: '#7FA2FF' }, + }, +} + +export const tealOverrides: GoodWidgetThemeOverrides = { + tokens: { + color: { + primary: '#00A884', + primaryDark: '#007A61', + primaryLight: '#33C9AA', + }, + }, + themes: { + dark_ClaimCard: { borderColor: '#00A884', shadowColor: 'rgba(0,168,132,0.65)' }, + dark_ClaimActionGlow: { primary: '#33C9AA', primaryLight: '#78E0CB' }, + dark_ClaimActionRing: { primary: '#00A884', primaryLight: '#33C9AA' }, + dark_ClaimActionInner: { backgroundDark: '#062A23', backgroundDarkHover: '#0B3B31' }, + dark_TokenAmountText: { color: '#BFF5E7', secondaryColor: '#66D5BB' }, + }, +} + +export const BRAND_PRESET_OPTIONS = ['None', 'Cobalt', 'Teal'] as const +export type BrandPreset = (typeof BRAND_PRESET_OPTIONS)[number] + +export function brandPresetOverrides(preset: BrandPreset | undefined): GoodWidgetThemeOverrides | undefined { + switch (preset) { + case 'Cobalt': + return cobaltOverrides + case 'Teal': + return tealOverrides + default: + return undefined + } +} diff --git a/examples/storybook/src/stories/staking-migration-widget/StakingMigrationWidgetShowcase.stories.tsx b/examples/storybook/src/stories/staking-migration-widget/StakingMigrationWidgetShowcase.stories.tsx index 236fb33..233753a 100644 --- a/examples/storybook/src/stories/staking-migration-widget/StakingMigrationWidgetShowcase.stories.tsx +++ b/examples/storybook/src/stories/staking-migration-widget/StakingMigrationWidgetShowcase.stories.tsx @@ -1,16 +1,38 @@ import type { Meta, StoryObj } from '@storybook/react' import { StakingMigrationWidget } from '@goodwidget/staking-migration-widget' import { InjectedWalletStory } from '../helpers/stakingMigrationWidgetStories' +import { BRAND_PRESET_OPTIONS, brandPresetOverrides, type BrandPreset } from '../helpers/themeOverridePresets' -const meta: Meta = { +interface StakingMigrationWidgetStoryArgs { + defaultTheme: 'light' | 'dark' + brandPreset: BrandPreset +} + +const meta: Meta = { title: 'Widgets/StakingMigrationWidget/Showcase', component: StakingMigrationWidget, tags: ['integrator', 'manual', 'showcase'], parameters: { layout: 'padded' }, + argTypes: { + defaultTheme: { + control: 'radio', + options: ['dark', 'light'], + description: 'Base theme applied via the widget’s own defaultTheme prop.', + }, + brandPreset: { + control: 'select', + options: BRAND_PRESET_OPTIONS, + description: 'Sample host-branding themeOverrides preset.', + }, + }, + args: { + defaultTheme: 'dark', + brandPreset: 'None', + }, } export default meta -type Story = StoryObj +type Story = StoryObj export const InjectedWallet: Story = { parameters: { @@ -18,5 +40,7 @@ export const InjectedWallet: Story = { useShell: false, }, }, - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } diff --git a/examples/storybook/src/stories/streaming-widget/StreamingWidget.stories.tsx b/examples/storybook/src/stories/streaming-widget/StreamingWidget.stories.tsx index f93bd51..5eb914d 100644 --- a/examples/storybook/src/stories/streaming-widget/StreamingWidget.stories.tsx +++ b/examples/storybook/src/stories/streaming-widget/StreamingWidget.stories.tsx @@ -1,8 +1,15 @@ import type { Meta, StoryObj } from '@storybook/react' -import { StreamingWidget, type StreamingWidgetProps } from '@goodwidget/streaming-widget' +import { StreamingWidget } from '@goodwidget/streaming-widget' import { InjectedWalletStory } from '../helpers/streamingWidgetStories' +import { BRAND_PRESET_OPTIONS, brandPresetOverrides, type BrandPreset } from '../helpers/themeOverridePresets' -const meta: Meta = { +interface StreamingWidgetStoryArgs { + apiKey?: string + defaultTheme: 'light' | 'dark' + brandPreset: BrandPreset +} + +const meta: Meta = { title: 'Widgets/StreamingWidget/Showcase', component: StreamingWidget, tags: ['integrator', 'manual', 'showcase'], @@ -14,14 +21,26 @@ const meta: Meta = { description: 'Optional TheGraph key passed to the SDK-backed streaming adapter for Base SUP reserve queries.', }, + defaultTheme: { + control: 'radio', + options: ['dark', 'light'], + description: 'Base theme applied via the widget’s own defaultTheme prop.', + }, + brandPreset: { + control: 'select', + options: BRAND_PRESET_OPTIONS, + description: 'Sample host-branding themeOverrides preset.', + }, }, args: { apiKey: '', + defaultTheme: 'dark', + brandPreset: 'None', }, } export default meta -type Story = StoryObj +type Story = StoryObj export const InjectedWallet: Story = { parameters: { @@ -29,7 +48,11 @@ export const InjectedWallet: Story = { useShell: false, }, }, - render: ({ apiKey }: Pick) => ( - + render: ({ apiKey, defaultTheme, brandPreset }) => ( + ), } From 6572b7cbf10135b2dd36920583b6f4ddac05b891 Mon Sep 17 00:00:00 2001 From: Thompson <140930314+Godbrand0@users.noreply.github.com> Date: Wed, 1 Jul 2026 05:41:51 -0400 Subject: [PATCH 2/7] docs(storybook): add integration snippets to widget MDX guides CitizenClaimWidget.mdx and StakingMigrationWidget.mdx were missing the "How to mount it" code snippet that ClaimWidget.mdx already has, so integrators had no copy-paste starting point for those two widgets. Adds the same Source block pattern to both, and adds a one-line note to all three widget docs pages (CitizenClaimWidget, StakingMigrationWidget, StreamingWidget) pointing at the new defaultTheme/themeOverrides Storybook controls added in this branch. --- .../CitizenClaimWidget.mdx | 28 +++++++++++++++++-- .../StakingMigrationWidget.mdx | 28 +++++++++++++++++-- .../streaming-widget/StreamingWidget.mdx | 2 +- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidget.mdx b/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidget.mdx index 177240e..4df267a 100644 --- a/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidget.mdx +++ b/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidget.mdx @@ -1,4 +1,4 @@ -import { Canvas, Meta } from '@storybook/blocks'; +import { Canvas, Meta, Source } from '@storybook/blocks'; import * as ShowcaseStories from './CitizenClaimWidgetShowcase.stories'; import { DocsCallout, DocsCard, DocsGrid, DocsPage, DocsSection } from '../docs/DocsLayout'; @@ -11,11 +11,35 @@ import { DocsCallout, DocsCard, DocsGrid, DocsPage, DocsSection } from '../docs/ > + + + + + ) +}`} + /> + + + + + + + ) +}`} + /> + + From 56bec229ae658e37d629406c0ce66f0c739ca07e Mon Sep 17 00:00:00 2001 From: Thompson <140930314+Godbrand0@users.noreply.github.com> Date: Wed, 1 Jul 2026 05:42:07 -0400 Subject: [PATCH 3/7] fix(storybook): Stepper Controllable story ignored activeStepId control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found during manual visual verification of issue #64 (each control must visibly change what renders): the Stepper Controllable story passed a static STEPS array with hardcoded per-step `status` values, so moving the `activeStepId` control only auto-scrolled the list — the blue "active" highlight stayed pinned to whichever step had status:'active' in the array (always "Submit migration"), regardless of the selected control value. Now recomputes each step's status relative to the chosen activeStepId so the control drives a real state change. --- .../stories/design-system/Stepper.stories.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/storybook/src/stories/design-system/Stepper.stories.tsx b/examples/storybook/src/stories/design-system/Stepper.stories.tsx index 995d4a8..4cb12fa 100644 --- a/examples/storybook/src/stories/design-system/Stepper.stories.tsx +++ b/examples/storybook/src/stories/design-system/Stepper.stories.tsx @@ -42,18 +42,29 @@ export const Default: Story = { ), } +/** Recomputes each step's status relative to the chosen active step, so moving the + * `activeStepId` control actually restyles the list instead of only scrolling to it. */ +function stepsWithActive(activeStepId: string): StepperStepItem[] { + const activeIndex = STEPS.findIndex((step) => step.id === activeStepId) + return STEPS.map((step, index) => ({ + ...step, + status: index < activeIndex ? 'completed' : index === activeIndex ? 'active' : 'pending', + })) +} + /** Controllable instance — edit args in the Controls panel. */ export const Controllable: Story = { args: { activeStepId: 'submit', maxHeight: 280, }, - render: (args) => ( + render: ({ activeStepId, maxHeight }) => ( Transaction steps} - {...args} + maxHeight={maxHeight} /> ), From 4493468e204941b1fb51c78560f054353ffa57dd Mon Sep 17 00:00:00 2001 From: Thompson <140930314+Godbrand0@users.noreply.github.com> Date: Wed, 1 Jul 2026 06:32:06 -0400 Subject: [PATCH 4/7] fix(storybook): brandPreset control was a no-op for StreamingWidget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found during manual verification: StreamingWidget's card surfaces use their own named Tamagui theme keys (StreamRow, PoolRow, BalanceCard, EmptyStateCard, ErrorStateCard, SetStreamFormCard — see packages/streaming-widget/src/components/shared.tsx), none of which overlap with the Claim-family keys (ClaimCard, ClaimActionGlow, etc.) the shared cobalt/teal presets originally targeted. So toggling brandPreset on the StreamingWidget showcase story changed nothing. Adds matching border/shadow overrides for StreamingWidget's card names to both presets so the control has a real effect there too. StakingMigrationWidget already reuses the ClaimCard/StreakCard names (packages/staking-migration-widget/src/migrationWidgetComponents.ts), so it was already covered — it just isn't visible on the "no wallet" fallback screen, since that screen renders plain text with no themed Card at all. --- .../src/stories/helpers/themeOverridePresets.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/examples/storybook/src/stories/helpers/themeOverridePresets.ts b/examples/storybook/src/stories/helpers/themeOverridePresets.ts index 5d7deef..2a43a08 100644 --- a/examples/storybook/src/stories/helpers/themeOverridePresets.ts +++ b/examples/storybook/src/stories/helpers/themeOverridePresets.ts @@ -19,6 +19,14 @@ export const cobaltOverrides: GoodWidgetThemeOverrides = { dark_ClaimActionRing: { primary: '#2E5DE8', primaryLight: '#6E8DFF' }, dark_ClaimActionInner: { backgroundDark: '#0E1A3A', backgroundDarkHover: '#172B60' }, dark_TokenAmountText: { color: '#BBD0FF', secondaryColor: '#7FA2FF' }, + // StreamingWidget card surfaces — not part of the Claim component family, so they + // need their own entries for the brand preset to visibly affect that widget too. + dark_StreamRow: { borderColor: '#2E5DE8', shadowColor: 'rgba(46,93,232,0.7)' }, + dark_PoolRow: { borderColor: '#2E5DE8', shadowColor: 'rgba(46,93,232,0.7)' }, + dark_BalanceCard: { borderColor: '#2E5DE8', shadowColor: 'rgba(46,93,232,0.7)' }, + dark_EmptyStateCard: { borderColor: '#2E5DE8', shadowColor: 'rgba(46,93,232,0.7)' }, + dark_ErrorStateCard: { borderColor: '#2E5DE8', shadowColor: 'rgba(46,93,232,0.7)' }, + dark_SetStreamFormCard: { borderColor: '#2E5DE8', shadowColor: 'rgba(46,93,232,0.7)' }, }, } @@ -36,6 +44,13 @@ export const tealOverrides: GoodWidgetThemeOverrides = { dark_ClaimActionRing: { primary: '#00A884', primaryLight: '#33C9AA' }, dark_ClaimActionInner: { backgroundDark: '#062A23', backgroundDarkHover: '#0B3B31' }, dark_TokenAmountText: { color: '#BFF5E7', secondaryColor: '#66D5BB' }, + // StreamingWidget card surfaces — see cobaltOverrides comment above. + dark_StreamRow: { borderColor: '#00A884', shadowColor: 'rgba(0,168,132,0.65)' }, + dark_PoolRow: { borderColor: '#00A884', shadowColor: 'rgba(0,168,132,0.65)' }, + dark_BalanceCard: { borderColor: '#00A884', shadowColor: 'rgba(0,168,132,0.65)' }, + dark_EmptyStateCard: { borderColor: '#00A884', shadowColor: 'rgba(0,168,132,0.65)' }, + dark_ErrorStateCard: { borderColor: '#00A884', shadowColor: 'rgba(0,168,132,0.65)' }, + dark_SetStreamFormCard: { borderColor: '#00A884', shadowColor: 'rgba(0,168,132,0.65)' }, }, } From b3942920fa6f4573e9535b1d4e661c6d857be47f Mon Sep 17 00:00:00 2001 From: Thompson <140930314+Godbrand0@users.noreply.github.com> Date: Wed, 1 Jul 2026 07:05:54 -0400 Subject: [PATCH 5/7] docs(storybook): clarify Default stories don't respond to Controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Controls panel is auto-generated for every story of a component, so it appears (and looks interactive) on Default/WithAction-style stories too — but their render functions use fixed hardcoded props so those stories stay stable for other docs pages and Playwright screenshot tests. Only the "Controllable" story actually reads args. This was confusing during manual verification, so each Default story across Card/GlowCard/Stepper/Drawer now says so explicitly. --- examples/storybook/src/stories/design-system/Card.stories.tsx | 3 ++- .../storybook/src/stories/design-system/Drawer.stories.tsx | 3 ++- .../storybook/src/stories/design-system/GlowCard.stories.tsx | 3 ++- .../storybook/src/stories/design-system/Stepper.stories.tsx | 2 ++ 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/storybook/src/stories/design-system/Card.stories.tsx b/examples/storybook/src/stories/design-system/Card.stories.tsx index aae81ac..0cdc0ce 100644 --- a/examples/storybook/src/stories/design-system/Card.stories.tsx +++ b/examples/storybook/src/stories/design-system/Card.stories.tsx @@ -23,7 +23,8 @@ const meta: Meta = { export default meta type Story = StoryObj -/** Default Card using base theme values. */ +/** Default Card using base theme values. Fixed reference story — the Controls panel is + * inert here; use "Controllable" below to drive props live. */ export const Default: Story = { render: () => ( diff --git a/examples/storybook/src/stories/design-system/Drawer.stories.tsx b/examples/storybook/src/stories/design-system/Drawer.stories.tsx index 7142fa2..13f9277 100644 --- a/examples/storybook/src/stories/design-system/Drawer.stories.tsx +++ b/examples/storybook/src/stories/design-system/Drawer.stories.tsx @@ -24,7 +24,8 @@ const meta: Meta = { export default meta type Story = StoryObj -/** Controlled Drawer triggered by a button. */ +/** Controlled Drawer triggered by a button. Fixed reference story (has an interaction + * test) — the Controls panel is inert here; use "Controllable" below to drive props live. */ export const Default: Story = { render: () => { // eslint-disable-next-line react-hooks/rules-of-hooks diff --git a/examples/storybook/src/stories/design-system/GlowCard.stories.tsx b/examples/storybook/src/stories/design-system/GlowCard.stories.tsx index e1e5458..2e5f2d6 100644 --- a/examples/storybook/src/stories/design-system/GlowCard.stories.tsx +++ b/examples/storybook/src/stories/design-system/GlowCard.stories.tsx @@ -21,7 +21,8 @@ const meta: Meta = { export default meta type Story = StoryObj -/** Default GlowCard with theme-driven glow colour. */ +/** Default GlowCard with theme-driven glow colour. Fixed reference story — the Controls + * panel is inert here; use "Controllable" below to drive props live. */ export const Default: Story = { render: () => ( diff --git a/examples/storybook/src/stories/design-system/Stepper.stories.tsx b/examples/storybook/src/stories/design-system/Stepper.stories.tsx index 4cb12fa..a3df7f9 100644 --- a/examples/storybook/src/stories/design-system/Stepper.stories.tsx +++ b/examples/storybook/src/stories/design-system/Stepper.stories.tsx @@ -29,6 +29,8 @@ const meta: Meta = { export default meta type Story = StoryObj +/** Fixed reference story — the Controls panel is inert here; use "Controllable" below to + * drive props live. */ export const Default: Story = { render: () => ( From 092602ef4561de9f5f92bd983112a7aec982c5a0 Mon Sep 17 00:00:00 2001 From: Thompson <140930314+Godbrand0@users.noreply.github.com> Date: Wed, 1 Jul 2026 07:35:51 -0400 Subject: [PATCH 6/7] fix(storybook): themeOverrides dropped on StreamingWidget's no-wallet fallback Found during manual verification: InjectedWalletStory threaded themeOverrides through to StreamingWidgetStoryShell (the "wallet connected" path) but not to PreviewStoryShell (the "no wallet" fallback path, which most reviewers hit since they don't have a matching injected wallet). So brandPreset silently did nothing on the common case of that story. PreviewStoryShell and its underlying StreamingWidgetPreview call now also receive themeOverrides. --- .../storybook/src/stories/helpers/streamingWidgetStories.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx b/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx index e1761b3..f21c33f 100644 --- a/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx +++ b/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx @@ -210,12 +210,14 @@ function PreviewStoryShell({ initialTab = 'streams', initialStreamsFormOpen = false, defaultTheme, + themeOverrides, }: { adapter: StreamingWidgetAdapterResult dataTestId: string initialTab?: StreamingWidgetTab initialStreamsFormOpen?: boolean defaultTheme?: 'light' | 'dark' + themeOverrides?: StreamingWidgetProps['themeOverrides'] }) { return ( @@ -224,6 +226,7 @@ function PreviewStoryShell({ initialTab={initialTab} initialStreamsFormOpen={initialStreamsFormOpen} defaultTheme={defaultTheme} + themeOverrides={themeOverrides} /> ) @@ -280,6 +283,7 @@ export function InjectedWalletStory({ })} dataTestId="StreamingWidget-no-injected-wallet" defaultTheme={defaultTheme} + themeOverrides={themeOverrides} /> ) } From 41123179a185f769f0d9ffed87fea24915c090b1 Mon Sep 17 00:00:00 2001 From: Thompson <140930314+Godbrand0@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:43:47 -0400 Subject: [PATCH 7/7] feat(storybook): extend defaultTheme/brandPreset controls to QA fixture stories Per issue #64's literal DoD ("all stories are accompanied with controls"), the QA runtime-fixture stories previously had zero controls. Threads defaultTheme/themeOverrides through every QA helper function across all three widgets (20 StreamingWidget states, 1 CitizenClaimWidget state, 8 StakingMigrationWidget states) and wires matching argTypes into each QA story file, following the same pattern already used on the Showcase stories. QA determinism for Playwright is unaffected: automated tests navigate directly to story IDs without touching the Controls panel, so the fixed default args (dark theme, no brand preset) still apply during test runs. A human reviewer gets the same live override on top when browsing QA stories manually. LightThemePopulated/LightThemeReady keep their hardcoded defaultTheme="light" mount (that's the point of those specific stories) but now also accept the brandPreset control on top. --- .../CitizenClaimWidgetQA.stories.tsx | 30 ++++- .../helpers/citizenClaimWidgetStories.tsx | 10 +- .../helpers/stakingMigrationWidgetStories.tsx | 47 +++++-- .../helpers/streamingWidgetStories.tsx | 97 +++++++++++--- .../StakingMigrationWidgetQA.stories.tsx | 58 ++++++-- .../StreamingWidgetQA.stories.tsx | 125 ++++++++++++++---- 6 files changed, 298 insertions(+), 69 deletions(-) diff --git a/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidgetQA.stories.tsx b/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidgetQA.stories.tsx index a06642d..b0cea40 100644 --- a/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidgetQA.stories.tsx +++ b/examples/storybook/src/stories/citizen-claim-widget/CitizenClaimWidgetQA.stories.tsx @@ -1,17 +1,41 @@ import type { Meta, StoryObj } from '@storybook/react' import { CitizenClaimWidget } from '@goodwidget/citizen-claim-widget' import { CustodialLocalFixtureStory } from '../helpers/citizenClaimWidgetStories' +import { BRAND_PRESET_OPTIONS, brandPresetOverrides, type BrandPreset } from '../helpers/themeOverridePresets' -const meta: Meta = { +interface CitizenClaimWidgetQAArgs { + defaultTheme: 'light' | 'dark' + brandPreset: BrandPreset +} + +const meta: Meta = { title: 'QA/CitizenClaimWidget/Runtime Fixtures', component: CitizenClaimWidget, tags: ['autodocs', 'qa'], parameters: { layout: 'padded' }, + argTypes: { + defaultTheme: { + control: 'radio', + options: ['dark', 'light'], + description: 'Base theme applied via the widget’s own defaultTheme prop.', + }, + brandPreset: { + control: 'select', + options: BRAND_PRESET_OPTIONS, + description: 'Sample host-branding themeOverrides preset.', + }, + }, + args: { + defaultTheme: 'dark', + brandPreset: 'None', + }, } export default meta -type Story = StoryObj +type Story = StoryObj export const CustodialLocalFixture: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } diff --git a/examples/storybook/src/stories/helpers/citizenClaimWidgetStories.tsx b/examples/storybook/src/stories/helpers/citizenClaimWidgetStories.tsx index fb6b2f0..4731007 100644 --- a/examples/storybook/src/stories/helpers/citizenClaimWidgetStories.tsx +++ b/examples/storybook/src/stories/helpers/citizenClaimWidgetStories.tsx @@ -109,13 +109,21 @@ export function InjectedWalletStory({ ) } -export function CustodialLocalFixtureStory() { +export function CustodialLocalFixtureStory({ + defaultTheme, + themeOverrides, +}: { + defaultTheme?: 'light' | 'dark' + themeOverrides?: CitizenClaimWidgetProps['themeOverrides'] +} = {}) { try { const provider = createCustodialEip1193Provider() return ( ) } catch (error: unknown) { diff --git a/examples/storybook/src/stories/helpers/stakingMigrationWidgetStories.tsx b/examples/storybook/src/stories/helpers/stakingMigrationWidgetStories.tsx index d925fc8..dd73ad1 100644 --- a/examples/storybook/src/stories/helpers/stakingMigrationWidgetStories.tsx +++ b/examples/storybook/src/stories/helpers/stakingMigrationWidgetStories.tsx @@ -74,18 +74,30 @@ function createAdapterFactory( }) } +type ThemeArgs = { + defaultTheme?: 'light' | 'dark' + themeOverrides?: StakingMigrationWidgetProps['themeOverrides'] +} + function MockStoryShell({ adapterFactory, dataTestId, + defaultTheme, + themeOverrides, }: { adapterFactory: StakingMigrationWidgetAdapterFactory dataTestId: string -}) { +} & ThemeArgs) { try { const provider = createCustodialEip1193Provider() return ( - + ) } catch (error: unknown) { @@ -142,7 +154,7 @@ export function InjectedWalletStory({ ) } -export function EmptyBalanceStory() { +export function EmptyBalanceStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function ReadyStory() { +export function ReadyStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function WrongNetworkStory() { +export function WrongNetworkStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function ApprovalPendingStory() { +export function ApprovalPendingStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function MigratingStory() { +export function MigratingStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function SuccessStory() { +export function SuccessStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function ErrorStateStory() { +export function ErrorStateStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function LightThemeReadyStory() { +export function LightThemeReadyStory({ themeOverrides }: Pick = {}) { return ( @@ -228,6 +254,7 @@ export function LightThemeReadyStory() { diff --git a/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx b/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx index f21c33f..af80c7f 100644 --- a/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx +++ b/examples/storybook/src/stories/helpers/streamingWidgetStories.tsx @@ -299,7 +299,11 @@ export function InjectedWalletStory({ ) } -export function CustodialLocalFixtureStory({ apiKey }: Pick) { +export function CustodialLocalFixtureStory({ + apiKey, + defaultTheme, + themeOverrides, +}: Pick) { try { const provider = createCustodialEip1193Provider() return ( @@ -307,6 +311,8 @@ export function CustodialLocalFixtureStory({ apiKey }: Pick ) } catch (error: unknown) { @@ -326,7 +332,12 @@ export function CustodialLocalFixtureStory({ apiKey }: Pick ) } -export function WrongChainStory() { +export function WrongChainStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function LoadingStateStory() { +export function LoadingStateStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function EmptyStateStory() { +export function EmptyStateStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function ErrorStateStory() { +export function ErrorStateStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function PopulatedStateStory() { +export function PopulatedStateStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( - + ) } -export function LightThemePopulatedStory() { +export function LightThemePopulatedStory({ themeOverrides }: Pick = {}) { return ( ) } -export function CreateUpdateFormStory() { +export function CreateUpdateFormStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { const [form, setForm] = React.useState(validForm) return ( @@ -446,21 +473,25 @@ export function CreateUpdateFormStory() { )} dataTestId="StreamingWidget-create-update-form" initialStreamsFormOpen + defaultTheme={defaultTheme} + themeOverrides={themeOverrides} /> ) } -export function CreateUpdateInvalidInputStory() { +export function CreateUpdateInvalidInputStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function CreateUpdatePendingStory() { +export function CreateUpdatePendingStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function CreateUpdateSuccessStory() { +export function CreateUpdateSuccessStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function CreateUpdateFailureStory() { +export function CreateUpdateFailureStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function PoolClaimStateStory() { +export function PoolClaimStateStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function PoolConnectedStateStory() { +export function PoolConnectedStateStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function PoolClaimPendingStory() { +export function PoolClaimPendingStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function PoolClaimSuccessStory() { +export function PoolClaimSuccessStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function PoolClaimErrorStory() { +export function PoolClaimErrorStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function PoolClaimableAmountErrorStory() { +export function PoolClaimableAmountErrorStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { const [retrying, setRetrying] = React.useState(false) return ( @@ -592,11 +639,13 @@ export function PoolClaimableAmountErrorStory() { )} dataTestId="StreamingWidget-pool-claimable-amount-error" initialTab="pools" + defaultTheme={defaultTheme} + themeOverrides={themeOverrides} /> ) } -export function BaseSupBalanceAndReserveStory() { +export function BaseSupBalanceAndReserveStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } -export function NonBaseSupReserveDisabledStory() { +export function NonBaseSupReserveDisabledStory({ defaultTheme, themeOverrides }: ThemeArgs = {}) { return ( ) } diff --git a/examples/storybook/src/stories/staking-migration-widget/StakingMigrationWidgetQA.stories.tsx b/examples/storybook/src/stories/staking-migration-widget/StakingMigrationWidgetQA.stories.tsx index f23de5f..096a010 100644 --- a/examples/storybook/src/stories/staking-migration-widget/StakingMigrationWidgetQA.stories.tsx +++ b/examples/storybook/src/stories/staking-migration-widget/StakingMigrationWidgetQA.stories.tsx @@ -10,50 +10,88 @@ import { SuccessStory, WrongNetworkStory, } from '../helpers/stakingMigrationWidgetStories' +import { BRAND_PRESET_OPTIONS, brandPresetOverrides, type BrandPreset } from '../helpers/themeOverridePresets' -const meta: Meta = { +interface StakingMigrationWidgetQAArgs { + defaultTheme: 'light' | 'dark' + brandPreset: BrandPreset +} + +const meta: Meta = { title: 'QA/StakingMigrationWidget/Runtime Fixtures', component: StakingMigrationWidget, tags: ['autodocs', 'qa'], parameters: { layout: 'padded' }, + argTypes: { + defaultTheme: { + control: 'radio', + options: ['dark', 'light'], + description: 'Base theme applied via the widget’s own defaultTheme prop.', + }, + brandPreset: { + control: 'select', + options: BRAND_PRESET_OPTIONS, + description: 'Sample host-branding themeOverrides preset.', + }, + }, + args: { + defaultTheme: 'dark', + brandPreset: 'None', + }, } export default meta -type Story = StoryObj +type Story = StoryObj export const EmptyBalance: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const Ready: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const WrongNetwork: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const ApprovalPending: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const Migrating: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const Success: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const ErrorState: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } +/** Always mounts with defaultTheme="light" regardless of the control, to demonstrate the + * explicit light-theme branch; brandPreset still applies on top. */ export const LightThemeReady: Story = { parameters: { goodWidgetProvider: { useShell: false, }, }, - render: () => , + render: ({ brandPreset }) => , } diff --git a/examples/storybook/src/stories/streaming-widget/StreamingWidgetQA.stories.tsx b/examples/storybook/src/stories/streaming-widget/StreamingWidgetQA.stories.tsx index 9b6f46a..fcb85d8 100644 --- a/examples/storybook/src/stories/streaming-widget/StreamingWidgetQA.stories.tsx +++ b/examples/storybook/src/stories/streaming-widget/StreamingWidgetQA.stories.tsx @@ -23,8 +23,15 @@ import { PopulatedStateStory, WrongChainStory, } from '../helpers/streamingWidgetStories' +import { BRAND_PRESET_OPTIONS, brandPresetOverrides, type BrandPreset } from '../helpers/themeOverridePresets' -const meta: Meta = { +interface StreamingWidgetQAArgs { + apiKey?: string + defaultTheme: 'light' | 'dark' + brandPreset: BrandPreset +} + +const meta: Meta = { title: 'QA/StreamingWidget/Runtime Fixtures', component: StreamingWidget, tags: ['autodocs', 'qa'], @@ -41,95 +48,167 @@ const meta: Meta = { description: 'Optional TheGraph key passed to the SDK-backed streaming adapter for Base SUP reserve queries.', }, + defaultTheme: { + control: 'radio', + options: ['dark', 'light'], + description: 'Base theme applied via the widget’s own defaultTheme prop.', + }, + brandPreset: { + control: 'select', + options: BRAND_PRESET_OPTIONS, + description: 'Sample host-branding themeOverrides preset.', + }, }, args: { apiKey: '', + defaultTheme: 'dark', + brandPreset: 'None', }, } export default meta -type Story = StoryObj +type Story = StoryObj export const CustodialLocalFixture: Story = { - render: ({ apiKey }) => , + render: ({ apiKey, defaultTheme, brandPreset }) => ( + + ), } export const NoWallet: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const WrongChain: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const LoadingState: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const EmptyState: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const ErrorState: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const PopulatedState: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } +/** Always mounts with defaultTheme="light" regardless of the control, to demonstrate the + * explicit light-theme branch; brandPreset still applies on top. */ export const LightThemePopulated: Story = { - render: () => , + render: ({ brandPreset }) => ( + + ), } export const CreateUpdateForm: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const CreateUpdateInvalidInput: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const CreateUpdatePending: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const CreateUpdateSuccess: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const CreateUpdateFailure: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const PoolClaimState: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const PoolConnectedState: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const PoolClaimPending: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const PoolClaimSuccess: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const PoolClaimError: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const PoolClaimableAmountError: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const BaseSupBalanceAndReserve: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), } export const NonBaseSupReserveDisabled: Story = { - render: () => , + render: ({ defaultTheme, brandPreset }) => ( + + ), }