ui phase 1
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
'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';
|
||||
export type MoneyTone = 'default' | 'emphasis' | 'muted' | 'error';
|
||||
|
||||
const SIZE_VARIANT: Record<MoneySize, TypographyProps['variant']> = {
|
||||
sm: 'body2',
|
||||
md: 'body1',
|
||||
lg: 'h6',
|
||||
};
|
||||
|
||||
// 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;
|
||||
Reference in New Issue
Block a user