Переключение валют в интерфейсе
1) Принципы
1. Сначала смысл, потом UI. Отделяйте валюту аккаунта (бухгалтерская истина) от валюты отображения (удобство) и валюты операции (фактическая конвертация денег).
2. Нулевая двусмысленность. Показывайте код + символ при риске путаницы (`US$`, `CA$`, `MXN`, `R$`). Для ₴/₸/₼ — всегда добавляйте код в деталях.
3. Честность курсов. Видно: источник курса, момент последнего обновления, включены ли комиссии/спрэд.
4. Стабильность ввода. Переключение валюты не должно «прыгать» значения ввода без явного согласия (особенно в формах ставок/депозитов).
5. Локализация форматов. Разделители, пробелы, знак валюты — по локали пользователя; точность — по валюте.
2) Модели переключения
Отображение (display-only): все расчеты остаются в валюте аккаунта, UI показывает эквивалент в выбранной валюте. Используйте для каталога, профиля, аналитики.
Гибрид (soft convert): отображение в выбранной валюте + подтверждение операции в валюте аккаунта (показываем обе).
Операционная (hard convert): пользователь меняет валюту операции (депозит/вывод/ставка). Нужны явные курсы, комиссии, время фиксации.
Правило: по умолчанию — display-only, а «жесткую» конверсию включайте только в соответствующих потоках (касса, вывод, перевод средств).
3) Контролы и размещение
Переключатель валюты в шапке/на панели профиля (иконка «₴/€/$» или код валюты).
Селектор: поиск по коду/названию/символу; избранные/частые валюты — сверху.
Внутри форм (депозит/ставка): компактный селектор справа от поля суммы, рядом подсказка «≈ эквивалент в XXX».
Мобильный паттерн: bottom sheet со списком и вводом для фильтрации.
html
<button aria-haspopup="listbox" aria-expanded="false" class="currency-switch">UAH</button>
<ul role="listbox" class="currency-menu" hidden>
<li role="option" aria-selected="true">UAH — ₴</li>
<li role="option">USD — US$</li>
<li role="option">EUR — €</li>
<li role="option">TRY — ₺</li>
</ul>
4) Форматирование и точность
Минорные единицы: храните суммы в целых минимальных единицах (копейки, центы, сатоши).
Десятичные разряды по валюте:- 0: JPY, KRW, CLP
- 2: USD, EUR, UAH, TRY
- 3+: некоторые валюты ZAR (2), KWD (3), крипто (4–8)
- Криптовалюты: показывайте до 8 знаков (динамическая точность, но с нижней границей для читабельности).
- Табличные цифры: `font-variant-numeric: tabular-nums;` для выравнивания колонок.
js const fmt = (amountMinor, currency, locale) => {
const fraction = { JPY:0, KRW:0, KWD:3 }[currency]?? 2;
return new Intl.NumberFormat(locale, { style:'currency', currency, minimumFractionDigits:fraction, maximumFractionDigits:fraction })
.format(amountMinor / 10fraction);
};
fmt(200000, 'UAH', 'uk-UA'); // 2 000,00 ₴
5) Курсы и обновления
Источник: фиксируйте провайдера курса (внутренний прайсинг/банк/FX-API).
Кэш: обновляйте курсы с разумной частотой (напр., каждые 60–300 сек) + инкрементальные обновления по требованию.
Время фиксации: отображайте `обновлено N мин назад` и время фиксации при оформлении операции.
Спрэд/комиссия: показывайте явную строку: «Курс 1 USD = 36,60 UAH (включен спрэд 1.5%)».
Округления: банковское или обычное — выберите одно и зафиксируйте в политике.
6) UX текста и пояснений
Эквивалент: «≈ 52,10 €» — рядом с суммой, приглушенным цветом, обновляется в реальном времени.
Юридические оговорки: «Фактический курс и комиссия будут зафиксированы на шаге подтверждения».
Длинные коды: используйте tooltips/вторичную строку: «US$ — доллар США».
Конверсия в корзине: не меняйте «итого» без пояснения; показывайте строчку пересчета.
7) Доступность (A11y)
`role="listbox/option"` у селектора валют.
Поддержка клавиатуры: стрелки, Enter, Type-ahead по коду/названию.
Чтение для SR: «Валюта отображения: UAH — украинская гривня».
Цвет ≠ единственный носитель смысла (всегда есть код/текст).
RTL: числа/коды в `dir="ltr"` внутри арабских строк.
8) Перформанс и кэширование
Курсы — в памяти + localStorage с TTL (например, 5 мин).
Батч-обновления: пересчитывайте эквиваленты пачками (requestAnimationFrame, дебаунс 100–200 мс).
Не триггерите лишний ререндер списка при колебаниях курса < порога (например, 0,1%).
9) Специфика iGaming
Валюта аккаунта — базовая отчетность (депозиты, баланс, история).
Валюта ставки: обычно = валюте аккаунта; если задана другая — покажите двойной блок: «Списано X XXX в USD (≈ Y YYY в UAH)».
Фиксация при расчете: выигрыши конвертируются по курсу на момент расчета, а не ставки — это должно быть видно в деталях купона/истории.
Депозит/вывод: курс и комиссия PSP/банка — отдельной строкой; ETA по методу.
Лимиты ответственной игры: определяются в валюте аккаунта; если UI в другой валюте — показывайте оба значения.
Турниры и призы: валюта призового фонда фиксируется; при отображении эквивалент — ориентировочный, с пометкой.
10) Антипаттерны
«Магическая» смена значения в поле ввода при переключении валюты — без явного согласия.
Использование одного символа «$» без кода страны.
Скрытая комиссия в курсе (нет строки про спрэд).
Перемешивание локали и валюты (форматируете по `en-US` для `UAH`).
Жесткая точность «2 знака» для JPY/KRW или «8 знаков» для всех криптовалют.
Пересчет исторических транзакций «задним числом» по текущему курсу — без отметки «пересчет».
11) Токены дизайн-системы (пример)
json
{
"currency": {
"default": "UAH",
"displayList": ["UAH","USD","EUR","TRY","PLN","BRL","MXN"],
"fractions": { "JPY":0, "KRW":0, "KWD":3, "BTC":8 },
"showCodeWithSymbol": ["USD","CAD","AUD","NZD"],
"approxPrefix": "≈ "
},
"format": {
"tabularNums": true,
"grouping": "locale",
"negative": "−"
},
"fx": {
"ttlSec": 300,
"changeThresholdPct": 0.1,
"showSpread": true
}
}
12) Сниппеты
Переключатель валюты (React, контекст + Intl)
tsx import { createContext, useContext, useState, useMemo } from 'react';
type Cur = 'UAH' 'USD' 'EUR' 'TRY';
const CurrencyCtx = createContext<{cur:Cur,set:(c:Cur)=>void, rate:(from:Cur,to:Cur)=>number}>({cur:'UAH',set:()=>{},rate:()=>1});
export function CurrencyProvider({children}:{children:React.ReactNode}){
const [cur, set] = useState<Cur>('UAH');
// fx: получить из кэша/апи; здесь — заглушка const table = { UAH:{USD:0.027,EUR:0.025,TRY:0.89,UAH:1}, USD:{UAH:36.6,EUR:0.93,TRY:33.0,USD:1}, EUR:{UAH:39.2,USD:1.07,TRY:35.4,EUR:1}, TRY:{UAH:1.12,USD:0.030,EUR:0.028,TRY:1} };
const rate = (from:Cur,to:Cur)=> table[from][to];
const value = useMemo(()=>({cur, set, rate}),[cur]);
return <CurrencyCtx.Provider value={value}>{children}</CurrencyCtx.Provider>;
}
export function useCurrency(){ return useContext(CurrencyCtx); }
export function Money({minor, iso}:{minor:number, iso:Cur}){
const { cur, rate } = useCurrency();
const fraction = { JPY:0, KRW:0, KWD:3 }[cur as any]?? 2;
const v = (minor/10fraction) rate(iso, cur);
return <span style={{fontVariantNumeric:'tabular-nums'}}>{new Intl.NumberFormat(undefined,{style:'currency',currency:cur, minimumFractionDigits:fraction, maximumFractionDigits:fraction}).format(v)}</span>;
}
Двойное отображение (операционная конверсия)
html
<div class="amount">
<label>Сумма депозита</label>
<div class="row">
<input type="number" inputmode="decimal" aria-describedby="fxnote">
<select aria-label="Валюта операции">
<option>USD</option><option>EUR</option><option>UAH</option>
</select>
</div>
<small id="fxnote">≈ 2 000,00 ₴ · Курс будет зафиксирован на следующем шаге</small>
</div>
13) Метрики
FX latency: время от переключения валюты до обновления всех полей (цель ≤ 150 мс).
Correctness rate: доля обращений в саппорт по «неверным суммам» (< 0,2%).
Display vs account mismatch: события, где пользователь путает валюты (снижаем подсказками).
CTR подсказки курса: клики по «Подробнее о курсе/комиссии».
Абандон кассы при конверсии: доля отказов, связанная с «внезапным» изменением суммы.
14) QA-чек-лист
Смысл и прозрачность
- Везде видна валюта аккаунта и/или операции.
- Для $ показан код страны (US$, CA$ и т. п.).
- Есть строка про курс, дату обновления и спрэд/комиссию.
Формат и точность
- Десятичные разряды по валюте (JPY=0, KWD=3, crypto=до 8).
- Локаль числа/валюты соответствует языку UI.
- Исторические операции не пересчитаны «по текущему курсу» без пометки.
Поведение
- Переключение валюты не меняет ввод без подтверждения.
- Эквивалент «≈» обновляется плавно и быстро.
- Селектор валют клавиатуро-доступен, работает Type-ahead.
iGaming-специфика
- В купоне: списание/выигрыш и их валюта подписаны, курс фиксации указан.
- В кассе: комиссии PSP/банка видны отдельно.
- В лимитах: показываются обе величины (аккаунтная и отображаемая).
RTL/A11y
- Коды/суммы читаются корректно в RTL (`dir="ltr"` для чисел).
- Контраст и фокус-индикаторы соответствуют AA.
15) Документация в дизайн-системе
Компоненты: `CurrencySwitch`, `Money`, `FxNote`, `DualAmount`.
Политика точности/округлений и единая функция форматирования.
Правила: «когда display-only», «когда hard-convert», «как показывать спрэд».
Справочник валют: код, символ, разряды, региональные коллизии символов.
Галерея Do/Don’t: «$ без кода», автопрыжок ввода, скрытые комиссии.
Краткое резюме
Переключение валют — это не просто селектор «₴/€/$». Это ясная модель денег (аккаунтная валюта vs отображение vs операция), честный курс с комиссией, корректное форматирование по локали и бережное поведение полей ввода. Зафиксируйте правила в дизайн-системе, автоматизируйте форматирование и кэширование курсов — и пользователи будут уверенно работать с суммами, не сомневаясь в цифрах и не теряя деньги на «невидимых» спрэдах.