ui phase 1

This commit is contained in:
hamid
2026-07-17 17:10:39 +03:30
parent f1cba6cf74
commit 370c1beefa
151 changed files with 4254 additions and 1840 deletions
@@ -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;