Skip to content

Map GoodWalletV2 UI components to GoodWidget Tamagui primitives #4

@L03TJ3

Description

@L03TJ3

Original prompt:

Use GitHub MCP to inspect the repository:

GoodDollar/GoodWalletV2
Context: We already have mapped, based on GoodWalletV2, the high-level design system and css into our local preset (local path: packages/ui/src/presets.ts) it also includes already claim-flow localized design definitions from GoodWalletV2. (local path: packages/claim-widget/src/ClaimWidget) and component like Drawer (local path: pacakges/ui/src/components/Drawer.tsx)

Use these as starting reference before starting to analyse and map GoodWalletV2's remaining components.

Goal: Map existing UI components from GoodWalletV2 to GoodWidget primitives using Tamagui.

Steps:

Explore GoodWalletV2 repository and identify reusable UI components
For each component:
name
purpose
usage patterns
design system: verify both what it uses from high-level design system (GoodWalletV2 path: src/ui/styles.css) and what is in GoodWalletV2 defined in a given .module.css (usually sibling file in same directory of a component).
Map each to:
Tamagui primitive (if exists)
or GoodWidget composition
Use Drawer.tsx as reference for correct pattern
Output:

structured mapping
recommended first-wave primitives
create new GitHub issue for GoodWidget

=== End of original prompt, below is the Agents contribution

Overview

This issue tracks iterative application of GoodWalletV2 design elements onto GoodWidget's existing demo primitives in packages/ui/src/components-test/ and packages/ui/src/components/.

We are not migrating all primitives at once. Instead, we start from GoodWalletV2 in-use components and add UI primitives on a per-need basis, iteratively.

Relationship to issue #3:
Issue #3 covers the first-wave migration of components-test primitives from Stack-based scaffolds to correct Tamagui-native equivalents. That migration is a pre-condition for several items below. Where a component needs issue #3 migration before GoodWalletV2 design can be cleanly applied, this is called out explicitly.

