Загрузка данных
.legend {
display: flex;
width: 100%;
flex-direction: column;
gap: var(--space-0250);
color: var(--neutral-12);
.row {
display: flex;
justify-content: start;
align-items: center;
flex-wrap: wrap;
gap: var(--space-0500);
}
.item {
display: flex;
gap: var(--space-0250);
}
.item,
.symbol {
font-size: var(--space-0750);
line-height: var(--space-1000);
font-weight: var(--font-medium);
}
}
import { Observable } from 'rxjs';
import { LegendSeriesRow } from '@src/components/LegendSeriesRow';
import { IndicatorsIds } from '@src/core/Indicators';
import { CompareMode, LegendVM, OHLCConfig } from '../../types';
import { formatPrice, getPriceColor, useObservable } from '../../utils';
import styles from './index.module.scss';
export interface LegendProps {
ohlcConfig?: OHLCConfig;
viewModel: Observable<LegendVM>;
onCompareRemove?: (symbol: string, mode: CompareMode) => void;
onIndicatorEdit?: (indicator: IndicatorsIds) => void;
onIndicatorRemove?: (indicatorId: IndicatorsIds) => void;
}
export const LegendComponent = ({
ohlcConfig,
viewModel: vm,
onCompareRemove,
onIndicatorRemove,
onIndicatorEdit,
}: LegendProps) => {
const viewModel = useObservable(vm);
const ohlc = viewModel?.ohlc ?? null;
const symbol = viewModel?.symbol ?? null;
const compareItems = viewModel?.compareItems ?? [];
const indicatorItems = viewModel?.indicatorItems ?? [];
const showOhlc = ohlcConfig?.show;
const priceColor = getPriceColor(ohlc);
const renderLegendItem = (label: string, value?: string | number, show = true) => {
if (!show || !value) return null;
return (
<div className={styles.item}>
{label} <span style={{ color: priceColor }}>{value}</span>
</div>
);
};
return (
<section className={styles.legend}>
<div className={styles.row}>
{symbol && <div className={styles.symbol}>{symbol}</div>}
{showOhlc && (
<>
{renderLegendItem('Отк.', formatPrice(ohlc?.open), !!ohlc?.open)}
{renderLegendItem('Макс.', formatPrice(ohlc?.high), !!ohlc?.high)}
{renderLegendItem('Мин.', formatPrice(ohlc?.low), !!ohlc?.low)}
{renderLegendItem('Закр.', formatPrice(ohlc?.close), !!ohlc?.close)}
{renderLegendItem(
'Изм.',
`${formatPrice(ohlc?.percentageChange)}%`,
!!ohlc?.absoluteChange && !!ohlc?.percentageChange,
)}
</>
)}
</div>
{indicatorItems.map(({ id, label, values }) => (
<LegendSeriesRow
key={id}
label={label}
values={values}
onEdit={() => onIndicatorEdit?.(id)}
onRemove={() => onIndicatorRemove?.(id)}
/>
))}
{compareItems.map(({ symbol: itemSymbol, mode, values }) => (
<LegendSeriesRow
key={`${itemSymbol}|${mode}`}
label={itemSymbol}
values={values}
onRemove={() => onCompareRemove?.(itemSymbol, mode)}
/>
))}
</section>
);
};
.item {
width: 100%;
max-width: max-content;
display: flex;
align-items: center;
gap: var(--space-0500);
.priceWrapper {
position: relative;
display: flex;
justify-content: start;
align-items: center;
gap: var(--space-0500);
}
.symbol,
.price {
font-size: var(--space-0750);
font-weight: var(--font-medium);
}
.button {
display: none;
width: var(--space-1000);
height: var(--space-1000);
border-radius: var(--space-0125);
color: var(--neutral-13);
padding: 0;
min-width: 0;
opacity: 0;
pointer-events: none;
& p {
display: flex;
justify-content: center;
align-items: start;
padding: 0;
svg {
width: var(--space-0875);
height: var(--space-0875);
}
}
}
&:hover .button {
display: block;
opacity: 1;
pointer-events: auto;
}
&:hover .price {
display: none;
opacity: 0;
pointer-events: none;
}
}
import { Button } from 'exchange-elements/v2';
import { GearIcon, TrashIcon } from '@src/components/Icon';
import { LegendValueItem } from '@src/types';
import styles from './index.module.scss';
interface LegendSeriesRowProps {
label: string;
values?: LegendValueItem[];
onRemove?: () => void;
onEdit?: () => void;
}
export function LegendSeriesRow({ label, values, onRemove, onEdit }: LegendSeriesRowProps) {
return (
<div className={styles.item}>
<div className={styles.symbol}>{label}</div>
<div className={styles.priceWrapper}>
{values?.map(({ id, value, color }) => (
<span
key={id}
style={{ color }}
className={styles.price}
>
{value}
</span>
))}
{onEdit && (
<Button
size="sm"
className={styles.button}
onClick={onEdit}
label={<GearIcon />}
/>
)}
{onRemove && (
<Button
size="sm"
className={styles.button}
onClick={onRemove}
label={<TrashIcon />}
/>
)}
</div>
</div>
);
}