@orderly.network/ui-tpsl 2.0.1-alpha.3 → 2.0.1-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +34 -0
- package/CHANGELOG.md +12 -0
- package/package.json +13 -19
- package/src/editorPopover.tsx +169 -0
- package/src/editorSheet.tsx +185 -0
- package/src/index.ts +5 -0
- package/src/pnlInput/pnlInput.ui.tsx +115 -0
- package/src/pnlInput/pnlInput.widget.tsx +13 -0
- package/src/pnlInput/useBuilder.script.ts +220 -0
- package/src/tailwind.css +3 -0
- package/src/tpsl.ui.tsx +642 -0
- package/src/tpsl.widget.tsx +15 -0
- package/src/useTPSL.script.ts +228 -0
- package/src/version.ts +14 -0
- package/tsconfig.build.json +28 -0
- package/tsconfig.json +36 -0
- package/tsup.config.ts +20 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @orderly.network/ui-tpsl@2.0.0 build /Users/leo/orderly/orderly-web/packages/ui-tpsl
|
|
4
|
+
> tsup && pnpm run build:css
|
|
5
|
+
|
|
6
|
+
[34mCLI[39m Building entry: src/index.ts
|
|
7
|
+
[34mCLI[39m Using tsconfig: tsconfig.build.json
|
|
8
|
+
[34mCLI[39m tsup v7.3.0
|
|
9
|
+
[34mCLI[39m Using tsup config: /Users/leo/orderly/orderly-web/packages/ui-tpsl/tsup.config.ts
|
|
10
|
+
[34mCLI[39m Target: es2020
|
|
11
|
+
[34mCLI[39m Cleaning output folder
|
|
12
|
+
[34mESM[39m Build start
|
|
13
|
+
[34mCJS[39m Build start
|
|
14
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m16.78 KB[39m
|
|
15
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m64.70 KB[39m
|
|
16
|
+
[32mESM[39m ⚡️ Build success in 1155ms
|
|
17
|
+
[32mCJS[39m [1mdist/index.js [22m[32m18.22 KB[39m
|
|
18
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m64.70 KB[39m
|
|
19
|
+
[32mCJS[39m ⚡️ Build success in 1158ms
|
|
20
|
+
[34mDTS[39m Build start
|
|
21
|
+
[32mDTS[39m ⚡️ Build success in 17485ms
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m1.97 KB[39m
|
|
23
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.97 KB[39m
|
|
24
|
+
|
|
25
|
+
> @orderly.network/ui-tpsl@2.0.0 build:css /Users/leo/orderly/orderly-web/packages/ui-tpsl
|
|
26
|
+
> tailwindcss build -i src/tailwind.css -o dist/styles.css --minify
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Rebuilding...
|
|
30
|
+
|
|
31
|
+
[1m[33mwarn[39m[22m - No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.
|
|
32
|
+
[1m[33mwarn[39m[22m - https://tailwindcss.com/docs/content-configuration
|
|
33
|
+
|
|
34
|
+
Done in 352ms.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @orderly.network/ui-tpsl
|
|
2
|
+
|
|
3
|
+
## 1.0.1-alpha.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fix bugs;
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @orderly.network/hooks@2.0.1-alpha.4
|
|
10
|
+
- @orderly.network/types@2.0.1-alpha.4
|
|
11
|
+
- @orderly.network/utils@2.0.1-alpha.4
|
|
12
|
+
- @orderly.network/ui@2.0.1-alpha.4
|
package/package.json
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orderly.network/ui-tpsl",
|
|
3
|
-
"version": "2.0.1-alpha.
|
|
3
|
+
"version": "2.0.1-alpha.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup && pnpm run build:css",
|
|
10
|
+
"build:css": "tailwindcss build -i src/tailwind.css -o dist/styles.css --minify",
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
8
13
|
"keywords": [
|
|
9
14
|
"Orderly",
|
|
10
15
|
"UI",
|
|
11
16
|
"TPSL"
|
|
12
17
|
],
|
|
13
|
-
"files": [
|
|
14
|
-
"dist"
|
|
15
|
-
],
|
|
16
|
-
"publishConfig": {
|
|
17
|
-
"access": "public"
|
|
18
|
-
},
|
|
19
18
|
"dependencies": {
|
|
20
|
-
"@orderly.network/ui": "
|
|
21
|
-
"@orderly.network/hooks": "
|
|
22
|
-
"@orderly.network/
|
|
23
|
-
"@orderly.network/
|
|
19
|
+
"@orderly.network/ui": "workspace:*",
|
|
20
|
+
"@orderly.network/hooks": "workspace:*",
|
|
21
|
+
"@orderly.network/types": "workspace:*",
|
|
22
|
+
"@orderly.network/utils": "workspace:*"
|
|
24
23
|
},
|
|
25
24
|
"devDependencies": {
|
|
26
25
|
"@types/react": "^18.3.2",
|
|
@@ -28,13 +27,8 @@
|
|
|
28
27
|
"react": "^18.3.1",
|
|
29
28
|
"react-dom": "^18.2.0",
|
|
30
29
|
"tailwindcss": "^3.4.4",
|
|
30
|
+
"tsconfig": "workspace:*",
|
|
31
31
|
"tsup": "^7.1.0",
|
|
32
|
-
"typescript": "^5.1.6"
|
|
33
|
-
"tsconfig": "0.3.17"
|
|
34
|
-
},
|
|
35
|
-
"scripts": {
|
|
36
|
-
"build": "tsup && pnpm run build:css",
|
|
37
|
-
"build:css": "tailwindcss build -i src/tailwind.css -o dist/styles.css --minify",
|
|
38
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
32
|
+
"typescript": "^5.1.6"
|
|
39
33
|
}
|
|
40
|
-
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { useLocalStorage } from "@orderly.network/hooks";
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
cn,
|
|
5
|
+
modal,
|
|
6
|
+
PopoverContent,
|
|
7
|
+
PopoverRoot,
|
|
8
|
+
PopoverTrigger,
|
|
9
|
+
} from "@orderly.network/ui";
|
|
10
|
+
import { useState } from "react";
|
|
11
|
+
import { TPSLWidget } from "./tpsl.widget";
|
|
12
|
+
import { PositionTPSLConfirm } from "./tpsl.ui";
|
|
13
|
+
import { AlgoOrderRootType, API } from "@orderly.network/types";
|
|
14
|
+
import { ButtonProps } from "@orderly.network/ui";
|
|
15
|
+
|
|
16
|
+
export const PositionTPSLPopover = (props: {
|
|
17
|
+
position: API.Position;
|
|
18
|
+
order?: API.AlgoOrder;
|
|
19
|
+
label: string;
|
|
20
|
+
baseDP?: number;
|
|
21
|
+
quoteDP?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Button props
|
|
24
|
+
*/
|
|
25
|
+
buttonProps?: ButtonProps;
|
|
26
|
+
isEditing?: boolean;
|
|
27
|
+
}) => {
|
|
28
|
+
const { position, order, baseDP, quoteDP, buttonProps, isEditing } = props;
|
|
29
|
+
const [open, setOpen] = useState(false);
|
|
30
|
+
const [visible, setVisible] = useState(true);
|
|
31
|
+
|
|
32
|
+
const [needConfirm] = useLocalStorage("orderly_order_confirm", true);
|
|
33
|
+
|
|
34
|
+
const isPositionTPSL = isEditing ? order?.algo_type === AlgoOrderRootType.POSITIONAL_TP_SL : undefined;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<PopoverRoot
|
|
38
|
+
onOpenChange={(isOpen) => {
|
|
39
|
+
// console.log("isOpen", isOpen);
|
|
40
|
+
if (visible) {
|
|
41
|
+
setOpen(isOpen);
|
|
42
|
+
}
|
|
43
|
+
}}
|
|
44
|
+
open={open}
|
|
45
|
+
>
|
|
46
|
+
<PopoverTrigger asChild>
|
|
47
|
+
<Button
|
|
48
|
+
variant="outlined"
|
|
49
|
+
size="sm"
|
|
50
|
+
color="secondary"
|
|
51
|
+
{...buttonProps}
|
|
52
|
+
onClick={() => {
|
|
53
|
+
setOpen(true);
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
{props.label}
|
|
57
|
+
</Button>
|
|
58
|
+
</PopoverTrigger>
|
|
59
|
+
<PopoverContent
|
|
60
|
+
className={cn(
|
|
61
|
+
"oui-w-[360px]",
|
|
62
|
+
visible ? "oui-visible" : "oui-invisible"
|
|
63
|
+
)}
|
|
64
|
+
align="end"
|
|
65
|
+
side={"top"}
|
|
66
|
+
>
|
|
67
|
+
<TPSLWidget
|
|
68
|
+
position={position}
|
|
69
|
+
order={order}
|
|
70
|
+
isEditing={isEditing}
|
|
71
|
+
onComplete={() => {
|
|
72
|
+
// console.log("tpsl order completed");
|
|
73
|
+
setOpen(false);
|
|
74
|
+
}}
|
|
75
|
+
onCancel={() => {
|
|
76
|
+
setOpen(false);
|
|
77
|
+
}}
|
|
78
|
+
onConfirm={(order, options) => {
|
|
79
|
+
if (!needConfirm) {
|
|
80
|
+
return Promise.resolve(true);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setVisible(false);
|
|
84
|
+
|
|
85
|
+
const maxQty = Math.abs(Number(position.position_qty));
|
|
86
|
+
|
|
87
|
+
// console.log(
|
|
88
|
+
// "order",
|
|
89
|
+
// order,
|
|
90
|
+
// isEditing ||
|
|
91
|
+
// (!!order &&
|
|
92
|
+
// order.algo_type === AlgoOrderRootType.POSITIONAL_TP_SL &&
|
|
93
|
+
// order.quantity === maxQty)
|
|
94
|
+
// );
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
(`${order.tp_trigger_price ?? ""}`).length === 0 &&
|
|
98
|
+
(`${order.sl_trigger_price ?? ""}`).length === 0
|
|
99
|
+
) {
|
|
100
|
+
return modal
|
|
101
|
+
.confirm({
|
|
102
|
+
title: "Cancel Order",
|
|
103
|
+
content:
|
|
104
|
+
"Are you sure you want to cancel this TP/SL order?",
|
|
105
|
+
onOk: () => {
|
|
106
|
+
return options.cancel();
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
.then(
|
|
110
|
+
() => {
|
|
111
|
+
setOpen(false);
|
|
112
|
+
setVisible(true);
|
|
113
|
+
return true;
|
|
114
|
+
},
|
|
115
|
+
() => {
|
|
116
|
+
setVisible(true);
|
|
117
|
+
return Promise.reject(false);
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const finalIsEditing =
|
|
123
|
+
isEditing ||
|
|
124
|
+
(!!order &&
|
|
125
|
+
order.algo_type === AlgoOrderRootType.POSITIONAL_TP_SL &&
|
|
126
|
+
order.quantity === maxQty);
|
|
127
|
+
|
|
128
|
+
return modal
|
|
129
|
+
.confirm({
|
|
130
|
+
title: finalIsEditing ? "Edit Order" : "Confirm Order",
|
|
131
|
+
// bodyClassName: "lg:oui-py-0",
|
|
132
|
+
onOk: () => {
|
|
133
|
+
return options.submit();
|
|
134
|
+
},
|
|
135
|
+
classNames: {
|
|
136
|
+
body: "!oui-pb-0"
|
|
137
|
+
},
|
|
138
|
+
content: (
|
|
139
|
+
<PositionTPSLConfirm
|
|
140
|
+
isPositionTPSL={isPositionTPSL}
|
|
141
|
+
isEditing={finalIsEditing}
|
|
142
|
+
symbol={order.symbol!}
|
|
143
|
+
qty={Number(order.quantity)}
|
|
144
|
+
maxQty={maxQty}
|
|
145
|
+
tpPrice={Number(order.tp_trigger_price)}
|
|
146
|
+
slPrice={Number(order.sl_trigger_price)}
|
|
147
|
+
side={order.side!}
|
|
148
|
+
quoteDP={quoteDP ?? 2}
|
|
149
|
+
baseDP={baseDP ?? 2}
|
|
150
|
+
/>
|
|
151
|
+
),
|
|
152
|
+
})
|
|
153
|
+
.then(
|
|
154
|
+
() => {
|
|
155
|
+
setOpen(false);
|
|
156
|
+
setVisible(true);
|
|
157
|
+
return true;
|
|
158
|
+
},
|
|
159
|
+
() => {
|
|
160
|
+
setVisible(true);
|
|
161
|
+
return Promise.reject(false);
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
}}
|
|
165
|
+
/>
|
|
166
|
+
</PopoverContent>
|
|
167
|
+
</PopoverRoot>
|
|
168
|
+
);
|
|
169
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { AlgoOrderRootType, API } from "@orderly.network/types";
|
|
2
|
+
import {
|
|
3
|
+
cn,
|
|
4
|
+
Flex,
|
|
5
|
+
modal,
|
|
6
|
+
useModal,
|
|
7
|
+
Text,
|
|
8
|
+
Box,
|
|
9
|
+
Badge,
|
|
10
|
+
Divider,
|
|
11
|
+
toast,
|
|
12
|
+
} from "@orderly.network/ui";
|
|
13
|
+
import { TPSLWidget, TPSLWidgetProps } from "./tpsl.widget";
|
|
14
|
+
import { PositionTPSLConfirm } from "./tpsl.ui";
|
|
15
|
+
import { useMemo, useState } from "react";
|
|
16
|
+
import { useLocalStorage, useMarkPrice } from "@orderly.network/hooks";
|
|
17
|
+
|
|
18
|
+
type TPSLSheetProps = {
|
|
19
|
+
position: API.Position;
|
|
20
|
+
order?: API.AlgoOrder;
|
|
21
|
+
// label: string;
|
|
22
|
+
// baseDP?: number;
|
|
23
|
+
// quoteDP?: number;
|
|
24
|
+
symbolInfo: API.SymbolExt;
|
|
25
|
+
isEditing?: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const PositionTPSLSheet = (props: TPSLWidgetProps & TPSLSheetProps) => {
|
|
29
|
+
const { position, order, symbolInfo, isEditing } = props;
|
|
30
|
+
const { resolve, hide, updateArgs } = useModal();
|
|
31
|
+
|
|
32
|
+
const [needConfirm] = useLocalStorage("orderly_order_confirm", true);
|
|
33
|
+
|
|
34
|
+
const isPositionTPSL = isEditing ? order?.algo_type === AlgoOrderRootType.POSITIONAL_TP_SL : undefined;
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
const updateSheetTitle = (title: string) => {
|
|
38
|
+
if (isEditing) return;
|
|
39
|
+
updateArgs({ title });
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const onCompleted = () => {
|
|
43
|
+
resolve();
|
|
44
|
+
hide();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const { quote_dp, base_dp } = symbolInfo;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<PositionInfo position={position} symbolInfo={symbolInfo} />
|
|
52
|
+
|
|
53
|
+
<TPSLWidget
|
|
54
|
+
{...props}
|
|
55
|
+
onTPSLTypeChange={(type) => {
|
|
56
|
+
updateSheetTitle(
|
|
57
|
+
type === AlgoOrderRootType.TP_SL ? "TP/SL" : "Position TP/SL"
|
|
58
|
+
);
|
|
59
|
+
}}
|
|
60
|
+
onComplete={onCompleted}
|
|
61
|
+
onConfirm={(order, options) => {
|
|
62
|
+
if (!needConfirm) {
|
|
63
|
+
return Promise.resolve(true);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return modal
|
|
67
|
+
.confirm({
|
|
68
|
+
title: isEditing ? "Edit Order" : "Confirm Order",
|
|
69
|
+
bodyClassName: "oui-pb-0 lg:oui-pb-0",
|
|
70
|
+
onOk: () => {
|
|
71
|
+
return options.submit();
|
|
72
|
+
},
|
|
73
|
+
content: (
|
|
74
|
+
<PositionTPSLConfirm
|
|
75
|
+
isPositionTPSL={isPositionTPSL}
|
|
76
|
+
isEditing={isEditing}
|
|
77
|
+
symbol={order.symbol!}
|
|
78
|
+
qty={Number(order.quantity)}
|
|
79
|
+
maxQty={Math.abs(Number(position.position_qty))}
|
|
80
|
+
tpPrice={Number(order.tp_trigger_price)}
|
|
81
|
+
slPrice={Number(order.sl_trigger_price)}
|
|
82
|
+
side={order.side!}
|
|
83
|
+
quoteDP={quote_dp ?? 2}
|
|
84
|
+
baseDP={base_dp ?? 2}
|
|
85
|
+
/>
|
|
86
|
+
),
|
|
87
|
+
})
|
|
88
|
+
.then(
|
|
89
|
+
() => {
|
|
90
|
+
// setOpen(false);
|
|
91
|
+
// setVisible(true);
|
|
92
|
+
return true;
|
|
93
|
+
},
|
|
94
|
+
(reject) => {
|
|
95
|
+
if (reject?.message) {
|
|
96
|
+
toast.error(reject.message);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// setVisible(true);
|
|
100
|
+
return Promise.reject(false);
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}}
|
|
104
|
+
onCancel={() => {
|
|
105
|
+
hide();
|
|
106
|
+
}}
|
|
107
|
+
/>
|
|
108
|
+
</>
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export const TPSLSheetTitle = () => {
|
|
113
|
+
const modal = useModal();
|
|
114
|
+
|
|
115
|
+
const title = useMemo<string>(() => {
|
|
116
|
+
return (modal.args?.title || "TP/SL") as string;
|
|
117
|
+
}, [modal.args?.title]);
|
|
118
|
+
|
|
119
|
+
return <span>{title}</span>;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const PositionInfo = (props: {
|
|
123
|
+
position: API.Position;
|
|
124
|
+
symbolInfo: API.SymbolExt;
|
|
125
|
+
}) => {
|
|
126
|
+
const { position, symbolInfo } = props;
|
|
127
|
+
const { data: markPrice } = useMarkPrice(position.symbol);
|
|
128
|
+
const modal = useModal();
|
|
129
|
+
const isPositionTPSL = useMemo(() => {
|
|
130
|
+
return modal.args?.title === "Position TP/SL";
|
|
131
|
+
}, [modal.args?.title]);
|
|
132
|
+
return (
|
|
133
|
+
<>
|
|
134
|
+
<Flex justify={"between"} pb={3} itemAlign={"center"}>
|
|
135
|
+
<Text.formatted rule="symbol" className="oui-text-xs" showIcon>
|
|
136
|
+
{position.symbol}
|
|
137
|
+
</Text.formatted>
|
|
138
|
+
<Flex gapX={1}>
|
|
139
|
+
{isPositionTPSL && <Badge size="xs" color="primaryLight">Position</Badge>}
|
|
140
|
+
<Badge size="xs" color="neutral">
|
|
141
|
+
TP/SL
|
|
142
|
+
</Badge>
|
|
143
|
+
{position.position_qty < 0 ? (
|
|
144
|
+
<Badge size="xs" color="buy">
|
|
145
|
+
Buy
|
|
146
|
+
</Badge>
|
|
147
|
+
) : (
|
|
148
|
+
<Badge size="xs" color="sell">
|
|
149
|
+
Sell
|
|
150
|
+
</Badge>
|
|
151
|
+
)}
|
|
152
|
+
</Flex>
|
|
153
|
+
</Flex>
|
|
154
|
+
<Divider intensity={8} />
|
|
155
|
+
<Box py={3} className="oui-space-y-1">
|
|
156
|
+
<Flex justify={"between"}>
|
|
157
|
+
<Text size="sm" intensity={54}>
|
|
158
|
+
Avg. open
|
|
159
|
+
</Text>
|
|
160
|
+
<Text.numeral
|
|
161
|
+
className="oui-text-xs"
|
|
162
|
+
unit={symbolInfo.quote}
|
|
163
|
+
dp={symbolInfo.quote_dp}
|
|
164
|
+
unitClassName="oui-ml-1 oui-text-base-contrast-36"
|
|
165
|
+
>
|
|
166
|
+
{position.average_open_price}
|
|
167
|
+
</Text.numeral>
|
|
168
|
+
</Flex>
|
|
169
|
+
<Flex justify={"between"}>
|
|
170
|
+
<Text size="sm" intensity={54}>
|
|
171
|
+
Mark price
|
|
172
|
+
</Text>
|
|
173
|
+
<Text.numeral
|
|
174
|
+
className="oui-text-xs"
|
|
175
|
+
unit={symbolInfo.quote}
|
|
176
|
+
dp={symbolInfo.quote_dp}
|
|
177
|
+
unitClassName="oui-ml-1 oui-text-base-contrast-36"
|
|
178
|
+
>
|
|
179
|
+
{markPrice}
|
|
180
|
+
</Text.numeral>
|
|
181
|
+
</Flex>
|
|
182
|
+
</Box>
|
|
183
|
+
</>
|
|
184
|
+
);
|
|
185
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CaretDownIcon,
|
|
3
|
+
cn,
|
|
4
|
+
Input,
|
|
5
|
+
MenuItem,
|
|
6
|
+
SimpleDropdownMenu,
|
|
7
|
+
} from "@orderly.network/ui";
|
|
8
|
+
import { PNLInputState, PnLMode } from "./useBuilder.script";
|
|
9
|
+
import { inputFormatter, Text } from "@orderly.network/ui";
|
|
10
|
+
import { useEffect, useMemo, useState } from "react";
|
|
11
|
+
|
|
12
|
+
export type PNLInputProps = PNLInputState & { testId?: string; quote: string };
|
|
13
|
+
|
|
14
|
+
export const PNLInput = (props: PNLInputProps) => {
|
|
15
|
+
const {
|
|
16
|
+
mode,
|
|
17
|
+
modes,
|
|
18
|
+
onModeChange,
|
|
19
|
+
onValueChange,
|
|
20
|
+
quote,
|
|
21
|
+
quote_dp,
|
|
22
|
+
value,
|
|
23
|
+
pnl,
|
|
24
|
+
} = props;
|
|
25
|
+
|
|
26
|
+
const [prefix, setPrefix] = useState<string>(mode);
|
|
27
|
+
const [placeholder, setPlaceholder] = useState<string>(
|
|
28
|
+
mode === PnLMode.PERCENTAGE ? "%" : quote
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const color = useMemo(() => {
|
|
32
|
+
const num = Number(pnl);
|
|
33
|
+
|
|
34
|
+
if (isNaN(num) || num === 0) return "";
|
|
35
|
+
|
|
36
|
+
if (num > 0) return "oui-text-trade-profit";
|
|
37
|
+
if (num < 0) return "oui-text-trade-loss";
|
|
38
|
+
}, [pnl]);
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
setPrefix(mode);
|
|
42
|
+
setPlaceholder(mode === PnLMode.PERCENTAGE ? "%" : quote);
|
|
43
|
+
}, [mode]);
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Input
|
|
49
|
+
prefix={mode}
|
|
50
|
+
size={{
|
|
51
|
+
initial: "lg",
|
|
52
|
+
lg: "md",
|
|
53
|
+
}}
|
|
54
|
+
placeholder={placeholder}
|
|
55
|
+
align={"right"}
|
|
56
|
+
value={value}
|
|
57
|
+
data-testid={props.testId}
|
|
58
|
+
autoComplete={"off"}
|
|
59
|
+
onValueChange={onValueChange}
|
|
60
|
+
formatters={[
|
|
61
|
+
// inputFormatter.numberFormatter,
|
|
62
|
+
props.formatter({ dp: quote_dp, mode }),
|
|
63
|
+
inputFormatter.currencyFormatter,
|
|
64
|
+
]}
|
|
65
|
+
// className={color}
|
|
66
|
+
classNames={{
|
|
67
|
+
input: color,
|
|
68
|
+
prefix: "oui-text-base-contrast-54",
|
|
69
|
+
root: "oui-outline-line-12 focus-within:oui-outline-primary-light",
|
|
70
|
+
}}
|
|
71
|
+
onFocus={() => {
|
|
72
|
+
setPlaceholder("");
|
|
73
|
+
}}
|
|
74
|
+
onBlur={() => {
|
|
75
|
+
setPlaceholder(mode === PnLMode.PERCENTAGE ? "%" : quote);
|
|
76
|
+
}}
|
|
77
|
+
// value={props.value}
|
|
78
|
+
suffix={
|
|
79
|
+
<>
|
|
80
|
+
{mode === PnLMode.PERCENTAGE && !!value && (
|
|
81
|
+
<Text size={"2xs"} color="inherit" className={cn("oui-ml-[2px]", color)}>
|
|
82
|
+
%
|
|
83
|
+
</Text>
|
|
84
|
+
)}
|
|
85
|
+
<PNLMenus
|
|
86
|
+
mode={mode}
|
|
87
|
+
modes={modes}
|
|
88
|
+
onModeChange={(item) => onModeChange(item.value as PnLMode)}
|
|
89
|
+
/>
|
|
90
|
+
</>
|
|
91
|
+
}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const PNLMenus = (props: {
|
|
97
|
+
mode?: string;
|
|
98
|
+
modes: MenuItem[];
|
|
99
|
+
onModeChange: (value: MenuItem) => void;
|
|
100
|
+
}) => {
|
|
101
|
+
return (
|
|
102
|
+
<SimpleDropdownMenu
|
|
103
|
+
currentValue={props.mode}
|
|
104
|
+
menu={props.modes}
|
|
105
|
+
align={"end"}
|
|
106
|
+
size={"xs"}
|
|
107
|
+
className={"oui-min-w-[80px]"}
|
|
108
|
+
onSelect={(item) => props.onModeChange(item as MenuItem)}
|
|
109
|
+
>
|
|
110
|
+
<button className={"oui-p-2"}>
|
|
111
|
+
<CaretDownIcon size={12} color={"white"} />
|
|
112
|
+
</button>
|
|
113
|
+
</SimpleDropdownMenu>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PNLInput } from "./pnlInput.ui";
|
|
2
|
+
import { BuilderProps, usePNLInputBuilder } from "./useBuilder.script";
|
|
3
|
+
|
|
4
|
+
export const PnlInputWidget = (
|
|
5
|
+
props: BuilderProps & {
|
|
6
|
+
testId?: string;
|
|
7
|
+
quote: string;
|
|
8
|
+
}
|
|
9
|
+
) => {
|
|
10
|
+
const { testId, quote, ...rest } = props;
|
|
11
|
+
const state = usePNLInputBuilder(rest);
|
|
12
|
+
return <PNLInput {...state} testId={testId} quote={quote} />;
|
|
13
|
+
};
|