About components-test:
Files in packages/ui/src/components-test/ are scaffolded demos. They are not yet correctly mapped to the GoodWidget preset or using proper Tamagui primitives in all cases. Changes here require:

  1. Confirming or fixing the Tamagui primitive base (see issue [BOUNTY] Migrate First-Wave UI Primitives To Tamagui-Native Wrappers #3 mapping)
  2. Then applying GoodWalletV2 design values through the preset

Design System Reference

GoodWalletV2's styles.css defines two brand themes (theme-blue, theme-green) switching --token-primary between blue (#1a85ff) and green (#13c636). All semantic layers are already captured in the GoodWidget preset:

CSS token Preset key
--token-bg tokens.color.background / theme.background
--token-bg-raised tokens.color.surface / theme.backgroundHover
--token-bg-input tokens.color.backgroundInput / theme.backgroundPress
--token-bg-overlay tokens.color.backgroundOverlay / theme.backgroundOverlay
--token-primary tokens.color.primary / light_Button.background
--token-text tokens.color.text / theme.color
--token-text-muted tokens.color.textSecondary / theme.placeholderColor
--token-border tokens.color.border / theme.borderColor
--z-drawer (200) tokens.zIndex.4

Components

1. Icon — ❌ new component (no existing demo)

GoodWalletV2 source: src/ui/icon/Icon.tsx
Local file: none — needs to be created in packages/ui/src/components/
Tamagui base: no direct Tamagui Icon primitive; wrap SVG/icon-library elements using styled(Stack) or a named createComponent

What's needed:

  • Decide on icon library (react-icons as in V2, Lucide, or custom SVG set)
  • Create a named Icon component via createComponent with name: 'Icon'
  • Props: name, size (2xs2xl), color (primary/text/muted/error/success/inherit), spin, round
  • Map sizes to existing tokens.size.icon* tokens (icon2xs=12icon2xl=64)

Blocks: Button icon/list variants, Dialog close button, Toast status icons


2. Button⚠️ extend components-test/Button.tsx

GoodWalletV2 source: src/ui/button/Button.tsx + Button.module.css
Local file: packages/ui/src/components-test/Button.tsx
Current base: ButtonFrame = createComponent(Stack, { name: 'Button', ... }) — uses Stack, not Tamagui Button

Pre-condition (issue #3): Migrate ButtonFrame from Stack to Tamagui Button as its behavioral base before extending variants.

What's needed after migration:

  • Radius: V2 solid buttons use pill radius (9999px). Current default is $2 (8px). Update default to $full or $6 for brand alignment.
  • Missing variants to add:
Variant Description
pill Badge/chip style — height: 35, borderRadius: $full, uppercase 11px, muted bg + colored text
icon Icon-only, transparent bg — sizes sm/md/lg/larger (16–35px)
text Full-width text link — 14px 500w; hover changes opacity (not bg, unlike ghost)
list Icon + label row — full width, 42px min height, left-aligned
  • Text color on solid: V2 uses --black text on --main bg for green theme. Current light_Button hardcodes white. Note this theme-level distinction.
  • Loading state: V2 uses shimmer overlay. GoodWidget uses Spinner. Document this difference.

3. Separator⚠️ extend components-test/Separator.ts

GoodWalletV2 source: src/ui/divider/Divider.tsx + Divider.module.css
Local file: packages/ui/src/components-test/Separator.ts
Current base: createComponent(YStack, { name: 'Separator', height: 1, ... }) — uses YStack

Pre-condition (issue #3): Evaluate replacing YStack base with Tamagui's own Separator primitive for semantic correctness.

What's needed after base decision:

  • size variant: sm = 1px, md = 2px, lg = 4px (currently only 1px)
  • color variant: default ($borderColor), muted ($borderColor + opacity), primary ($primary)

4. Text⚠️ extend components-test/Text.ts

GoodWalletV2 source: src/ui/typography/Typography.tsx
Local file: packages/ui/src/components-test/Text.ts
Current base: createComponent(TamaguiText, { name: 'GWText', ... }) — already wraps Tamagui Text correctly; no issue #3 pre-condition

What's needed:

  • Add truncate prop — single-line ellipsis (numberOfLines={1}, overflow: hidden, textOverflow: ellipsis)
  • Add noWrap prop — whiteSpace: 'nowrap'
  • Missing color levels not in current theme:
    • text-soft (#ccc) — between $color (white) and $placeholderColor (grey500); consider adding colorSoft semantic key to light/dark themes in presets.ts
    • text-tertiary (#4d4d4d) — exists as $grey600 token but not as a theme semantic key; consider adding colorDim

5. Dialog — ❌ new component (no existing demo)

GoodWalletV2 source: src/ui/dialog/Dialog.tsx + dialogStore.ts
Local file: none — needs to be created in packages/ui/src/components/
Tamagui base: Tamagui Dialog primitive

What's needed:

  • Use Tamagui Dialog as the behavioral base
  • Styled subparts: DialogOverlay (name: 'DialogOverlay'), DialogFrame (name: 'Dialog', extends Card)
  • Add light_Dialog / dark_Dialog component theme entries in presets.ts
  • Visual spec: centered modal, $backgroundOverlay overlay, $backgroundHover container bg, borderRadius="$4" (16px), width={345}, padding="$8" (32px)
  • Optional image, title (20px/600w), body text (12px/400w), accept + optional reject CTAs, optional close icon button
  • Enter/exit opacity animation
  • Queue-based store: createDialog, updateStatus, useDialog

6. Toast⚠️ extend components-test/Toast.tsx

GoodWalletV2 source: src/ui/notifications/Notifications.tsx + notificationStore.ts
Local file: packages/ui/src/components-test/Toast.tsx
Current base: ToastFrame = createComponent(Stack, { name: 'Toast', ... }) — uses Stack, not Tamagui Toast

Pre-condition (issue #3 second wave): Migrate ToastFrame from Stack to Tamagui Toast as its behavioral base before extending.

What's needed after migration:

  • ToastContainer — fixed-position wrapper at widget boundary bottom (max-width 768px, z-1000)
  • toastStore — queue with createToast, updateToast, removeToast, useToast
  • status variant on ToastFrame: pending (spinner icon), success (check icon), error (exclamation icon) — depends on Icon component (Add iframe/webview bridge package and runnable Expo demo #1 above)
  • Progress bar — animated bottom bar (background: $primary, 0→100% in 3s) for auto-close toasts
  • Enter/exit animation (opacity + max-height)

Public API: Toast, ToastContainer, createToast, updateToast, removeToast, useToast


Implementation Order (recommended)

  1. Icon — unblocked; needed by Button list/icon variants, Dialog close button, Toast status icons
  2. Button additional variants — after issue [BOUNTY] Migrate First-Wave UI Primitives To Tamagui-Native Wrappers #3 Button migration + Icon
  3. Separator enhancements — after issue [BOUNTY] Migrate First-Wave UI Primitives To Tamagui-Native Wrappers #3 Separator base decision; quick and self-contained
  4. Text enhancementstruncate, noWrap, optional colorSoft/colorDim theme keys; no issue [BOUNTY] Migrate First-Wave UI Primitives To Tamagui-Native Wrappers #3 pre-condition
  5. Dialog — new named primitive + store + theme entries; after Icon. should be based on tamagui's Dialog.
  6. Toast / notification system — after issue [BOUNTY] Migrate First-Wave UI Primitives To Tamagui-Native Wrappers #3 Toast migration + Icon

All finalized components should be moved over to, or newly created in components folder


Related files

  • Token/theme preset: packages/ui/src/presets.ts
  • Stable component examples: packages/ui/src/components/Card.ts, packages/ui/src/components/Drawer.tsx
  • Demo primitives to extend: packages/ui/src/components-test/
  • Claim widget (in-use reference): packages/claim-widget/src/ClaimWidget.tsx
  • Component factory: packages/ui/src/createComponent.ts
  • GoodWalletV2 source: src/ui/ in GoodDollar/GoodWalletV2

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions