Files
baya-monorepo/client/src/components/common/Money/Money.tsx
T
2026-07-19 09:49:25 +03:30

96 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { FunctionComponent } from 'react';
import { useLocale, useTranslations } from 'next-intl';
import Box from '@mui/material/Box';
import Typography, { TypographyProps } from '@mui/material/Typography';
import { formatIrrToToman } from '@/utils';
export type MoneySize = 'sm' | 'md' | 'lg' | 'xl';
export type MoneyTone = 'default' | 'emphasis' | 'muted' | 'error';
const SIZE_VARIANT: Record<MoneySize, TypographyProps['variant']> = {
sm: 'body2',
md: 'body1',
lg: 'h6',
// The C6/confirmation "prominent total" hero figure — the single most important number on a payment
// screen gets its own weight class, above the existing card/row emphasis.
xl: 'h4',
};
// House weight system has no 600 face (see typography.ts) — 700 for the strong tones, 500/400 otherwise.
const TONE_STYLE: Record<MoneyTone, { color: string; fontWeight: 400 | 500 | 700 }> = {
default: { color: 'inherit', fontWeight: 500 },
emphasis: { color: 'var(--bal-money-emphasis)', fontWeight: 700 },
muted: { color: 'var(--bal-text-secondary)', fontWeight: 400 },
error: { color: 'var(--bal-error)', fontWeight: 700 },
};
export interface MoneyProps extends Omit<TypographyProps, 'children' | 'color'> {
/** A served IRR digit-string, straight off the wire. `<Money>` only formats — it never computes. */
amountIrr: string;
size?: MoneySize;
tone?: MoneyTone;
/** Marks the amount as a deduction (a commission, a fee): prefixes an explicit `` sign, held together
* with the digits in a forced `dir="ltr"` run so the sign never gets swallowed inside RTL text — never
* relies on a bare Unicode minus floating in a Persian paragraph. Defaults `tone` to `muted` unless the
* caller overrides it. */
deduction?: boolean;
/** Hides the "تومان"/"Toman" unit label — the bare grouped number only. */
hideUnit?: boolean;
/** Renders the amount with a line-through (e.g. a superseded price next to a discounted one). */
strikethrough?: boolean;
}
/**
* The single display primitive for every served IRR amount: formats via `utils/money.ts` (BigInt,
* Toman + Persian digits) and never computes a figure itself. Replaces the 24 hand-assembled
* `formatIrrToToman(x) + t('currency_toman')` call sites — including the ones that colored the total
* terracotta (`--bal-secondary` fails AA contrast for small text; emphasis uses `--bal-money-emphasis`
* instead, see the frontend-designer skill).
* @component Money
*/
const Money: FunctionComponent<MoneyProps> = ({
amountIrr,
size = 'md',
tone,
deduction = false,
hideUnit = false,
strikethrough = false,
sx,
...rest
}) => {
const locale = useLocale();
const tc = useTranslations('common');
const effectiveTone = tone ?? (deduction ? 'muted' : 'default');
const { color, fontWeight } = TONE_STYLE[effectiveTone];
const formatted = formatIrrToToman(amountIrr, locale);
return (
<Typography
variant={SIZE_VARIANT[size]}
component="span"
data-money
data-deduction={deduction ? 'true' : undefined}
sx={{
color,
fontWeight,
textDecoration: strikethrough ? 'line-through' : undefined,
...sx,
}}
{...rest}
>
{deduction ? (
<Box component="span" dir="ltr" sx={{ display: 'inline-block' }}>
{''}
{formatted}
</Box>
) : (
formatted
)}
{hideUnit ? null : ` ${tc('currency_toman')}`}
</Typography>
);
};
export default Money;