mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
178 lines
5.5 KiB
TypeScript
178 lines
5.5 KiB
TypeScript
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<string, string | number>;
|
|
|
|
export interface HeadingProps extends Omit<React.HTMLAttributes<HTMLHeadingElement>, '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<HeadingLevel, string> = {
|
|
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
|
|
* <Heading tKey="dashboard.title" />
|
|
* <Heading tKey="dashboard.greeting" replacements={{ name: 'John' }} />
|
|
* <Heading level="h2">Direct Text</Heading>
|
|
*/
|
|
export const Heading = React.forwardRef<HTMLHeadingElement, HeadingProps>(
|
|
({ 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 (
|
|
<Component ref={ref} className={clsx(baseStyles, className)} {...props}>
|
|
{content}
|
|
</Component>
|
|
);
|
|
}
|
|
);
|
|
|
|
Heading.displayName = 'Heading';
|
|
|
|
export type TextVariant = 'body' | 'small' | 'muted' | 'bold' | 'italic';
|
|
|
|
export interface TextProps extends Omit<React.HTMLAttributes<HTMLElement>, '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<TextVariant, string> = {
|
|
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
|
|
* <Text tKey="dashboard.description" />
|
|
* <Text tKey="dashboard.itemsCount" replacements={{ count: 5 }} />
|
|
* <Text variant="muted">Direct text content</Text>
|
|
*/
|
|
export const Text = React.forwardRef<HTMLElement, TextProps>(
|
|
({ 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 (
|
|
<Component ref={ref as any} className={clsx(baseStyles, className)} {...props}>
|
|
{content}
|
|
</Component>
|
|
);
|
|
}
|
|
);
|
|
|
|
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
|
|
* <Price value={99.99} currency="EUR" />
|
|
* <Price value={99.99} showUnit unitTKey="common.currency.perUnit" />
|
|
* <Price value={99.99} showUnit unit="per hour" />
|
|
*/
|
|
export const Price: React.FC<PriceProps> = ({
|
|
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<string, string> = {
|
|
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 (
|
|
<span className={clsx(sizeClasses[variant], 'text-primary', className)}>
|
|
{formattedValue}
|
|
{showUnit && unitLabel && (
|
|
<span className="text-sm text-muted-foreground ml-1">{unitLabel}</span>
|
|
)}
|
|
</span>
|
|
);
|
|
};
|