96 lines
3.4 KiB
TypeScript
96 lines
3.4 KiB
TypeScript
'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;
|