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/
>
+
+
+
+
+ )
+}`}
+ />
+
+
= {
+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/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..0cdc0ce 100644
--- a/examples/storybook/src/stories/design-system/Card.stories.tsx
+++ b/examples/storybook/src/stories/design-system/Card.stories.tsx
@@ -13,11 +13,18 @@ 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
-/** 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: () => (
@@ -59,3 +66,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..13f9277 100644
--- a/examples/storybook/src/stories/design-system/Drawer.stories.tsx
+++ b/examples/storybook/src/stories/design-system/Drawer.stories.tsx
@@ -13,11 +13,19 @@ 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
-/** 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
@@ -49,3 +57,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..2e5f2d6 100644
--- a/examples/storybook/src/stories/design-system/GlowCard.stories.tsx
+++ b/examples/storybook/src/stories/design-system/GlowCard.stories.tsx
@@ -13,11 +13,16 @@ 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
-/** 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: () => (
@@ -29,3 +34,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..a3df7f9 100644
--- a/examples/storybook/src/stories/design-system/Stepper.stories.tsx
+++ b/examples/storybook/src/stories/design-system/Stepper.stories.tsx
@@ -2,25 +2,35 @@ 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' },
-]
-
+/** Fixed reference story — the Controls panel is inert here; use "Controllable" below to
+ * drive props live. */
export const Default: Story = {
render: () => (
@@ -33,3 +43,31 @@ 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: ({ activeStepId, maxHeight }) => (
+
+ Transaction steps}
+ maxHeight={maxHeight}
+ />
+
+ ),
+}
diff --git a/examples/storybook/src/stories/helpers/citizenClaimWidgetStories.tsx b/examples/storybook/src/stories/helpers/citizenClaimWidgetStories.tsx
index 196603d..4731007 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,17 +103,27 @@ 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/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..dd73ad1 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'
@@ -73,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) {
@@ -99,7 +112,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 +139,8 @@ export function InjectedWalletStory() {
{!migrationApiBaseUrl && (
@@ -133,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 (
@@ -219,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 21dfb3b..af80c7f 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}
/>
)
@@ -234,11 +237,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 +254,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 +282,8 @@ export function InjectedWalletStory({ apiKey }: Pick
)
}
@@ -281,11 +293,17 @@ export function InjectedWalletStory({ apiKey }: Pick
)
}
-export function CustodialLocalFixtureStory({ apiKey }: Pick) {
+export function CustodialLocalFixtureStory({
+ apiKey,
+ defaultTheme,
+ themeOverrides,
+}: Pick) {
try {
const provider = createCustodialEip1193Provider()
return (
@@ -293,6 +311,8 @@ export function CustodialLocalFixtureStory({ apiKey }: Pick
)
} catch (error: unknown) {
@@ -312,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 (
@@ -432,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 (
@@ -578,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/helpers/themeOverridePresets.ts b/examples/storybook/src/stories/helpers/themeOverridePresets.ts
new file mode 100644
index 0000000..2a43a08
--- /dev/null
+++ b/examples/storybook/src/stories/helpers/themeOverridePresets.ts
@@ -0,0 +1,69 @@
+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' },
+ // 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)' },
+ },
+}
+
+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' },
+ // 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)' },
+ },
+}
+
+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/StakingMigrationWidget.mdx b/examples/storybook/src/stories/staking-migration-widget/StakingMigrationWidget.mdx
index 03eb75f..5ccfee2 100644
--- a/examples/storybook/src/stories/staking-migration-widget/StakingMigrationWidget.mdx
+++ b/examples/storybook/src/stories/staking-migration-widget/StakingMigrationWidget.mdx
@@ -1,4 +1,4 @@
-import { Canvas, Meta } from '@storybook/blocks';
+import { Canvas, Meta, Source } from '@storybook/blocks';
import * as ShowcaseStories from './StakingMigrationWidgetShowcase.stories';
import { DocsCallout, DocsCard, DocsGrid, DocsPage, DocsSection } from '../docs/DocsLayout';
@@ -11,11 +11,35 @@ import { DocsCallout, DocsCard, DocsGrid, DocsPage, DocsSection } from '../docs/
>
+
+
+
+
+ )
+}`}
+ />
+
+
= {
+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/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.mdx b/examples/storybook/src/stories/streaming-widget/StreamingWidget.mdx
index e1bb84e..142bf5e 100644
--- a/examples/storybook/src/stories/streaming-widget/StreamingWidget.mdx
+++ b/examples/storybook/src/stories/streaming-widget/StreamingWidget.mdx
@@ -11,7 +11,7 @@ import { DocsCallout, DocsCard, DocsGrid, DocsPage, DocsSection } from '../docs/
>
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 }) => (
+
),
}
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 }) => (
+
+ ),
}