Skip to content
This repository was archived by the owner on Nov 12, 2025. It is now read-only.

Conversation

@ttsahi
Copy link
Contributor

@ttsahi ttsahi commented Oct 7, 2025

🎯 Add Generic Price and Address Components

Overview

This PR introduces two new foundational components to the headless-components library: Price and Address. These components provide flexible, internationalized solutions for displaying monetary values and addresses across different contexts and locales.

🚀 What's New

💰 Price Component

A comprehensive price display system built with composable primitives following Radix UI architecture patterns.

Key Features:

  • Multi-currency support with proper localization
  • Flexible rendering options - formatted, amount-only, or currency-only displays
  • Advanced formatting - configurable precision, currency display modes, and accounting formats
  • Currency service integration with Wix Services Manager
  • TypeScript-first with comprehensive type definitions

Components:

  • Price.Root - Context provider with currency service setup
  • Price.Amount - Raw amount display without currency formatting
  • Price.Currency - Currency symbol/code display
  • Price.Formatted - Fully formatted price with locale-aware formatting

🏠 Address Component

An international address formatting system powered by @fragaria/address-formatter.

Key Features:

  • International formatting - Supports 200+ countries with local conventions
  • Multiple display modes - Single formatted string or array of lines
  • Flexible formatting options - Abbreviations, country appending, custom country codes
  • TypeScript-first with comprehensive address data types
  • Layout flexibility - Custom rendering with access to formatted data

Components:

  • Address.Root - Context provider for address data
  • Address.Formatted - Single formatted address string
  • Address.Lines - Array of address lines for custom layouts

🎨 Component Architecture

Both components follow established patterns from the headless-components library:

Compound Component Pattern - Composable sub-components
Context-based Data Sharing - Efficient prop drilling elimination
AsChild Support - Full custom rendering flexibility
TypeScript Integration - Comprehensive type safety
Test ID Standardization - Consistent testing approach
Documentation Completeness - Full API documentation with examples

📝 Usage Examples

Basic Price Display

<Price.Root price={{ money: { amount: 29.99, currency: 'USD' } }}>
  <Price.Formatted className="text-2xl font-bold text-foreground" />
</Price.Root>

Custom Price Layout

<Price.Root price={{ money: { amount: 1999, currency: 'EUR' }, locale: 'de-DE' }}>
  <div className="flex items-center gap-2">
    <Price.Amount className="text-3xl font-bold text-foreground" />
    <Price.Currency className="text-lg text-secondary-foreground" />
  </div>
</Price.Root>

International Address Display

<Address.Root address={{
  houseNumber: '10',
  road: 'Downing Street', 
  city: 'London',
  postcode: 'SW1A 2AA',
  countryCode: 'GB'
}}>
  <Address.Formatted className="whitespace-pre-line font-paragraph" />
</Address.Root>

Custom Address Layout

<Address.Root address={addressData}>
  <Address.Lines asChild>
    {React.forwardRef(({ lines, ...props }, ref) => (
      <div ref={ref} {...props} className="space-y-1">
        {lines.map((line, index) => (
          <div key={index} className={index === 0 ? 'font-bold' : 'text-secondary-foreground'}>
            {line}
          </div>
        ))}
      </div>
    ))}
  </Address.Lines>
</Address.Root>

🛠️ Implementation Details

Price Component Integration

  • Currency Service: Integrated with @wix/services-manager-react for centralized currency/locale management
  • Formatting: Uses Intl.NumberFormat for proper locale-aware number formatting
  • Flexibility: Supports symbol, code, name, and narrow symbol currency displays

Address Component Integration

  • Formatter Library: Leverages @fragaria/address-formatter for international address standards
  • Country Support: 200+ countries with proper local formatting conventions
  • Customization: Configurable abbreviations, country appending, and fallback options

Code Quality

  • TypeScript Coverage: 100% TypeScript with exported interfaces
  • Testing Support: Comprehensive test IDs following established patterns
  • Documentation: Complete API documentation with usage examples
  • Architecture Compliance: Follows headless-components patterns and conventions

📋 Files Changed

Core Components

  • packages/headless-components/components/src/react/address.tsx - Address component implementation
  • packages/headless-components/components/src/react/price.tsx - Price component implementation
  • packages/headless-components/components/src/services/currency-service.ts - Currency service for Price

Documentation

  • docs/api/generic-components/ADDRESS_INTERFACE.md - Complete Address API documentation
  • docs/api/generic-components/PRICE_INTERFACE.md - Complete Price API documentation

Examples

  • examples/astro-components-demo/src/components/AddressExamples.jsx - Comprehensive Address usage examples
  • examples/astro-components-demo/src/components/PriceExamples.jsx - Comprehensive Price usage examples

✅ Checklist

  • Components follow headless-components architecture patterns
  • TypeScript interfaces and exports are complete
  • Test IDs follow established naming conventions
  • AsChild pattern implemented for custom rendering
  • Context pattern implemented for data sharing
  • Documentation includes comprehensive examples
  • International localization support implemented
  • Components are exported from main index

🎯 Impact

These components provide essential building blocks for e-commerce, booking, and other applications requiring price and address displays. They eliminate the need for custom formatting logic while ensuring international compatibility and accessibility.


This PR establishes the foundation for consistent price and address handling across all Wix headless components, with full internationalization support and flexible rendering options.

@guyofeck
Copy link
Contributor

guyofeck commented Oct 8, 2025

@ttsahi we need to derive the locale for @wix/essentials
regarding the price, we need to understand wether we need to add currency for essentials as well

range?: PriceRange;
discount?: Discount;
isOnSale?: boolean;
locale?: string; // For formatting - defaults to 'en-US'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should come from regional settings if not provided

@ttsahi ttsahi requested a review from carmelc October 21, 2025 07:27
interface Price {
money: Money;
locale?: string; // For formatting - defaults to 'en-US'
precision?: number; // Decimal places - default 2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default should be per locale, in Intl when not passing it, it is locale based

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the default, we get the locale & currency information from essentials

}

function formatAmount(amount: number, precision: number = 2): string {
return amount.toFixed(precision);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should not use precision this way, use minimumFractionDigits and maximumSignificantDigits instead, and don't default to 2 but leave undefined so by default it is based on locale

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

// Address.Formatted Component Props
interface AddressFormattedProps {
children?: AsChildChildren<{
formattedAddress: string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when using asChild with render props (function) also return the original address object, this way Event.Address can render formatted address by default but also allow the user to use render props with the address object instead (like parts in intl)
I would even call it parts

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants