Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
fa46bfa
Update tradebox header tabs
qwinndev Nov 28, 2025
aacdae0
Update leverage component
qwinndev Nov 30, 2025
2608e4b
Fix TradeBoxHeaderTabs rounding
qwinndev Nov 30, 2025
892dcc2
Update Pool and Collateral fields
qwinndev Nov 30, 2025
04f6216
Update fields placement
qwinndev Dec 1, 2025
870b6f9
Update tradebox fields with responsivness
qwinndev Dec 2, 2025
2746a18
Update componennts names from row to field
qwinndev Dec 2, 2025
43af4aa
Add best pool option to PoolSelector
qwinndev Dec 2, 2025
37b99e8
Fix single pool selection and persist best mode enabling
qwinndev Dec 2, 2025
f35e059
Init margin fields
qwinndev Dec 3, 2025
181a585
Update margin fields with designs
qwinndev Dec 3, 2025
0798082
Price field for margin fields
qwinndev Dec 3, 2025
eac92e6
Small UI improvements in tradebox
qwinndev Dec 4, 2025
73a660a
Update submit tradebox button text
qwinndev Dec 4, 2025
8d751f4
Add TP/SL tooltip and update execution details button bg
qwinndev Dec 4, 2025
f110e9a
Fix naming and remove old sidecar logic
qwinndev Dec 5, 2025
3352c9f
Update sidecar orders UI
qwinndev Dec 5, 2025
0d32336
Tradebox display mode change fixes
qwinndev Dec 5, 2025
4200903
Fix sidecar order dropdown
qwinndev Dec 5, 2025
740a40b
Update side order stop loss values calculations
qwinndev Dec 8, 2025
4bbf245
Init TP/SL columns
qwinndev Dec 8, 2025
78b882f
Update positions list actions
qwinndev Dec 9, 2025
8ff74cb
Add size toggle in PositionList
qwinndev Dec 9, 2025
e5c6ebd
Init new TP/SL modal
qwinndev Dec 10, 2025
c4e5baf
Update TP/SL modal for mobile screens
qwinndev Dec 10, 2025
e4adedc
New AddTPSLModal
qwinndev Dec 11, 2025
f29eb2d
Add OrderEditor on edit TPSLOrdersList click
qwinndev Dec 11, 2025
f1569f7
Update margin slider
qwinndev Dec 11, 2025
c52f449
Update close size change on open in AddTPSLModal
qwinndev Dec 11, 2025
13be4a1
Merge branch 'master' of qwinndev:gmx-io/gmx-interface into tradebox-…
qwinndev Dec 12, 2025
c413551
Add TP/SL creation on limit and stop market order edit
qwinndev Dec 15, 2025
ad51aff
Fix TradeboxMarginFields percentage change
qwinndev Dec 15, 2025
2510521
Keep TP/SL tradebox values on pool and collateral change
qwinndev Dec 15, 2025
07b621d
Better persistance for pool selection and bug fixes
qwinndev Dec 15, 2025
0a9ed58
Fix tradebox margin field size mode changing
qwinndev Dec 15, 2025
1fd928b
Update close button labels
qwinndev Dec 15, 2025
89ebf6a
Update tradebox tabs
qwinndev Dec 16, 2025
0341435
Fixed closing suggestions on click outside
qwinndev Dec 16, 2025
8ca9082
Merge branch 'release' of qwinndev:gmx-io/gmx-interface into tradebox…
qwinndev Dec 16, 2025
ccbc92f
Code improvements
qwinndev Dec 17, 2025
7830ced
Improvements to TPSLInputRow
qwinndev Dec 17, 2025
11d5794
Reduce duplications in TPSLOrdersList
qwinndev Dec 17, 2025
d049e53
Code improvements
qwinndev Dec 17, 2025
3a99e40
Update locales
qwinndev Dec 18, 2025
cf549b6
Code improvements
qwinndev Dec 19, 2025
a3a788e
Revert best pool options from pool selector
qwinndev Dec 19, 2025
badf161
useOrderEditorTPSL code improvements
qwinndev Dec 22, 2025
27c2c10
Move tpsl utils to separate file and improve naming
qwinndev Dec 22, 2025
7e3b35e
Update locales
qwinndev Dec 22, 2025
c424cd1
Merge branch 'release' of qwinndev:gmx-io/gmx-interface into tradebox…
qwinndev Dec 22, 2025
624f746
Get rid of UI staff in TPSL utils
qwinndev Dec 22, 2025
90f3002
Update position list table sizes
qwinndev Dec 23, 2025
13c13ce
Update header fields grid
qwinndev Dec 23, 2025
91f627a
Separate tradebox perp tabs when swap enabled
qwinndev Dec 23, 2025
3f2bc52
Update tpsl modal height
qwinndev Dec 23, 2025
aff338c
Update popover placements for tradebox elements
qwinndev Dec 25, 2025
516ad27
Improve TPSL modals flow
qwinndev Dec 30, 2025
61d806a
Merge branch 'release' of qwinndev:gmx-io/gmx-interface into tradebox…
qwinndev Dec 30, 2025
5c97e60
Reduce duplications between tpsl modal and tradebox tpsl
qwinndev Dec 30, 2025
360cf6c
Added getPositionForTpSl to useOrderEditorTPSL
qwinndev Jan 5, 2026
1ed3497
Fix network fee calculation on OrderEditor and AddTPSLModal
qwinndev Jan 5, 2026
d06e094
Improve ref setting in SelectorBase and DisplayModeSelector components
qwinndev Jan 5, 2026
4ad05fd
Disable actions when position is opens
qwinndev Jan 5, 2026
0f87758
Fix SelectorBase flex grow
qwinndev Jan 5, 2026
4f7326f
Small tradebox UI improvements
qwinndev Jan 5, 2026
7c29070
Improve block fields clicks
qwinndev Jan 6, 2026
b61ec39
Update tpsl inputs
qwinndev Jan 6, 2026
fd163c7
Remove TPSL from position seller and tradebox
qwinndev Jan 6, 2026
8a7c4d3
Add Close column title to position list
qwinndev Jan 6, 2026
d347e9e
Fix liquidation price color in TPSL modal
qwinndev Jan 6, 2026
db388d8
Add estimated pnl to all tpsl input rows
qwinndev Jan 7, 2026
c4459f1
Remove tabs from position seller
qwinndev Jan 7, 2026
9001579
Updated changing size field type in tradebox
qwinndev Jan 7, 2026
95481bf
Fix disabling TPSL orders in tradebox
qwinndev Jan 7, 2026
1e2445a
Update tooltip icons in position seller rows
qwinndev Jan 8, 2026
6f86fc4
Fix balanceType on max button click
qwinndev Jan 8, 2026
5efb785
Fix size input glitches
qwinndev Jan 8, 2026
c3f82f9
Fixed TPSL modal close all button to close only displayed orders
qwinndev Jan 8, 2026
b7b8545
Update price field control in tradebox margin fields
qwinndev Jan 9, 2026
966e9fe
Fix inconsistency between multiple AddTPSLModals
qwinndev Jan 9, 2026
60cfd0c
Remove display mode selection from price field
qwinndev Jan 9, 2026
4c45817
Fix suggestion input options on mobile
qwinndev Jan 9, 2026
8b742d5
Update loss field logic on TPSLInputRow
qwinndev Jan 9, 2026
b2b48d6
Update fields rows on desktop header
qwinndev Jan 9, 2026
903f99c
Merge branch 'release' of qwinndev:gmx-io/gmx-interface into tradebox…
qwinndev Jan 9, 2026
6f63ed5
Fix position list table
qwinndev Jan 12, 2026
4a054f2
Update tradebox margin fields logic
qwinndev Jan 13, 2026
ed646cc
Refactor TradeboxMarginFields logic
qwinndev Jan 13, 2026
85c064d
Fix transparent line on curtain drag
qwinndev Jan 13, 2026
73c6e7e
Show TPSL position changes in AddTPSLModal
qwinndev Jan 19, 2026
2661a3e
Merge branch 'release' of qwinndev:gmx-io/gmx-interface into tradebox…
qwinndev Jan 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk/src/utils/numbers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export const trimZeroDecimals = (amount: string) => {
export function bigintToNumber(value: bigint, decimals: number) {
const negative = value < 0;
if (negative) value *= -1n;
const precision = 10n ** BigInt(decimals);
const precision = expandDecimals(1, decimals);
const int = value / precision;
const frac = value % precision;

Expand Down
108 changes: 108 additions & 0 deletions src/components/BlockField/BlockField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import cx from "classnames";
import {
ReactNode,
useCallback,
useRef,
type MouseEvent,
type PointerEvent,
type Ref,
type MutableRefObject,
} from "react";

type Props = {
label: ReactNode;
content: ReactNode;
className?: string;
labelClassName?: string;
contentClassName?: string;
containerRef?: Ref<HTMLDivElement>;
forwardClickToSelector?: boolean;
};

export function BlockField({
label,
content,
className,
labelClassName,
contentClassName,
containerRef,
forwardClickToSelector,
}: Props) {
const localRef = useRef<HTMLDivElement | null>(null);
const wasOpenOnPointerDown = useRef(false);

const setContainerRef = useCallback(
(node: HTMLDivElement | null) => {
localRef.current = node;
if (!containerRef) {
return;
}
if (typeof containerRef === "function") {
containerRef(node);
} else {
(containerRef as MutableRefObject<HTMLDivElement | null>).current = node;
}
},
[containerRef]
);

const handlePointerDown = useCallback(
(event: PointerEvent<HTMLDivElement>) => {
if (!forwardClickToSelector) {
return;
}
const button = localRef.current?.querySelector<HTMLElement>(".SelectorBase-button");
const target = event.target as Node | null;

if (!button || (target && button.contains(target))) {
wasOpenOnPointerDown.current = false;
return;
}

const rootState = button.closest("[data-headlessui-state]")?.getAttribute("data-headlessui-state") ?? "";
const buttonState = button.getAttribute("data-headlessui-state") ?? "";
wasOpenOnPointerDown.current =
button.getAttribute("aria-expanded") === "true" || rootState.includes("open") || buttonState.includes("open");
},
[forwardClickToSelector]
);

const handleClick = useCallback(
(event: MouseEvent<HTMLDivElement>) => {
if (!forwardClickToSelector) {
return;
}
const button = localRef.current?.querySelector<HTMLElement>(".SelectorBase-button");
const target = event.target as Node | null;

if (!button || button.classList.contains("SelectorBase-button-disabled") || (target && button.contains(target))) {
wasOpenOnPointerDown.current = false;
return;
}

if (wasOpenOnPointerDown.current) {
wasOpenOnPointerDown.current = false;
return;
}

button.click();
wasOpenOnPointerDown.current = false;
},
[forwardClickToSelector]
);

return (
<div
ref={setContainerRef}
className={cx(
"flex cursor-pointer items-center justify-between gap-10 rounded-4 bg-slate-800 px-8 py-[3.5px]",
className
)}
onPointerDown={handlePointerDown}
onClick={handleClick}
>
<div className={cx("select-none text-13 text-typography-secondary", labelClassName)}>{label}</div>
<div className={cx("flex min-w-0 flex-1 justify-end", contentClassName)}>{content}</div>
</div>
);
}
11 changes: 10 additions & 1 deletion src/components/CollateralSelector/CollateralSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,22 @@ type Props = {
options: TokenData[] | undefined;
disabledOptions?: TokenData[];
onSelect: (tokenAddress: string) => void;
// eslint-disable-next-line react/no-unused-prop-types
popoverReferenceRef?: React.RefObject<HTMLElement | null>;
};

export function CollateralSelector(props: Props) {
const isMobile = useMedia(`(max-width: ${SELECTOR_BASE_MOBILE_THRESHOLD}px)`);

return (
<SelectorBase label={props.selectedTokenSymbol} modalLabel={t`Collateral In`} qa="collateral-in-selector">
<SelectorBase
label={props.selectedTokenSymbol}
modalLabel={t`Collateral In`}
qa="collateral-in-selector"
chevronClassName="w-12 text-typography-secondary max-lg:ml-4 group-gmx-hover/selector-field:text-blue-300"
handleClassName="text-12 group-gmx-hover/selector-field:text-blue-300"
popoverReferenceRef={props.popoverReferenceRef}
>
{isMobile ? <CollateralSelectorMobile {...props} /> : <CollateralSelectorDesktop {...props} />}
</SelectorBase>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/ExitPriceRow/ExitPriceRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function ExitPriceRow({
<SyntheticsInfoRow
label={
<TooltipWithPortal
variant="icon"
variant="iconStroke"
handle={t`Exit Price`}
content={t`Expected exit price for the order, including the current capped net price impact.`}
/>
Expand Down
15 changes: 13 additions & 2 deletions src/components/ExpandableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ interface Props {
withToggleSwitch?: boolean;
handleClassName?: string;
chevronClassName?: string;
wrapped?: boolean;
}

export function ExpandableRow({
Expand All @@ -83,6 +84,7 @@ export function ExpandableRow({
withToggleSwitch = false,
handleClassName,
chevronClassName,
wrapped = false,
}: Props) {
const previousHasError = usePrevious(hasError);
const contentRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -135,9 +137,18 @@ export function ExpandableRow({
);

return (
<div className={cx("min-h-16", className)}>
<div
className={cx("min-h-16", className, {
"rounded-8 border-1/2 border-slate-600 bg-slate-950/50 px-12 py-10": wrapped,
})}
>
<AnimatePresence initial={false}>
<div key="handle" className={cx({ "mb-14": open })}>
<div
key="handle"
className={cx({
"mb-14": open,
})}
>
<SyntheticsInfoRow
className={cx("group relative !items-center gmx-hover:text-blue-300", {
"cursor-not-allowed": disabled,
Expand Down
96 changes: 96 additions & 0 deletions src/components/LeverageField/LeverageField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { useCallback, useEffect, useMemo, useState } from "react";

import SuggestionInput from "components/SuggestionInput/SuggestionInput";

const defaultMarks = [0.1, 25, 50];
const DEFAULT_LEVERAGE = 20;

type Props = {
value: number | null;
onChange: (value: number) => void;
marks: number[];
};

function clampLeverage(value: number, min: number, max: number) {
const safeMin = min > 0 ? min : DEFAULT_LEVERAGE;
return Math.min(Math.max(value, safeMin), max);
}

function formatLeverage(value: number) {
return parseFloat(value.toFixed(2)).toString();
}

export function LeverageField({ value, onChange, marks }: Props) {
const finalMarks = useMemo(() => (marks?.length ? marks : defaultMarks), [marks]);
const minMark = finalMarks[0] ?? DEFAULT_LEVERAGE;
const maxMark = finalMarks.at(-1) ?? DEFAULT_LEVERAGE;

const [inputValue, setInputValue] = useState<string>(() => {
if (value !== null) {
return formatLeverage(clampLeverage(value, minMark, maxMark));
}

return formatLeverage(minMark);
});

useEffect(() => {
if (value !== null) {
setInputValue(formatLeverage(clampLeverage(value, minMark, maxMark)));
}
}, [value, minMark, maxMark]);

const parseAndClampValue = useCallback(
(rawValue: string) => {
const numericValue = parseFloat(rawValue);

if (Number.isNaN(numericValue) || numericValue <= 0) {
return undefined;
}

return clampLeverage(numericValue, minMark, maxMark);
},
[maxMark, minMark]
);

const commitValue = useCallback(
(rawValue?: string) => {
const nextValue = rawValue ?? inputValue;
const parsed = parseAndClampValue(nextValue);
const fallback = clampLeverage(value ?? minMark, minMark, maxMark);
const finalValue = parsed ?? fallback;
const formatted = formatLeverage(finalValue);

setInputValue(formatted);
onChange(finalValue);
},
[inputValue, maxMark, minMark, onChange, value, parseAndClampValue]
);

const handleInputChange = useCallback(
(nextValue: string) => {
setInputValue(nextValue);

const parsed = parseAndClampValue(nextValue);
if (parsed !== undefined) {
onChange(parsed);
}
},
[parseAndClampValue, onChange]
);

return (
<div data-qa="leverage-slider">
<SuggestionInput
suggestionsPlacement="bottom-start"
value={inputValue ?? "N/A"}
setValue={handleInputChange}
onBlur={commitValue}
suggestionList={finalMarks}
suggestionWithSuffix
suffix="x"
inputClassName="w-full text-right"
className="leading-none !rounded-4 px-0 py-[1.5px]"
/>
</div>
);
}
Loading