This commit is contained in:
2026-06-06 16:23:55 +03:00
parent 517059d53e
commit 8487ac5ca0
12 changed files with 373 additions and 222 deletions

161
dist/assets/index-CF-a3AIG.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/index-f9EVcxOv.css vendored Normal file

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View File

@@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ЭКСА — Ваш мост в мир цифровых активов</title>
<script type="module" crossorigin src="/assets/index-CPUmmycy.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DPCnfw2D.css">
<script type="module" crossorigin src="/assets/index-CF-a3AIG.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-f9EVcxOv.css">
</head>
<body>
<div id="root"></div>

View File

@@ -44,45 +44,6 @@
margin-top: -12px;
}
/* Селект срока ожидания — стилизован под FormField input */
.field {
display: flex;
flex-direction: column;
gap: 6px;
}
.fieldLabel {
font-size: 12px;
color: var(--text-secondary);
font-weight: 600;
letter-spacing: 0.08em;
}
.select {
width: 100%;
height: 48px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: var(--text-primary);
font-size: 14px;
font-family: var(--font-sans);
padding: 0 16px;
outline: none;
cursor: pointer;
transition: border-color 0.2s, box-shadow 0.2s;
}
.select:focus {
border-color: var(--interactive);
box-shadow: 0 0 0 3px rgba(74, 109, 255, 0.15);
}
.select option {
background: var(--glass-bg, #1a1a2e);
color: var(--text-primary);
}
/* Панель условий / комиссии */
.infoCol {
display: flex;

View File

@@ -1,5 +1,5 @@
import { useState } from 'react'
import { FormField } from '@shared/ui'
import { FormField, Select } from '@shared/ui'
import styles from './LegalConverterPage.module.css'
const MIN_ORDER = 500_000
@@ -70,23 +70,16 @@ export function LegalConverterPage() {
</p>
)}
<div className={styles.field}>
<label className={styles.fieldLabel} htmlFor="term">
Срок ожидания операции
</label>
<select
id="term"
className={styles.select}
value={days}
onChange={(e) => setDays(Number(e.target.value))}
>
{TERM_OPTIONS.map((o) => (
<option key={o.days} value={o.days}>
{dayLabel(o.days)} комиссия {(o.rate * 100).toFixed(1)} %
</option>
))}
</select>
</div>
<Select
id="term"
label="Срок ожидания операции"
value={days}
onChange={setDays}
options={TERM_OPTIONS.map((o) => ({
value: o.days,
label: `${dayLabel(o.days)} — комиссия ${(o.rate * 100).toFixed(1)} %`,
}))}
/>
<FormField
label="Как к вам обращаться"

View File

@@ -0,0 +1,101 @@
.field {
position: relative;
display: flex;
flex-direction: column;
gap: 6px;
}
.label {
font-size: 12px;
color: var(--text-secondary);
font-weight: 600;
letter-spacing: 0.08em;
}
.trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
width: 100%;
height: 48px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: var(--text-primary);
font-size: 14px;
font-family: var(--font-sans);
text-align: left;
padding: 0 16px;
cursor: pointer;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
.trigger:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.trigger:focus-visible,
.triggerOpen {
border-color: var(--interactive);
box-shadow: 0 0 0 3px rgba(74, 109, 255, 0.15);
}
.value {
color: var(--text-primary);
}
.placeholder {
color: var(--text-secondary);
}
.arrow {
flex-shrink: 0;
color: var(--text-secondary);
font-size: 12px;
transition: transform 0.2s;
}
.arrowOpen {
transform: rotate(180deg);
}
.dropdown {
position: absolute;
top: calc(100% + 6px);
left: 0;
right: 0;
z-index: 20;
margin: 0;
padding: 6px;
list-style: none;
background: var(--bg-mid);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 10px;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
max-height: 280px;
overflow-y: auto;
}
.option {
padding: 11px 12px;
border-radius: 8px;
font-size: 14px;
color: var(--text-primary);
cursor: pointer;
transition: background-color 0.15s;
}
.option:hover {
background: rgba(255, 255, 255, 0.08);
}
.optionSelected {
background: rgba(91, 61, 184, 0.35);
}
.optionSelected:hover {
background: rgba(91, 61, 184, 0.45);
}

View File

@@ -0,0 +1,92 @@
import { useEffect, useRef, useState } from 'react'
import styles from './Select.module.css'
export interface SelectOption<T extends string | number = string> {
value: T
label: string
}
interface Props<T extends string | number> {
value: T
options: SelectOption<T>[]
onChange: (value: T) => void
label?: string
placeholder?: string
id?: string
disabled?: boolean
}
export function Select<T extends string | number>({
value,
options,
onChange,
label,
placeholder = 'Выберите',
id,
disabled,
}: Props<T>) {
const [open, setOpen] = useState(false)
const ref = useRef<HTMLDivElement>(null)
const selected = options.find((o) => o.value === value)
useEffect(() => {
if (!open) return
const handlePointer = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
}
const handleKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') setOpen(false)
}
document.addEventListener('mousedown', handlePointer)
document.addEventListener('keydown', handleKey)
return () => {
document.removeEventListener('mousedown', handlePointer)
document.removeEventListener('keydown', handleKey)
}
}, [open])
return (
<div className={styles.field} ref={ref}>
{label && (
<label className={styles.label} htmlFor={id}>
{label}
</label>
)}
<button
type="button"
id={id}
className={`${styles.trigger} ${open ? styles.triggerOpen : ''}`}
onClick={() => !disabled && setOpen((v) => !v)}
disabled={disabled}
aria-haspopup="listbox"
aria-expanded={open}
>
<span className={selected ? styles.value : styles.placeholder}>
{selected ? selected.label : placeholder}
</span>
<span className={`${styles.arrow} ${open ? styles.arrowOpen : ''}`} aria-hidden>
</span>
</button>
{open && (
<ul className={styles.dropdown} role="listbox">
{options.map((o) => (
<li
key={String(o.value)}
role="option"
aria-selected={o.value === value}
className={`${styles.option} ${o.value === value ? styles.optionSelected : ''}`}
onClick={() => {
onChange(o.value)
setOpen(false)
}}
>
{o.label}
</li>
))}
</ul>
)}
</div>
)
}

View File

@@ -0,0 +1,2 @@
export { Select } from './Select'
export type { SelectOption } from './Select'

View File

@@ -6,4 +6,6 @@ export { FormField } from './FormField'
export { Notification } from './Notification'
export { Pill } from './Pill'
export { PrimaryButton } from './PrimaryButton'
export { Select } from './Select'
export type { SelectOption } from './Select'
export { TokenIcon } from './TokenIcon'

File diff suppressed because one or more lines are too long