import { useTranslation } from '@/hooks/useI18n.tsx'; import { clsx } from 'clsx'; import React from 'react'; export type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; export type TranslationReplacements = Record; export interface HeadingProps extends Omit, 'children'> { level?: HeadingLevel; as?: HeadingLevel; /** Translation key (e.g., 'dashboard.title') */ tKey?: string; /** Translation replacements for interpolation (e.g., { name: 'John' }) */ replacements?: TranslationReplacements; /** Direct text content (alternative to tKey) */ children?: React.ReactNode; className?: string; } const headingStyles: Record = { h1: 'font-serif text-2xl sm:text-3xl font-bold tracking-tight text-foreground', h2: 'text-2xl font-serif font-semibold text-foreground', h3: 'text-lg font-semibold text-foreground', h4: 'text-base font-semibold text-foreground', h5: 'text-sm font-semibold text-foreground', h6: 'text-xs font-semibold text-foreground', }; /** * Heading component for semantic headings with consistent styling * Supports both translation keys and direct text content * * @example * * * Direct Text */ export const Heading = React.forwardRef( ({ level = 'h1', as, tKey, replacements, children, className, ...props }, ref) => { const { t } = useTranslation(); const Component = as || level; const baseStyles = headingStyles[level]; // Use translation if tKey is provided, otherwise use children // If both are provided, tKey takes precedence const content = tKey ? t(tKey, replacements) : children; return ( {content} ); } ); Heading.displayName = 'Heading'; export type TextVariant = 'body' | 'small' | 'muted' | 'bold' | 'italic'; export interface TextProps extends Omit, 'children'> { variant?: TextVariant; as?: 'p' | 'span' | 'div'; /** Translation key (e.g., 'dashboard.description') */ tKey?: string; /** Translation replacements for interpolation (e.g., { count: 5 }) */ replacements?: TranslationReplacements; /** Direct text content (alternative to tKey) */ children?: React.ReactNode; className?: string; } const textStyles: Record = { body: 'text-base text-foreground', small: 'text-sm text-foreground', muted: 'text-sm text-muted-foreground', bold: 'text-base font-semibold text-foreground', italic: 'text-base text-muted-foreground italic', }; /** * Text component for consistent text styling * Supports both translation keys and direct text content * * @example * * * Direct text content */ export const Text = React.forwardRef( ({ variant = 'body', as = 'p', tKey, replacements, children, className, ...props }, ref) => { const { t } = useTranslation(); const Component = as; const baseStyles = textStyles[variant]; // Use translation if tKey is provided, otherwise use children // If both are provided, tKey takes precedence const content = tKey ? t(tKey, replacements) : children; return ( {content} ); } ); Text.displayName = 'Text'; export interface PriceProps { value: number; currency?: string; variant?: 'large' | 'medium' | 'small'; showUnit?: boolean; /** Translation key for unit label (e.g., 'common.currency.unit') */ unitTKey?: string; /** Direct unit text (alternative to unitTKey) */ unit?: string; /** Use locale-aware number formatting based on current language */ useLocale?: boolean; className?: string; } /** * Price component for displaying currency values with consistent formatting * Supports localized currency formatting and unit labels via translation keys * * @example * * * */ export const Price: React.FC = ({ value, currency = 'EUR', variant = 'large', showUnit = false, unitTKey, unit, useLocale = true, className, }) => { const { t, lang } = useTranslation(); // Get locale for number formatting (e.g., 'en-US', 'ru-RU', 'tt-TT') const localeMap: Record = { en: 'en-US', ru: 'ru-RU', tt: 'tt-TT', }; const locale = useLocale ? localeMap[lang] || 'en-US' : 'en-US'; const formattedValue = new Intl.NumberFormat(locale, { style: 'currency', currency, minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(value); const sizeClasses = { large: 'text-2xl font-bold', medium: 'text-lg font-semibold', small: 'text-base font-medium', }; // Get unit label from translation or use direct unit const unitLabel = unitTKey ? t(unitTKey) : unit; return ( {formattedValue} {showUnit && unitLabel && ( {unitLabel} )} ); };