@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.
@@ -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
+ CLI Building entry: src/index.ts
7
+ CLI Using tsconfig: tsconfig.build.json
8
+ CLI tsup v7.3.0
9
+ CLI Using tsup config: /Users/leo/orderly/orderly-web/packages/ui-tpsl/tsup.config.ts
10
+ CLI Target: es2020
11
+ CLI Cleaning output folder
12
+ ESM Build start
13
+ CJS Build start
14
+ ESM dist/index.mjs 16.78 KB
15
+ ESM dist/index.mjs.map 64.70 KB
16
+ ESM ⚡️ Build success in 1155ms
17
+ CJS dist/index.js 18.22 KB
18
+ CJS dist/index.js.map 64.70 KB
19
+ CJS ⚡️ Build success in 1158ms
20
+ DTS Build start
21
+ DTS ⚡️ Build success in 17485ms
22
+ DTS dist/index.d.mts 1.97 KB
23
+ DTS dist/index.d.ts 1.97 KB
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
+ warn - No utility classes were detected in your source files. If this is unexpected, double-check the `content` option in your Tailwind CSS configuration.
32
+ warn - 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",
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": "2.0.1-alpha.3",
21
- "@orderly.network/hooks": "2.0.1-alpha.3",
22
- "@orderly.network/utils": "2.0.1-alpha.3",
23
- "@orderly.network/types": "2.0.1-alpha.3"
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,5 @@
1
+ export { TPSLWidget } from "./tpsl.widget";
2
+ export { PositionTPSLPopover } from "./editorPopover";
3
+ export { PositionTPSLSheet } from "./editorSheet";
4
+ export { PositionTPSLConfirm } from "./tpsl.ui";
5
+ export type { TPSLWidgetProps } from "./tpsl.widget";
@@ -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
+ };