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 }) => ( + + ), }