Storybook polish: interactive theming controls for widgets (#64)#73
Storybook polish: interactive theming controls for widgets (#64)#73Godbrand0 wants to merge 12 commits into
Conversation
0xOlivanode
left a comment
There was a problem hiding this comment.
Code itself is solid, the Stepper activeStepId fix is a real and correctly implemented fix, the cobalt/teal preset extraction preserves every value exactly, and the theme prop threading across all the QA/Showcase files is consistent throughout.
Requesting changes on process, not code:
This PR's description says "closes #64", but it's explicitly framed as a "first increment" and its own test plan lists remaining DoD items as follow-up work. Checking issue #64 directly, its Definition of Done has 3 items, and this PR only partially covers them:
- "All stories are accompanied with controls": this PR covers 4 widgets (StreamingWidget, CitizenClaimWidget, StakingMigrationWidget, ClaimWidget Theme Demo), but the repo has 6 widget areas with story files. ai-credits-widget and governance-widget both have QA + Showcase stories that aren't touched here.
- "All stories reviewed for clear guiding text": explicitly deferred per the PR's own test plan.
- "Component-level issues resolved or documented": partially done (the Stepper fix), no broader sweep.
Since this is tagged as a bounty issue, merging with "closes #64" as-is will auto-close the tracking issue before its DoD is actually met. Can we change this to "part of #64" and open a follow-up issue for the remaining scope (ai-credits-widget, governance-widget, and the human-eye review pass), or confirm with whoever manages the bounty that partial closure here is intentional?
Small nit: the PR description says "no QA fixture stories were touched", the three *QA.stories.tsx files were edited (just to add the new controls, no data-testid changes), so the underlying claim about stable test IDs holds, but the wording overstates it slightly.
| dark_SetStreamFormCard: { borderColor: '#00A884', shadowColor: 'rgba(0,168,132,0.65)' }, | ||
| }, | ||
| } | ||
|
|
There was a problem hiding this comment.
Every entry here is a dark_* key, there's no light_* equivalent for any of these (ClaimCard, StreamRow, PoolRow, BalanceCard, etc).
Since the QA/Showcase stories now expose defaultTheme and brandPreset as independent controls, selecting defaultTheme: light + a brand preset will apply the top-level tokens.color.primary globally, but none of the component-specific border/shadow/glow overrides will render, since those only exist for dark. That's the part that actually makes the preset visually "brand," so in light mode it'll look like the preset silently isn't working.
Is that intentional (presets are meant as dark-mode demos only), or should light variants be added here too?
|
@0xOlivanode the note on 'auto-closing', this is turned off in the repo so does not affect anything. |
…ow key Addresses PR GoodDollar#73 review comment: every brandPreset override was dark_* only, so selecting defaultTheme: light left only the top-level tokens.color.primary applied globally, while every component-specific border/shadow/glow override silently did nothing — the preset looked broken in light mode. Adds a light_* counterpart for every dark_* entry, using field values appropriate for light mode (much lower shadow opacity, matching the base design preset's light_Card/light_Toast/etc precedent in packages/ui/src/presets.ts; darker/more saturated text color for contrast on a light background instead of the pastel dark-mode shade). ClaimActionInner stays dark-only, matching the base preset's own precedent of never defining a light variant for it. Also fixes dark_ClaimActionGlow, which used the wrong field name (primary instead of backgroundColor) — CircularActionButton.tsx reads $backgroundColor for the glow's resting fill and $primaryLight only for its hover state, so the old key silently did nothing.
|
hello @L03TJ3 , so should i change the pr name to "closes part of 64" or i should leave it as is. will the other issues be created or should i work on them myself?? |
|
Thats review feedback so should be followed up on in this PR once done, you request review from @0xOlivanode |
0xOlivanode
left a comment
There was a problem hiding this comment.
Thank you for the fast turnaround on the light_* fix. I pulled the branch and went through it in depth. The review-response commit fully checks out:
Every dark_* key now has a light_* counterpart, with ClaimActionInner intentionally excepted, matching the base preset's own precedent in packages/ui/src/presets.ts.
Verified against CircularActionButton.tsx that the glow reads $backgroundColor / $glowOpacity, so the old primary key was indeed dead. Nice catch; that was a pre-existing latent bug, and the new light values follow the base preset's light_ClaimActionGlow convention.
pnpm install --frozen-lockfile, full workspace build (9/9), and build-storybook all pass on the branch. test:demo gives 33/35 with only the two already-noted staking-migration failures.
Re-verified QA determinism directly: zero data-testid changes across the branch, no story export renames, and all three widgets internally default to 'dark', so the now-explicit QA args are behavior-identical for the test runner.
Requesting changes on three items:
Rebase required, the branch no longer merges into main. There's a content conflict in examples/storybook/src/stories/helpers/citizenClaimWidgetStories.tsx: main has since refactored that story shell (the WidgetTabs wrapper was removed and the widget now mounts directly), so the themeOverrides threading needs to be re-applied to the new component shape rather than mechanically conflict-resolved.
Post-rebase, main now has three additional widget story areas with no controls: ai-credits-widget (Showcase + QA + MDX), governance-widget, and goodreserve-widget. @L03TJ3, per the DoD's "all stories are accompanied with controls," should these be extended in this PR after the rebase, or split into a follow-up issue? Either works; it just needs an explicit call so the bounty scope is unambiguous.
One small fix while you're in ClaimWidget.stories.tsx, see inline comment: goodWidgetProvider is placed at the meta top level, but the decorator reads it from parameters, so it's currently a silent no-op. Pre-existing, but this PR already edits that meta object.
Non-blocking: the PR description still says "no QA fixture stories were touched," which 4112317 made outdated, so worth a quick edit. Separately, the two failing staking-migration tests (ready summary / migrating timeline) reproduce outside the sandbox on a normal connection too, so the sandbox-network explanation doesn't fully cover them. I'm following up on whether they also fail on main; not attributing them to this PR for now since the specs predate it.
| } from '../helpers/claimWidgetStories' | ||
| import { ClaimWidgetStoryCanvas } from '../helpers/claimWidgetStories' | ||
| import { BRAND_PRESET_OPTIONS, brandPresetOverrides, type BrandPreset } from '../helpers/themeOverridePresets' | ||
|
|
There was a problem hiding this comment.
goodWidgetProvider here sits at the top level of meta, but the decorator in .storybook/preview.tsx reads context.parameters.goodWidgetProvider, so disableProvider: true, useShell: false never actually applies to these stories. Moving it inside parameters (as every other story file does) makes it functional and also clears the TS2353 error on this file.
|
@Godbrand0 @0xOlivanode Only thing that should be left/kept in place is the removal of miniapp-shell and goodwidget provider in |
…ow key Addresses PR GoodDollar#73 review comment: every brandPreset override was dark_* only, so selecting defaultTheme: light left only the top-level tokens.color.primary applied globally, while every component-specific border/shadow/glow override silently did nothing — the preset looked broken in light mode. Adds a light_* counterpart for every dark_* entry, using field values appropriate for light mode (much lower shadow opacity, matching the base design preset's light_Card/light_Toast/etc precedent in packages/ui/src/presets.ts; darker/more saturated text color for contrast on a light background instead of the pastel dark-mode shade). ClaimActionInner stays dark-only, matching the base preset's own precedent of never defining a light variant for it. Also fixes dark_ClaimActionGlow, which used the wrong field name (primary instead of backgroundColor) — CircularActionButton.tsx reads $backgroundColor for the glow's resting fill and $primaryLight only for its hover state, so the old key silently did nothing.
947c695 to
9059962
Compare
Addresses PR GoodDollar#73 review comment: main removed the global GoodWidgetProvider/ MiniAppShell decorator from .storybook/preview.tsx entirely (every widget already wraps its own provider internally, so the outer one was redundant). That decorator was the only thing reading context.parameters.goodWidgetProvider, so every remaining goodWidgetProvider parameter block in the touched story files is now dead configuration with nothing left to consume it. Removes it from the five files this PR touches (ClaimWidget, StreamingWidget Showcase/QA, StakingMigrationWidget Showcase/QA). This also resolves the specific nit about ClaimWidget.stories.tsx: that block was placed at the meta top level instead of inside `parameters` and was already a no-op before this rebase — deleting it (rather than moving it) is correct now since there's no decorator left to read it either way.
There was a problem hiding this comment.
Not a good demonstration of the requested controls and I find it relatively 'lazy' for lack of a better word.
I also don't see anything done towards 'clear and concise showcase ... write new widgets
the basics work. sure.
Note this section of the original issue:
The main structure as-is now setup should be kept. This boutny is about about polishing the content, the way widgets or components are demonstrated, including guiding documentation and code examples). And besides having an interactive demo functionality, it should also be interactive by being able to play with the design-system and override as we promote: 'almost all UI aspects of a given widget'. This requires clear mapping of exposed styling variables and making it configurable/overridable through storybooks native controls feature.
Then my AI review:
For StreamingWidget, the current control offers only:
defaultThemebrandPreset: None / Cobalt / Teal
That is a fixed bundle of overrides. It cannot answer “make only primary buttons purple,” for example.
The underlying API can do it:
<StreamingWidget
themeOverrides={{
themes: {
dark_Button: {
background: '#7C3AED',
backgroundHover: '#6D28D9',
backgroundPress: '#5B21B6',
backgroundFocus: '#6D28D9',
textColor: '#FFFFFF',
},
},
}}
/>Button is a public named theme target, and StreamingWidget uses it for its primary actions. But no StreamingWidget story or guide exposes or explains that target. The current presets only override a selection of card surfaces (dark_StreamRow, dark_PoolRow, etc.), not dark_Button.
For the ClaimWidget theme demo, the API is similarly capable of targeted overrides:
dark_ClaimCarddark_ClaimActionGlow— usebackgroundColor/glowOpacitydark_ClaimActionRing— useprimary/primaryLightdark_ClaimActionInnerdark_TokenAmountTextdark_Button
But the current “Theming and Overrides” experience does not make those targets discoverable, structured, or interactively editable. Worse, the supposedly canonical playground still shows stale glow fields (primary) where the actual component uses backgroundColor and glowOpacity; the new shared preset fixed that elsewhere. See ThemePlayground.stories.tsx and ThemingAndOverrides.mdx.
So I’d frame it this way:
- You should not necessarily expect a color picker for every internal style value.
- You should expect a Storybook “advanced overrides” path that demonstrates the supported public contract per widget.
- It should show the target names, which semantic fields they accept, light/dark counterparts, a live editable example (structured controls or an object/JSON control), and copy-paste code that matches the live result.
- QA fixtures should stay deterministic. This belongs primarily in Showcase/Integrator stories, not by putting two broad controls on every QA state.
Today, this PR demonstrates “choose one of two brands,” not “almost all UI aspects are overridable.” That is materially below issue #64’s stated goal.
…tories 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.
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.
Found during manual visual verification of issue GoodDollar#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.
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.
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.
… 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.
…re stories Per issue GoodDollar#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.
…ow key Addresses PR GoodDollar#73 review comment: every brandPreset override was dark_* only, so selecting defaultTheme: light left only the top-level tokens.color.primary applied globally, while every component-specific border/shadow/glow override silently did nothing — the preset looked broken in light mode. Adds a light_* counterpart for every dark_* entry, using field values appropriate for light mode (much lower shadow opacity, matching the base design preset's light_Card/light_Toast/etc precedent in packages/ui/src/presets.ts; darker/more saturated text color for contrast on a light background instead of the pastel dark-mode shade). ClaimActionInner stays dark-only, matching the base preset's own precedent of never defining a light variant for it. Also fixes dark_ClaimActionGlow, which used the wrong field name (primary instead of backgroundColor) — CircularActionButton.tsx reads $backgroundColor for the glow's resting fill and $primaryLight only for its hover state, so the old key silently did nothing.
Addresses PR GoodDollar#73 review comment: main removed the global GoodWidgetProvider/ MiniAppShell decorator from .storybook/preview.tsx entirely (every widget already wraps its own provider internally, so the outer one was redundant). That decorator was the only thing reading context.parameters.goodWidgetProvider, so every remaining goodWidgetProvider parameter block in the touched story files is now dead configuration with nothing left to consume it. Removes it from the five files this PR touches (ClaimWidget, StreamingWidget Showcase/QA, StakingMigrationWidget Showcase/QA). This also resolves the specific nit about ClaimWidget.stories.tsx: that block was placed at the meta top level instead of inside `parameters` and was already a no-op before this rebase — deleting it (rather than moving it) is correct now since there's no decorator left to read it either way.
9059962 to
bc797f4
Compare
…ix brand preset Button/glow gaps Addresses L03TJ3's review on PR GoodDollar#73: the existing brandPreset control (None/ Cobalt/Teal) only demonstrated picking from two curated palettes across a handful of hardcoded card targets, not "almost all UI aspects" being overridable as issue GoodDollar#64 asks for. - New Widgets/StreamingWidget/Advanced Overrides story: live color-picker controls (always visible, no collapsed JSON tree to expand) mapped to real themeOverrides.themes fields (dark_Button + dark_StreamRow/dark_BalanceCard from the rendered fixture), with a code snippet generated from the live arg values so it can never drift from what's rendered. Reference list for the remaining Card-derived targets (PoolRow, EmptyStateCard, ErrorStateCard, SetStreamFormCard) that share the same field shape. - Fixed dark_ClaimActionGlow using stale primary/primaryLight fields in ThemePlayground.stories.tsx (the component reads $backgroundColor, not $primary — same bug class already fixed in themeOverridePresets.ts). - Fixed cobaltOverrides/tealOverrides never touching dark_Button/light_Button: manually verified the InjectedWallet story's wallet-not-connected fallback (WalletGate.tsx) renders an EmptyStateCard + a Button-themed "Connect Wallet" action, so previously only a subtle card border tint changed between presets while the one loud, obviously-colored element never did. This also fixes the same gap for ClaimWidget/StakingMigrationWidget, which share the base Button theme. One widget scoped first (per plan) to validate the pattern before replicating to ClaimWidget and StakingMigrationWidget.
|
@L03TJ3 pushed a first pass at the "advanced overrides" pattern, scoped to one widget (StreamingWidget) before replicating it elsewhere, to get sign-off on the shape first. New story: Widgets/StreamingWidget/Advanced Overrides → Playground
Questions before I replicate this to ClaimWidget and StakingMigrationWidget:
|
|
I think its the right direction yes. some comments:
-- Also, maybe add text to how it works: 'Play around with some of the theme overrides using the controls below (you maybe have to activate controls top-right). The code here can later be copied and directly used in your own dapp. See the .... section for additional overridable styles' <-- Something along those lines, whatever makes sense
|
…and StakingMigrationWidget
Follow-up to L03TJ3's sign-off on the StreamingWidget pattern
("I think its the right direction yes"), replicating it to the other two
widgets and applying their requested refinements across all three:
- New Widgets/ClaimWidget Theme Demo/Theme overrides and Widgets/
StakingMigrationWidget/Theme overrides playgrounds, same shape as
StreamingWidget's: flat always-visible color controls for a couple of
high-impact targets per widget, a live code snippet, and a reference
callout listing every overridable path (controlled or not).
- ClaimWidget: dark_Button, dark_ClaimActionGlow, dark_ClaimActionRing,
dark_ClaimCard (it defines its own local ClaimActionGlow/Ring/Inner,
same shape as the shared CircularActionButton).
- StakingMigrationWidget: dark_ClaimCard, dark_ClaimActionGlow,
dark_ClaimActionRing (no dark_Button control — this widget has no plain
Button usage, only the shared circular action button).
- Renamed "Advanced Overrides" -> "Theme overrides" everywhere per feedback.
- preview.tsx: parameters.controls.disableSaveFromUI = true globally, so
editing any control (including the existing brandPreset/defaultTheme ones)
no longer shows Storybook's "Update story / Create new story" save bar.
- Each widget's .mdx docs page gets a new "Theme overrides" DocsSection
embedding the Playground story via Canvas, using the intro copy L03TJ3
suggested. The header/description lives only in the docs page (not
duplicated inside the story itself) to avoid the doubled heading that
showed up when both had it.
- Centered the code snippet + reference callout (maxWidth 560, margin auto)
so they align with the widget preview below them instead of stretching
full-bleed while the widget stays narrow and centered.
…ior commit These belonged in the previous commit (theme-overrides playground replication) but were dropped by a staging mistake: a failed git add on a stale path aborted the whole add call. - preview.tsx: parameters.controls.disableSaveFromUI = true. - Each widget's .mdx docs page: new "Theme overrides" DocsSection embedding its Playground story via Canvas.
|
@L03TJ3 pushed the refinements you asked for, plus replicated the pattern to the other two widgets. StreamingWidget refinements (all five points addressed):
Replicated to the other two widgets, same shape:
Docs integration: each widget's Everything typechecks and |
|
Deployment failed with the following error: |
Summary
Part of #64 (Polish storybook interaction and presentation) — not closing it, since the issue's Definition of Done has three items and this PR intentionally covers a scoped subset (see below). Focuses on the "controls" and "documentation" parts of the DoD:
argTypescontrols fordefaultThemeandthemeOverrides(via a shared "brand preset" select: None/Cobalt/Teal) across all four widget showcase stories — StreamingWidget, CitizenClaimWidget, StakingMigrationWidget, and ClaimWidget Theme Demo — so integrators can flip theme/branding live in the Controls panel instead of editing story code.claimWidgetStories.tsx) into a sharedthemeOverridePresets.tshelper reused by all widgets, with bothdark_*andlight_*variants for every named component theme (per review feedback — the first pass was dark-only and silently did nothing in light mode).argTypes+ aControllablestory to the Card, GlowCard, Stepper, and Drawer design-system primitives, following the existingTokenAmountpattern.CitizenClaimWidget.mdxandStakingMigrationWidget.mdxto matchClaimWidget.mdx's existing depth, plus a one-line pointer to the new controls on all three widget docs pages.Controllablestory'sactiveStepIdcontrol only auto-scrolled the list without changing which step rendered as active, and thebrandPresetcontrol was a no-op on StreamingWidget because its card surfaces use different named theme keys than the Claim-family widgets.goodWidgetProviderparameter blocks across the touched story files —.storybook/preview.tsxonmainno longer reads that parameter at all (the global provider/shell decorator was removed upstream since every widget already wraps its own provider), so this was leftover config with nothing consuming it. Also resolves the specificClaimWidget.stories.tsxTS2353 flagged in review — that block was placed outsideparametersand never worked even before the decorator was removed.Explicitly out of scope for this PR (per maintainer guidance in review):
ai-credits-widget,governance-widget, andgoodreserve-widgetlanded onmainafter this branch started and are not touched here. Also out of scope: the broader "human eye review" pass across every QA state for correctness, beyond the two bugs already found and fixed above.Rebased onto current
main(3620f63) to pick up the preview.tsx decorator removal and the new widget packages; the only conflict was incitizenClaimWidgetStories.tsx, wheremainhad already refactored the story shell — the theme-prop threading was re-applied to the new shape rather than mechanically resolved.Test plan
pnpm install+ full workspacepnpm build(13/13) +pnpm build-storybooksucceed post-rebasenpx tsc --noEmitinexamples/storybookshows no errors in any file this PR touches (remaining errors are pre-existing, inai-credits-widget/goodreserve-widgetstory files and atamagui/@types/nodeissue, unrelated to this change)defaultThemeandbrandPresetcontrols visibly change each widget's rendering in both dark and light mode, and that Card/Stepper/Drawer controls visibly change their primitivespnpm test:demo(Playwright) — 33/35 passed; the 2 failures instaking-migration-widget/states.spec.ts(ReadyStory/MigratingStory) predate this PR and are still being investigated separately (not attributed to this change)