@lifi/widget 4.1.0 → 4.2.0
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/dist/esm/AppDefault.js +14 -0
- package/dist/esm/AppDefault.js.map +1 -1
- package/dist/esm/components/AmountInput/AmountInput.js +2 -1
- package/dist/esm/components/AmountInput/AmountInput.js.map +1 -1
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +4 -2
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js +1 -1
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.style.js.map +1 -1
- package/dist/esm/components/AmountInput/PriceFormHelperText.js +4 -2
- package/dist/esm/components/AmountInput/PriceFormHelperText.js.map +1 -1
- package/dist/esm/components/AmountInputCard/AmountInputCard.style.js +98 -0
- package/dist/esm/components/AmountInputCard/AmountInputCard.style.js.map +1 -0
- package/dist/esm/components/AmountInputCard/AmountInputCardPair.js +25 -0
- package/dist/esm/components/AmountInputCard/AmountInputCardPair.js.map +1 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.js +35 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.js.map +1 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js +11 -0
- package/dist/esm/components/AmountInputCard/BalanceDisplay.style.js.map +1 -0
- package/dist/esm/components/AmountInputCard/FiatValueToggle.js +41 -0
- package/dist/esm/components/AmountInputCard/FiatValueToggle.js.map +1 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.js +80 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.js.map +1 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.style.js +43 -0
- package/dist/esm/components/AmountInputCard/PercentageChips.style.js.map +1 -0
- package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js +137 -0
- package/dist/esm/components/AmountInputCard/ReceiveAmountCard.js.map +1 -0
- package/dist/esm/components/AmountInputCard/SendAmountCard.js +102 -0
- package/dist/esm/components/AmountInputCard/SendAmountCard.js.map +1 -0
- package/dist/esm/components/Avatar/Avatar.js +12 -3
- package/dist/esm/components/Avatar/Avatar.js.map +1 -1
- package/dist/esm/components/Chains/VirtualizedChainList.js +2 -1
- package/dist/esm/components/Chains/VirtualizedChainList.js.map +1 -1
- package/dist/esm/components/Header/HeaderNavigationTabs.js +14 -1
- package/dist/esm/components/Header/HeaderNavigationTabs.js.map +1 -1
- package/dist/esm/components/IconCircle/IconCircle.style.js +1 -1
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js +99 -0
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.js.map +1 -0
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js +52 -0
- package/dist/esm/components/LimitOrderSettings/LimitOrderSettings.style.js.map +1 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.js +115 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.js.map +1 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js +89 -0
- package/dist/esm/components/LimitPriceCard/LimitPriceCard.style.js.map +1 -0
- package/dist/esm/components/LimitPriceCard/LimitPricePresets.js +91 -0
- package/dist/esm/components/LimitPriceCard/LimitPricePresets.js.map +1 -0
- package/dist/esm/components/RouteCard/RouteProviderCard.js +118 -0
- package/dist/esm/components/RouteCard/RouteProviderCard.js.map +1 -0
- package/dist/esm/components/Routes/LimitOrderRoutes.js +34 -0
- package/dist/esm/components/Routes/LimitOrderRoutes.js.map +1 -0
- package/dist/esm/components/Routes/Routes.js +2 -2
- package/dist/esm/components/Routes/Routes.js.map +1 -1
- package/dist/esm/components/SettingsPanel/SettingsPanel.js +51 -0
- package/dist/esm/components/SettingsPanel/SettingsPanel.js.map +1 -0
- package/dist/esm/components/SettingsPanel/SettingsPanel.style.js +33 -0
- package/dist/esm/components/SettingsPanel/SettingsPanel.style.js.map +1 -0
- package/dist/esm/components/SettingsPanel/utils.js +14 -0
- package/dist/esm/components/SettingsPanel/utils.js.map +1 -0
- package/dist/esm/components/SwapButton/SwapButton.js +50 -0
- package/dist/esm/components/SwapButton/SwapButton.js.map +1 -0
- package/dist/esm/components/SwapButton/SwapButton.style.js +24 -0
- package/dist/esm/components/SwapButton/SwapButton.style.js.map +1 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.js +52 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.js.map +1 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.style.js +39 -0
- package/dist/esm/components/TokenPillButton/TokenPillButton.style.js.map +1 -0
- package/dist/esm/config/version.d.ts +1 -1
- package/dist/esm/config/version.js +1 -1
- package/dist/esm/config/version.js.map +1 -1
- package/dist/esm/hooks/useChainSearch.js +2 -1
- package/dist/esm/hooks/useChainSearch.js.map +1 -1
- package/dist/esm/hooks/useLimitMarketRate.js +24 -0
- package/dist/esm/hooks/useLimitMarketRate.js.map +1 -0
- package/dist/esm/hooks/useLinkedLimitFields.js +49 -0
- package/dist/esm/hooks/useLinkedLimitFields.js.map +1 -0
- package/dist/esm/hooks/useRoutes.js +23 -6
- package/dist/esm/hooks/useRoutes.js.map +1 -1
- package/dist/esm/hooks/useTokens.js +16 -14
- package/dist/esm/hooks/useTokens.js.map +1 -1
- package/dist/esm/hooks/useWideVariant.js +5 -2
- package/dist/esm/hooks/useWideVariant.js.map +1 -1
- package/dist/esm/i18n/en.json +22 -0
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/pages/MainPage/LimitPageContent.js +23 -0
- package/dist/esm/pages/MainPage/LimitPageContent.js.map +1 -0
- package/dist/esm/pages/MainPage/MainPage.js +19 -22
- package/dist/esm/pages/MainPage/MainPage.js.map +1 -1
- package/dist/esm/pages/MainPage/MainPageActions.js +28 -0
- package/dist/esm/pages/MainPage/MainPageActions.js.map +1 -0
- package/dist/esm/pages/MainPage/ReviewButton.js +9 -2
- package/dist/esm/pages/MainPage/ReviewButton.js.map +1 -1
- package/dist/esm/pages/RoutePriorityPage.js +48 -0
- package/dist/esm/pages/RoutePriorityPage.js.map +1 -0
- package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js +2 -2
- package/dist/esm/pages/SettingsPage/BridgeAndExchangeSettings.js.map +1 -1
- package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js +12 -27
- package/dist/esm/pages/SettingsPage/RoutePrioritySettings.js.map +1 -1
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js +133 -0
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippagePage.js.map +1 -0
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js +14 -72
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.js.map +1 -1
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js +26 -14
- package/dist/esm/pages/SettingsPage/SlippageSettings/SlippageSettings.style.js.map +1 -1
- package/dist/esm/pages/SettingsPage/ThemeSettings.js +4 -0
- package/dist/esm/pages/SettingsPage/ThemeSettings.js.map +1 -1
- package/dist/esm/pages/TransactionPage/TransactionReview.js +4 -1
- package/dist/esm/pages/TransactionPage/TransactionReview.js.map +1 -1
- package/dist/esm/providers/I18nProvider/I18nProvider.js +1 -1
- package/dist/esm/providers/I18nProvider/enResource.d.ts +1 -1
- package/dist/esm/providers/I18nProvider/enResource.js +1 -1
- package/dist/esm/providers/I18nProvider/types.d.ts +2 -2
- package/dist/esm/providers/SDKClientProvider.js +25 -24
- package/dist/esm/providers/SDKClientProvider.js.map +1 -1
- package/dist/esm/providers/WidgetProvider/WidgetProvider.js +22 -21
- package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
- package/dist/esm/stores/form/URLSearchParamsBuilder.js +14 -3
- package/dist/esm/stores/form/URLSearchParamsBuilder.js.map +1 -1
- package/dist/esm/stores/form/createFormStore.js +6 -2
- package/dist/esm/stores/form/createFormStore.js.map +1 -1
- package/dist/esm/stores/form/getDefaultValuesFromQueryString.js +3 -0
- package/dist/esm/stores/form/getDefaultValuesFromQueryString.js.map +1 -1
- package/dist/esm/stores/form/types.d.ts +9 -1
- package/dist/esm/stores/form/types.d.ts.map +1 -1
- package/dist/esm/stores/form/types.js.map +1 -1
- package/dist/esm/stores/form/useClearAmountFields.js +1 -0
- package/dist/esm/stores/form/useClearAmountFields.js.map +1 -1
- package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js +4 -2
- package/dist/esm/stores/navigationTabs/useNavigationTabsStore.js.map +1 -1
- package/dist/esm/stores/navigationTabs/utils.js +38 -8
- package/dist/esm/stores/navigationTabs/utils.js.map +1 -1
- package/dist/esm/types/events.d.ts +10 -1
- package/dist/esm/types/events.d.ts.map +1 -1
- package/dist/esm/types/events.js +2 -0
- package/dist/esm/types/events.js.map +1 -1
- package/dist/esm/types/widget.d.ts +9 -0
- package/dist/esm/types/widget.d.ts.map +1 -1
- package/dist/esm/types/widget.js.map +1 -1
- package/dist/esm/utils/format.d.ts +1 -1
- package/dist/esm/utils/format.d.ts.map +1 -1
- package/dist/esm/utils/format.js +2 -2
- package/dist/esm/utils/format.js.map +1 -1
- package/dist/esm/utils/limitOrder.js +61 -0
- package/dist/esm/utils/limitOrder.js.map +1 -0
- package/dist/esm/utils/navigationRoutes.d.ts +2 -0
- package/dist/esm/utils/navigationRoutes.d.ts.map +1 -1
- package/dist/esm/utils/navigationRoutes.js +6 -0
- package/dist/esm/utils/navigationRoutes.js.map +1 -1
- package/package.json +11 -11
- package/src/AppDefault.tsx +16 -0
- package/src/components/AmountInput/AmountInputEndAdornment.style.ts +1 -1
- package/src/components/AmountInput/AmountInputEndAdornment.tsx +5 -2
- package/src/components/AmountInputCard/AmountInputCard.style.tsx +157 -0
- package/src/components/AmountInputCard/AmountInputCardPair.tsx +25 -0
- package/src/components/AmountInputCard/BalanceDisplay.style.tsx +9 -0
- package/src/components/AmountInputCard/BalanceDisplay.tsx +46 -0
- package/src/components/AmountInputCard/FiatValueToggle.tsx +55 -0
- package/src/components/AmountInputCard/PercentageChips.style.tsx +60 -0
- package/src/components/AmountInputCard/PercentageChips.tsx +100 -0
- package/src/components/AmountInputCard/ReceiveAmountCard.tsx +219 -0
- package/src/components/AmountInputCard/SendAmountCard.tsx +179 -0
- package/src/components/Avatar/Avatar.tsx +8 -3
- package/src/components/Header/HeaderNavigationTabs.tsx +19 -2
- package/src/components/LimitOrderSettings/LimitOrderSettings.style.tsx +69 -0
- package/src/components/LimitOrderSettings/LimitOrderSettings.tsx +124 -0
- package/src/components/LimitPriceCard/LimitPriceCard.style.tsx +103 -0
- package/src/components/LimitPriceCard/LimitPriceCard.tsx +153 -0
- package/src/components/LimitPriceCard/LimitPricePresets.tsx +139 -0
- package/src/components/RouteCard/RouteProviderCard.tsx +113 -0
- package/src/components/Routes/LimitOrderRoutes.tsx +64 -0
- package/src/components/Routes/Routes.tsx +3 -2
- package/src/components/SettingsPanel/SettingsPanel.style.tsx +41 -0
- package/src/components/SettingsPanel/SettingsPanel.tsx +73 -0
- package/src/components/SettingsPanel/utils.ts +27 -0
- package/src/components/SwapButton/SwapButton.style.tsx +28 -0
- package/src/components/SwapButton/SwapButton.tsx +71 -0
- package/src/components/TokenPillButton/TokenPillButton.style.tsx +50 -0
- package/src/components/TokenPillButton/TokenPillButton.tsx +81 -0
- package/src/config/version.ts +1 -1
- package/src/hooks/useLimitMarketRate.ts +39 -0
- package/src/hooks/useLinkedLimitFields.ts +74 -0
- package/src/hooks/useRoutes.ts +45 -4
- package/src/hooks/useWideVariant.ts +8 -2
- package/src/pages/MainPage/LimitPageContent.tsx +18 -0
- package/src/pages/MainPage/MainPage.tsx +43 -30
- package/src/pages/MainPage/MainPageActions.tsx +35 -0
- package/src/pages/MainPage/ReviewButton.tsx +13 -1
- package/src/pages/RoutePriorityPage.tsx +55 -0
- package/src/pages/SettingsPage/RoutePrioritySettings.tsx +14 -38
- package/src/pages/SettingsPage/SlippageSettings/SlippagePage.tsx +176 -0
- package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.style.tsx +32 -25
- package/src/pages/SettingsPage/SlippageSettings/SlippageSettings.tsx +14 -128
- package/src/pages/SettingsPage/ThemeSettings.tsx +4 -0
- package/src/stores/form/URLSearchParamsBuilder.tsx +25 -3
- package/src/stores/form/createFormStore.ts +7 -3
- package/src/stores/form/getDefaultValuesFromQueryString.ts +11 -0
- package/src/stores/form/types.ts +14 -1
- package/src/stores/form/useClearAmountFields.ts +1 -0
- package/src/stores/navigationTabs/types.ts +6 -0
- package/src/stores/navigationTabs/useNavigationTabsStore.tsx +8 -3
- package/src/stores/navigationTabs/utils.ts +67 -5
- package/src/types/events.ts +12 -0
- package/src/types/widget.ts +9 -0
- package/src/utils/format.ts +6 -2
- package/src/utils/limitOrder.ts +88 -0
- package/src/utils/navigationRoutes.ts +6 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useFieldActions } from "../../stores/form/useFieldActions.js";
|
|
2
|
+
import { formatDuration } from "../../utils/format.js";
|
|
3
|
+
import { useFieldValues } from "../../stores/form/useFieldValues.js";
|
|
4
|
+
import { Card as Card$1 } from "../Card/Card.js";
|
|
5
|
+
import { Switch as Switch$1 } from "../Switch.js";
|
|
6
|
+
import { Container as Container$1, ExpiryValue, SettingItemButton, SettingItemTitle, SettingItemValue, SettingsRow, ToggleLabel } from "./LimitOrderSettings.style.js";
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
import { Menu, MenuItem } from "@mui/material";
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { useTranslation } from "react-i18next";
|
|
11
|
+
import Check from "@mui/icons-material/Check";
|
|
12
|
+
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
|
13
|
+
//#region src/components/LimitOrderSettings/LimitOrderSettings.tsx
|
|
14
|
+
/** Selectable order-validity durations, in seconds. */
|
|
15
|
+
const DURATIONS = [
|
|
16
|
+
3600,
|
|
17
|
+
86400,
|
|
18
|
+
604800,
|
|
19
|
+
2592e3,
|
|
20
|
+
31536e3
|
|
21
|
+
];
|
|
22
|
+
const LimitOrderSettings = (props) => {
|
|
23
|
+
const { t, i18n } = useTranslation();
|
|
24
|
+
const [validUntil, partiallyFillable] = useFieldValues("validUntil", "partiallyFillable");
|
|
25
|
+
const { setFieldValue } = useFieldActions();
|
|
26
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
27
|
+
const open = Boolean(anchorEl);
|
|
28
|
+
const handleOpen = (event) => {
|
|
29
|
+
setAnchorEl(event.currentTarget);
|
|
30
|
+
};
|
|
31
|
+
const handleClose = () => {
|
|
32
|
+
setAnchorEl(null);
|
|
33
|
+
};
|
|
34
|
+
const handleSelect = (duration) => {
|
|
35
|
+
setFieldValue("validUntil", duration);
|
|
36
|
+
setAnchorEl(null);
|
|
37
|
+
};
|
|
38
|
+
const handlePartiallyFillableChange = (event) => {
|
|
39
|
+
setFieldValue("partiallyFillable", event.target.checked);
|
|
40
|
+
};
|
|
41
|
+
return /* @__PURE__ */ jsxs(Container$1, {
|
|
42
|
+
...props,
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ jsx(Card$1, { children: /* @__PURE__ */ jsxs(SettingItemButton, {
|
|
45
|
+
onClick: handleOpen,
|
|
46
|
+
disableRipple: true,
|
|
47
|
+
"aria-haspopup": "menu",
|
|
48
|
+
"aria-expanded": open,
|
|
49
|
+
children: [/* @__PURE__ */ jsx(SettingItemTitle, { children: t("limitOrder.expiresIn") }), /* @__PURE__ */ jsxs(ExpiryValue, { children: [/* @__PURE__ */ jsx(SettingItemValue, { children: formatDuration(validUntil, i18n.language, "long") }), /* @__PURE__ */ jsx(KeyboardArrowDownIcon, { sx: {
|
|
50
|
+
fontSize: 20,
|
|
51
|
+
height: "18px",
|
|
52
|
+
marginTop: "2px"
|
|
53
|
+
} })] })]
|
|
54
|
+
}) }),
|
|
55
|
+
/* @__PURE__ */ jsx(Menu, {
|
|
56
|
+
anchorEl,
|
|
57
|
+
open,
|
|
58
|
+
onClose: handleClose,
|
|
59
|
+
anchorOrigin: {
|
|
60
|
+
vertical: "bottom",
|
|
61
|
+
horizontal: "right"
|
|
62
|
+
},
|
|
63
|
+
transformOrigin: {
|
|
64
|
+
vertical: "top",
|
|
65
|
+
horizontal: "right"
|
|
66
|
+
},
|
|
67
|
+
slotProps: { paper: { sx: (theme) => ({
|
|
68
|
+
border: `1px solid ${theme.vars.palette.grey[300]}`,
|
|
69
|
+
boxShadow: "none",
|
|
70
|
+
...theme.applyStyles("dark", { borderColor: theme.vars.palette.grey[800] })
|
|
71
|
+
}) } },
|
|
72
|
+
children: DURATIONS.map((duration) => /* @__PURE__ */ jsxs(MenuItem, {
|
|
73
|
+
onClick: () => handleSelect(duration),
|
|
74
|
+
sx: (theme) => ({
|
|
75
|
+
justifyContent: "space-between",
|
|
76
|
+
gap: 4,
|
|
77
|
+
fontSize: 14,
|
|
78
|
+
fontWeight: 700,
|
|
79
|
+
lineHeight: "18px",
|
|
80
|
+
color: theme.vars.palette.text.primary
|
|
81
|
+
}),
|
|
82
|
+
children: [formatDuration(duration, i18n.language, "long"), duration === validUntil ? /* @__PURE__ */ jsx(Check, {
|
|
83
|
+
color: "primary",
|
|
84
|
+
fontSize: "small"
|
|
85
|
+
}) : null]
|
|
86
|
+
}, duration))
|
|
87
|
+
}),
|
|
88
|
+
/* @__PURE__ */ jsx(Card$1, { children: /* @__PURE__ */ jsxs(SettingsRow, { children: [/* @__PURE__ */ jsx(ToggleLabel, { children: t("limitOrder.partiallyFillable") }), /* @__PURE__ */ jsx(Switch$1, {
|
|
89
|
+
checked: partiallyFillable,
|
|
90
|
+
onChange: handlePartiallyFillableChange,
|
|
91
|
+
"aria-label": t("limitOrder.partiallyFillable")
|
|
92
|
+
})] }) })
|
|
93
|
+
]
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
//#endregion
|
|
97
|
+
export { LimitOrderSettings };
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=LimitOrderSettings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LimitOrderSettings.js","names":["Container","Card","Switch"],"sources":["../../../../src/components/LimitOrderSettings/LimitOrderSettings.tsx"],"sourcesContent":["import Check from '@mui/icons-material/Check'\nimport KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'\nimport type { BoxProps } from '@mui/material'\nimport { Menu, MenuItem } from '@mui/material'\nimport { type ChangeEvent, type JSX, type MouseEvent, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useFieldActions } from '../../stores/form/useFieldActions.js'\nimport { useFieldValues } from '../../stores/form/useFieldValues.js'\nimport { formatDuration } from '../../utils/format.js'\nimport { Card } from '../Card/Card.js'\nimport { Switch } from '../Switch.js'\nimport {\n Container,\n ExpiryValue,\n SettingItemButton,\n SettingItemTitle,\n SettingItemValue,\n SettingsRow,\n ToggleLabel,\n} from './LimitOrderSettings.style.js'\n\n/** Selectable order-validity durations, in seconds. */\nconst DURATIONS = [3600, 86400, 604800, 2592000, 31536000]\n\nexport const LimitOrderSettings: React.FC<BoxProps> = (props): JSX.Element => {\n const { t, i18n } = useTranslation()\n const [validUntil, partiallyFillable] = useFieldValues(\n 'validUntil',\n 'partiallyFillable'\n )\n const { setFieldValue } = useFieldActions()\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)\n const open = Boolean(anchorEl)\n\n const handleOpen = (event: MouseEvent<HTMLElement>): void => {\n setAnchorEl(event.currentTarget)\n }\n\n const handleClose = (): void => {\n setAnchorEl(null)\n }\n\n const handleSelect = (duration: number): void => {\n setFieldValue('validUntil', duration)\n setAnchorEl(null)\n }\n\n const handlePartiallyFillableChange = (\n event: ChangeEvent<HTMLInputElement>\n ): void => {\n setFieldValue('partiallyFillable', event.target.checked)\n }\n\n return (\n <Container {...props}>\n <Card>\n <SettingItemButton\n onClick={handleOpen}\n disableRipple\n aria-haspopup=\"menu\"\n aria-expanded={open}\n >\n <SettingItemTitle>{t('limitOrder.expiresIn')}</SettingItemTitle>\n <ExpiryValue>\n <SettingItemValue>\n {formatDuration(validUntil, i18n.language, 'long')}\n </SettingItemValue>\n <KeyboardArrowDownIcon\n sx={{ fontSize: 20, height: '18px', marginTop: '2px' }}\n />\n </ExpiryValue>\n </SettingItemButton>\n </Card>\n <Menu\n anchorEl={anchorEl}\n open={open}\n onClose={handleClose}\n anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}\n transformOrigin={{ vertical: 'top', horizontal: 'right' }}\n slotProps={{\n paper: {\n sx: (theme) => ({\n border: `1px solid ${theme.vars.palette.grey[300]}`,\n boxShadow: 'none',\n ...theme.applyStyles('dark', {\n borderColor: theme.vars.palette.grey[800],\n }),\n }),\n },\n }}\n >\n {DURATIONS.map((duration) => (\n <MenuItem\n key={duration}\n onClick={() => handleSelect(duration)}\n sx={(theme) => ({\n justifyContent: 'space-between',\n gap: 4,\n fontSize: 14,\n fontWeight: 700,\n lineHeight: '18px',\n color: theme.vars.palette.text.primary,\n })}\n >\n {formatDuration(duration, i18n.language, 'long')}\n {duration === validUntil ? (\n <Check color=\"primary\" fontSize=\"small\" />\n ) : null}\n </MenuItem>\n ))}\n </Menu>\n <Card>\n <SettingsRow>\n <ToggleLabel>{t('limitOrder.partiallyFillable')}</ToggleLabel>\n <Switch\n checked={partiallyFillable}\n onChange={handlePartiallyFillableChange}\n aria-label={t('limitOrder.partiallyFillable')}\n />\n </SettingsRow>\n </Card>\n </Container>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;AAsBA,MAAM,YAAY;CAAC;CAAM;CAAO;CAAQ;CAAS;AAAQ;AAEzD,MAAa,sBAA0C,UAAuB;CAC5E,MAAM,EAAE,GAAG,SAAS,eAAe;CACnC,MAAM,CAAC,YAAY,qBAAqB,eACtC,cACA,mBACF;CACA,MAAM,EAAE,kBAAkB,gBAAgB;CAC1C,MAAM,CAAC,UAAU,eAAe,SAA6B,IAAI;CACjE,MAAM,OAAO,QAAQ,QAAQ;CAE7B,MAAM,cAAc,UAAyC;EAC3D,YAAY,MAAM,aAAa;CACjC;CAEA,MAAM,oBAA0B;EAC9B,YAAY,IAAI;CAClB;CAEA,MAAM,gBAAgB,aAA2B;EAC/C,cAAc,cAAc,QAAQ;EACpC,YAAY,IAAI;CAClB;CAEA,MAAM,iCACJ,UACS;EACT,cAAc,qBAAqB,MAAM,OAAO,OAAO;CACzD;CAEA,OACE,qBAACA,aAAD;EAAW,GAAI;YAAf;GACE,oBAACC,QAAD,EAAA,UACE,qBAAC,mBAAD;IACE,SAAS;IACT,eAAA;IACA,iBAAc;IACd,iBAAe;cAJjB,CAME,oBAAC,kBAAD,EAAA,UAAmB,EAAE,sBAAsB,EAAoB,CAAA,GAC/D,qBAAC,aAAD,EAAA,UAAA,CACE,oBAAC,kBAAD,EAAA,UACG,eAAe,YAAY,KAAK,UAAU,MAAM,EACjC,CAAA,GAClB,oBAAC,uBAAD,EACE,IAAI;KAAE,UAAU;KAAI,QAAQ;KAAQ,WAAW;IAAM,EACtD,CAAA,CACU,EAAA,CAAA,CACI;MACf,CAAA;GACN,oBAAC,MAAD;IACY;IACJ;IACN,SAAS;IACT,cAAc;KAAE,UAAU;KAAU,YAAY;IAAQ;IACxD,iBAAiB;KAAE,UAAU;KAAO,YAAY;IAAQ;IACxD,WAAW,EACT,OAAO,EACL,KAAK,WAAW;KACd,QAAQ,aAAa,MAAM,KAAK,QAAQ,KAAK;KAC7C,WAAW;KACX,GAAG,MAAM,YAAY,QAAQ,EAC3B,aAAa,MAAM,KAAK,QAAQ,KAAK,KACvC,CAAC;IACH,GACF,EACF;cAEC,UAAU,KAAK,aACd,qBAAC,UAAD;KAEE,eAAe,aAAa,QAAQ;KACpC,KAAK,WAAW;MACd,gBAAgB;MAChB,KAAK;MACL,UAAU;MACV,YAAY;MACZ,YAAY;MACZ,OAAO,MAAM,KAAK,QAAQ,KAAK;KACjC;eAVF,CAYG,eAAe,UAAU,KAAK,UAAU,MAAM,GAC9C,aAAa,aACZ,oBAAC,OAAD;MAAO,OAAM;MAAU,UAAS;KAAS,CAAA,IACvC,IACI;OAfH,QAeG,CACX;GACG,CAAA;GACN,oBAACA,QAAD,EAAA,UACE,qBAAC,aAAD,EAAA,UAAA,CACE,oBAAC,aAAD,EAAA,UAAc,EAAE,8BAA8B,EAAe,CAAA,GAC7D,oBAACC,UAAD;IACE,SAAS;IACT,UAAU;IACV,cAAY,EAAE,8BAA8B;GAC7C,CAAA,CACU,EAAA,CAAA,EACT,CAAA;EACG;;AAEf"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Box, ButtonBase, Typography, styled } from "@mui/material";
|
|
2
|
+
//#region src/components/LimitOrderSettings/LimitOrderSettings.style.tsx
|
|
3
|
+
const Container$1 = styled(Box)(({ theme }) => ({
|
|
4
|
+
display: "flex",
|
|
5
|
+
flexDirection: "column",
|
|
6
|
+
gap: theme.spacing(1)
|
|
7
|
+
}));
|
|
8
|
+
const SettingsRow = styled(Box)(({ theme }) => ({
|
|
9
|
+
display: "flex",
|
|
10
|
+
justifyContent: "space-between",
|
|
11
|
+
alignItems: "center",
|
|
12
|
+
gap: theme.spacing(2),
|
|
13
|
+
padding: theme.spacing(1.5, 2)
|
|
14
|
+
}));
|
|
15
|
+
const ToggleLabel = styled(Box)(({ theme }) => ({
|
|
16
|
+
fontSize: 14,
|
|
17
|
+
fontWeight: 700,
|
|
18
|
+
lineHeight: 1,
|
|
19
|
+
color: theme.vars.palette.text.primary,
|
|
20
|
+
flex: "1 0 0",
|
|
21
|
+
minWidth: 0
|
|
22
|
+
}));
|
|
23
|
+
const ExpiryValue = styled(Box)(({ theme }) => ({
|
|
24
|
+
display: "flex",
|
|
25
|
+
alignItems: "center",
|
|
26
|
+
gap: theme.spacing(.5),
|
|
27
|
+
color: theme.vars.palette.text.secondary,
|
|
28
|
+
height: 14
|
|
29
|
+
}));
|
|
30
|
+
const SettingItemButton = styled(ButtonBase)(({ theme }) => ({
|
|
31
|
+
display: "flex",
|
|
32
|
+
justifyContent: "space-between",
|
|
33
|
+
alignItems: "center",
|
|
34
|
+
width: "100%",
|
|
35
|
+
padding: theme.spacing(2),
|
|
36
|
+
borderRadius: theme.vars.shape.borderRadius
|
|
37
|
+
}));
|
|
38
|
+
const SettingItemTitle = styled(Typography)(({ theme }) => ({
|
|
39
|
+
fontSize: 14,
|
|
40
|
+
fontWeight: 700,
|
|
41
|
+
color: theme.vars.palette.text.primary,
|
|
42
|
+
lineHeight: 1
|
|
43
|
+
}));
|
|
44
|
+
const SettingItemValue = styled(Typography)(({ theme }) => ({
|
|
45
|
+
fontSize: 14,
|
|
46
|
+
fontWeight: 700,
|
|
47
|
+
color: theme.vars.palette.text.primary
|
|
48
|
+
}));
|
|
49
|
+
//#endregion
|
|
50
|
+
export { Container$1 as Container, ExpiryValue, SettingItemButton, SettingItemTitle, SettingItemValue, SettingsRow, ToggleLabel };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=LimitOrderSettings.style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LimitOrderSettings.style.js","names":["Container"],"sources":["../../../../src/components/LimitOrderSettings/LimitOrderSettings.style.tsx"],"sourcesContent":["import { Box, ButtonBase, styled, Typography } from '@mui/material'\nimport type React from 'react'\n\nexport const Container: React.FC<React.ComponentProps<typeof Box>> = styled(\n Box\n)(({ theme }) => ({\n display: 'flex',\n flexDirection: 'column',\n gap: theme.spacing(1),\n}))\n\nexport const SettingsRow: React.FC<React.ComponentProps<typeof Box>> = styled(\n Box\n)(({ theme }) => ({\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n gap: theme.spacing(2),\n padding: theme.spacing(1.5, 2),\n}))\n\nexport const ToggleLabel: React.FC<React.ComponentProps<typeof Box>> = styled(\n Box\n)(({ theme }) => ({\n fontSize: 14,\n fontWeight: 700,\n lineHeight: 1,\n color: theme.vars.palette.text.primary,\n flex: '1 0 0',\n minWidth: 0,\n}))\n\nexport const ExpiryValue: React.FC<React.ComponentProps<typeof Box>> = styled(\n Box\n)(({ theme }) => ({\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(0.5),\n color: theme.vars.palette.text.secondary,\n height: 14,\n}))\n\nexport const SettingItemButton: React.FC<\n React.ComponentProps<typeof ButtonBase>\n> = styled(ButtonBase)(({ theme }) => ({\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n width: '100%',\n padding: theme.spacing(2),\n borderRadius: theme.vars.shape.borderRadius,\n}))\n\nexport const SettingItemTitle: React.FC<\n React.ComponentProps<typeof Typography>\n> = styled(Typography)(({ theme }) => ({\n fontSize: 14,\n fontWeight: 700,\n color: theme.vars.palette.text.primary,\n lineHeight: 1,\n}))\n\nexport const SettingItemValue: React.FC<\n React.ComponentProps<typeof Typography>\n> = styled(Typography)(({ theme }) => ({\n fontSize: 14,\n fontWeight: 700,\n color: theme.vars.palette.text.primary,\n}))\n"],"mappings":";;AAGA,MAAaA,cAAwD,OACnE,GACF,CAAC,EAAE,EAAE,aAAa;CAChB,SAAS;CACT,eAAe;CACf,KAAK,MAAM,QAAQ,CAAC;AACtB,EAAE;AAEF,MAAa,cAA0D,OACrE,GACF,CAAC,EAAE,EAAE,aAAa;CAChB,SAAS;CACT,gBAAgB;CAChB,YAAY;CACZ,KAAK,MAAM,QAAQ,CAAC;CACpB,SAAS,MAAM,QAAQ,KAAK,CAAC;AAC/B,EAAE;AAEF,MAAa,cAA0D,OACrE,GACF,CAAC,EAAE,EAAE,aAAa;CAChB,UAAU;CACV,YAAY;CACZ,YAAY;CACZ,OAAO,MAAM,KAAK,QAAQ,KAAK;CAC/B,MAAM;CACN,UAAU;AACZ,EAAE;AAEF,MAAa,cAA0D,OACrE,GACF,CAAC,EAAE,EAAE,aAAa;CAChB,SAAS;CACT,YAAY;CACZ,KAAK,MAAM,QAAQ,EAAG;CACtB,OAAO,MAAM,KAAK,QAAQ,KAAK;CAC/B,QAAQ;AACV,EAAE;AAEF,MAAa,oBAET,OAAO,UAAU,CAAC,EAAE,EAAE,aAAa;CACrC,SAAS;CACT,gBAAgB;CAChB,YAAY;CACZ,OAAO;CACP,SAAS,MAAM,QAAQ,CAAC;CACxB,cAAc,MAAM,KAAK,MAAM;AACjC,EAAE;AAEF,MAAa,mBAET,OAAO,UAAU,CAAC,EAAE,EAAE,aAAa;CACrC,UAAU;CACV,YAAY;CACZ,OAAO,MAAM,KAAK,QAAQ,KAAK;CAC/B,YAAY;AACd,EAAE;AAEF,MAAa,mBAET,OAAO,UAAU,CAAC,EAAE,EAAE,aAAa;CACrC,UAAU;CACV,YAAY;CACZ,OAAO,MAAM,KAAK,QAAQ,KAAK;AACjC,EAAE"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { useFieldActions } from "../../stores/form/useFieldActions.js";
|
|
2
|
+
import { formatInputAmount } from "../../utils/format.js";
|
|
3
|
+
import { FormKeyHelper } from "../../stores/form/types.js";
|
|
4
|
+
import { useFieldValues } from "../../stores/form/useFieldValues.js";
|
|
5
|
+
import { useToken } from "../../hooks/useToken.js";
|
|
6
|
+
import { useRoutes } from "../../hooks/useRoutes.js";
|
|
7
|
+
import { CardTitle } from "../Card/CardTitle.js";
|
|
8
|
+
import { useLimitMarketRate } from "../../hooks/useLimitMarketRate.js";
|
|
9
|
+
import { applyPriceOffset, invertPrice } from "../../utils/limitOrder.js";
|
|
10
|
+
import { useLinkedLimitFields } from "../../hooks/useLinkedLimitFields.js";
|
|
11
|
+
import { AmountCard, CardBodyRow, CardHeaderRow, LargeInput } from "../AmountInputCard/AmountInputCard.style.js";
|
|
12
|
+
import { InvertChip } from "./LimitPriceCard.style.js";
|
|
13
|
+
import { LimitPricePresets } from "./LimitPricePresets.js";
|
|
14
|
+
import { useEffect, useRef, useState } from "react";
|
|
15
|
+
import { Skeleton } from "@mui/material";
|
|
16
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
|
+
import { useTranslation } from "react-i18next";
|
|
18
|
+
import SwapHorizIcon from "@mui/icons-material/SwapHoriz";
|
|
19
|
+
//#region src/components/LimitPriceCard/LimitPriceCard.tsx
|
|
20
|
+
const cleanDisplay = (value) => value ? formatInputAmount(value, 8) : "";
|
|
21
|
+
const LimitPriceCard = (props) => {
|
|
22
|
+
const { t } = useTranslation();
|
|
23
|
+
const isEditingRef = useRef(false);
|
|
24
|
+
const [inputValue, setInputValue] = useState("");
|
|
25
|
+
const [fromChainId, fromTokenAddress, toChainId, toTokenAddress, priceInverted] = useFieldValues(FormKeyHelper.getChainKey("from"), FormKeyHelper.getTokenKey("from"), FormKeyHelper.getChainKey("to"), FormKeyHelper.getTokenKey("to"), "priceInverted");
|
|
26
|
+
const { token: fromToken } = useToken(fromChainId, fromTokenAddress);
|
|
27
|
+
const { token: toToken } = useToken(toChainId, toTokenAddress);
|
|
28
|
+
const { isFetching } = useRoutes();
|
|
29
|
+
const { limitPrice, setLimitPrice } = useLinkedLimitFields();
|
|
30
|
+
const { setFieldValue } = useFieldActions();
|
|
31
|
+
const hasTokens = Boolean(fromToken && toToken);
|
|
32
|
+
const showSkeleton = isFetching && !limitPrice;
|
|
33
|
+
const leadToken = priceInverted ? toToken : fromToken;
|
|
34
|
+
const unitToken = priceInverted ? fromToken : toToken;
|
|
35
|
+
const marketCanonical = useLimitMarketRate();
|
|
36
|
+
const displayPrice = priceInverted ? invertPrice(limitPrice) : limitPrice;
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setFieldValue("toAmount", "", {
|
|
39
|
+
isDirty: false,
|
|
40
|
+
isTouched: false
|
|
41
|
+
});
|
|
42
|
+
setFieldValue("selectedRouteId", void 0);
|
|
43
|
+
}, [
|
|
44
|
+
fromChainId,
|
|
45
|
+
fromTokenAddress,
|
|
46
|
+
toChainId,
|
|
47
|
+
toTokenAddress
|
|
48
|
+
]);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!isEditingRef.current) setInputValue(cleanDisplay(displayPrice));
|
|
51
|
+
}, [displayPrice]);
|
|
52
|
+
const handleChange = (event) => {
|
|
53
|
+
isEditingRef.current = true;
|
|
54
|
+
const formatted = formatInputAmount(event.target.value, 18, true);
|
|
55
|
+
setInputValue(formatted);
|
|
56
|
+
const numeric = Number(formatted);
|
|
57
|
+
if (!formatted || Number.isNaN(numeric) || numeric <= 0) {
|
|
58
|
+
setLimitPrice("");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const canonical = priceInverted ? 1 / numeric : numeric;
|
|
62
|
+
setLimitPrice(String(canonical));
|
|
63
|
+
};
|
|
64
|
+
const handleBlur = () => {
|
|
65
|
+
isEditingRef.current = false;
|
|
66
|
+
setInputValue(cleanDisplay(displayPrice));
|
|
67
|
+
};
|
|
68
|
+
const handleInvert = () => {
|
|
69
|
+
isEditingRef.current = false;
|
|
70
|
+
setFieldValue("priceInverted", !priceInverted);
|
|
71
|
+
};
|
|
72
|
+
const handlePreset = (pct) => {
|
|
73
|
+
if (marketCanonical === void 0) return;
|
|
74
|
+
isEditingRef.current = false;
|
|
75
|
+
setLimitPrice(applyPriceOffset(String(marketCanonical), pct));
|
|
76
|
+
};
|
|
77
|
+
const label = hasTokens ? t("limitOrder.priceLabel", { symbol: leadToken?.symbol }) : t("limitOrder.price");
|
|
78
|
+
return /* @__PURE__ */ jsxs(AmountCard, {
|
|
79
|
+
...props,
|
|
80
|
+
mask: false,
|
|
81
|
+
children: [
|
|
82
|
+
/* @__PURE__ */ jsxs(CardHeaderRow, { children: [/* @__PURE__ */ jsx(CardTitle, {
|
|
83
|
+
sx: { padding: 0 },
|
|
84
|
+
children: label
|
|
85
|
+
}), hasTokens ? /* @__PURE__ */ jsxs(InvertChip, {
|
|
86
|
+
onClick: handleInvert,
|
|
87
|
+
"aria-label": "invert price",
|
|
88
|
+
children: [unitToken?.symbol, /* @__PURE__ */ jsx(SwapHorizIcon, { sx: { fontSize: 16 } })]
|
|
89
|
+
}) : null] }),
|
|
90
|
+
/* @__PURE__ */ jsx(CardBodyRow, { children: showSkeleton ? /* @__PURE__ */ jsx(Skeleton, {
|
|
91
|
+
variant: "rounded",
|
|
92
|
+
height: 32,
|
|
93
|
+
sx: { flex: 1 }
|
|
94
|
+
}) : /* @__PURE__ */ jsx(LargeInput, {
|
|
95
|
+
size: "small",
|
|
96
|
+
autoComplete: "off",
|
|
97
|
+
placeholder: "0",
|
|
98
|
+
inputProps: { inputMode: "decimal" },
|
|
99
|
+
onChange: handleChange,
|
|
100
|
+
onBlur: handleBlur,
|
|
101
|
+
value: inputValue,
|
|
102
|
+
name: "limitPrice",
|
|
103
|
+
disabled: !hasTokens
|
|
104
|
+
}) }),
|
|
105
|
+
/* @__PURE__ */ jsx(LimitPricePresets, {
|
|
106
|
+
onSelect: handlePreset,
|
|
107
|
+
disabled: !hasTokens || marketCanonical === void 0
|
|
108
|
+
})
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
//#endregion
|
|
113
|
+
export { LimitPriceCard };
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=LimitPriceCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LimitPriceCard.js","names":[],"sources":["../../../../src/components/LimitPriceCard/LimitPriceCard.tsx"],"sourcesContent":["import SwapHorizIcon from '@mui/icons-material/SwapHoriz'\nimport type { CardProps } from '@mui/material'\nimport { Skeleton } from '@mui/material'\nimport { type ChangeEvent, type JSX, useEffect, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useLimitMarketRate } from '../../hooks/useLimitMarketRate.js'\nimport { useLinkedLimitFields } from '../../hooks/useLinkedLimitFields.js'\nimport { useRoutes } from '../../hooks/useRoutes.js'\nimport { useToken } from '../../hooks/useToken.js'\nimport { FormKeyHelper } from '../../stores/form/types.js'\nimport { useFieldActions } from '../../stores/form/useFieldActions.js'\nimport { useFieldValues } from '../../stores/form/useFieldValues.js'\nimport { formatInputAmount } from '../../utils/format.js'\nimport { applyPriceOffset, invertPrice } from '../../utils/limitOrder.js'\nimport {\n AmountCard,\n amountHeight,\n CardBodyRow,\n CardHeaderRow,\n LargeInput,\n} from '../AmountInputCard/AmountInputCard.style.js'\nimport { CardTitle } from '../Card/CardTitle.js'\nimport { InvertChip } from './LimitPriceCard.style.js'\nimport { LimitPricePresets } from './LimitPricePresets.js'\n\nconst cleanDisplay = (value: string): string =>\n value ? formatInputAmount(value, 8) : ''\n\nexport const LimitPriceCard: React.FC<CardProps> = (props): JSX.Element => {\n const { t } = useTranslation()\n const isEditingRef = useRef(false)\n const [inputValue, setInputValue] = useState('')\n\n const [\n fromChainId,\n fromTokenAddress,\n toChainId,\n toTokenAddress,\n priceInverted,\n ] = useFieldValues(\n FormKeyHelper.getChainKey('from'),\n FormKeyHelper.getTokenKey('from'),\n FormKeyHelper.getChainKey('to'),\n FormKeyHelper.getTokenKey('to'),\n 'priceInverted'\n )\n const { token: fromToken } = useToken(fromChainId, fromTokenAddress)\n const { token: toToken } = useToken(toChainId, toTokenAddress)\n\n const { isFetching } = useRoutes()\n\n // limitPrice is derived from the from/to amount fields, not stored.\n const { limitPrice, setLimitPrice } = useLinkedLimitFields()\n const { setFieldValue } = useFieldActions()\n\n const hasTokens = Boolean(fromToken && toToken)\n const showSkeleton = isFetching && !limitPrice\n const leadToken = priceInverted ? toToken : fromToken\n const unitToken = priceInverted ? fromToken : toToken\n\n const marketCanonical = useLimitMarketRate()\n\n // Canonical price is orientation-independent; flip only for display.\n const displayPrice = priceInverted ? invertPrice(limitPrice) : limitPrice\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: pair fields are intentional re-run triggers, not read in the body\n useEffect(() => {\n setFieldValue('toAmount', '', { isDirty: false, isTouched: false })\n setFieldValue('selectedRouteId', undefined)\n }, [fromChainId, fromTokenAddress, toChainId, toTokenAddress])\n\n // Keep the input in sync with derived/preset/invert updates, but never yank\n // the value out from under an actively-typing user.\n useEffect(() => {\n if (!isEditingRef.current) {\n setInputValue(cleanDisplay(displayPrice))\n }\n }, [displayPrice])\n\n const handleChange = (\n event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>\n ): void => {\n isEditingRef.current = true\n const formatted = formatInputAmount(event.target.value, 18, true)\n setInputValue(formatted)\n const numeric = Number(formatted)\n if (!formatted || Number.isNaN(numeric) || numeric <= 0) {\n setLimitPrice('')\n return\n }\n // Canonical price is orientation-independent; un-flip the displayed value.\n const canonical = priceInverted ? 1 / numeric : numeric\n setLimitPrice(String(canonical))\n }\n\n const handleBlur = (): void => {\n isEditingRef.current = false\n setInputValue(cleanDisplay(displayPrice))\n }\n\n const handleInvert = (): void => {\n isEditingRef.current = false\n setFieldValue('priceInverted', !priceInverted)\n }\n\n // Apply a percentage offset to the canonical market rate (0 = at market).\n const handlePreset = (pct: number): void => {\n if (marketCanonical === undefined) {\n return\n }\n isEditingRef.current = false\n setLimitPrice(applyPriceOffset(String(marketCanonical), pct))\n }\n\n const label = hasTokens\n ? t('limitOrder.priceLabel', { symbol: leadToken?.symbol })\n : t('limitOrder.price')\n\n return (\n <AmountCard {...props} mask={false}>\n <CardHeaderRow>\n <CardTitle sx={{ padding: 0 }}>{label}</CardTitle>\n {hasTokens ? (\n <InvertChip onClick={handleInvert} aria-label=\"invert price\">\n {unitToken?.symbol}\n <SwapHorizIcon sx={{ fontSize: 16 }} />\n </InvertChip>\n ) : null}\n </CardHeaderRow>\n <CardBodyRow>\n {showSkeleton ? (\n <Skeleton variant=\"rounded\" height={amountHeight} sx={{ flex: 1 }} />\n ) : (\n <LargeInput\n size=\"small\"\n autoComplete=\"off\"\n placeholder=\"0\"\n inputProps={{ inputMode: 'decimal' }}\n onChange={handleChange}\n onBlur={handleBlur}\n value={inputValue}\n name=\"limitPrice\"\n disabled={!hasTokens}\n />\n )}\n </CardBodyRow>\n <LimitPricePresets\n onSelect={handlePreset}\n disabled={!hasTokens || marketCanonical === undefined}\n />\n </AmountCard>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAyBA,MAAM,gBAAgB,UACpB,QAAQ,kBAAkB,OAAO,CAAC,IAAI;AAExC,MAAa,kBAAuC,UAAuB;CACzE,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,eAAe,OAAO,KAAK;CACjC,MAAM,CAAC,YAAY,iBAAiB,SAAS,EAAE;CAE/C,MAAM,CACJ,aACA,kBACA,WACA,gBACA,iBACE,eACF,cAAc,YAAY,MAAM,GAChC,cAAc,YAAY,MAAM,GAChC,cAAc,YAAY,IAAI,GAC9B,cAAc,YAAY,IAAI,GAC9B,eACF;CACA,MAAM,EAAE,OAAO,cAAc,SAAS,aAAa,gBAAgB;CACnE,MAAM,EAAE,OAAO,YAAY,SAAS,WAAW,cAAc;CAE7D,MAAM,EAAE,eAAe,UAAU;CAGjC,MAAM,EAAE,YAAY,kBAAkB,qBAAqB;CAC3D,MAAM,EAAE,kBAAkB,gBAAgB;CAE1C,MAAM,YAAY,QAAQ,aAAa,OAAO;CAC9C,MAAM,eAAe,cAAc,CAAC;CACpC,MAAM,YAAY,gBAAgB,UAAU;CAC5C,MAAM,YAAY,gBAAgB,YAAY;CAE9C,MAAM,kBAAkB,mBAAmB;CAG3C,MAAM,eAAe,gBAAgB,YAAY,UAAU,IAAI;CAG/D,gBAAgB;EACd,cAAc,YAAY,IAAI;GAAE,SAAS;GAAO,WAAW;EAAM,CAAC;EAClE,cAAc,mBAAmB,KAAA,CAAS;CAC5C,GAAG;EAAC;EAAa;EAAkB;EAAW;CAAc,CAAC;CAI7D,gBAAgB;EACd,IAAI,CAAC,aAAa,SAChB,cAAc,aAAa,YAAY,CAAC;CAE5C,GAAG,CAAC,YAAY,CAAC;CAEjB,MAAM,gBACJ,UACS;EACT,aAAa,UAAU;EACvB,MAAM,YAAY,kBAAkB,MAAM,OAAO,OAAO,IAAI,IAAI;EAChE,cAAc,SAAS;EACvB,MAAM,UAAU,OAAO,SAAS;EAChC,IAAI,CAAC,aAAa,OAAO,MAAM,OAAO,KAAK,WAAW,GAAG;GACvD,cAAc,EAAE;GAChB;EACF;EAEA,MAAM,YAAY,gBAAgB,IAAI,UAAU;EAChD,cAAc,OAAO,SAAS,CAAC;CACjC;CAEA,MAAM,mBAAyB;EAC7B,aAAa,UAAU;EACvB,cAAc,aAAa,YAAY,CAAC;CAC1C;CAEA,MAAM,qBAA2B;EAC/B,aAAa,UAAU;EACvB,cAAc,iBAAiB,CAAC,aAAa;CAC/C;CAGA,MAAM,gBAAgB,QAAsB;EAC1C,IAAI,oBAAoB,KAAA,GACtB;EAEF,aAAa,UAAU;EACvB,cAAc,iBAAiB,OAAO,eAAe,GAAG,GAAG,CAAC;CAC9D;CAEA,MAAM,QAAQ,YACV,EAAE,yBAAyB,EAAE,QAAQ,WAAW,OAAO,CAAC,IACxD,EAAE,kBAAkB;CAExB,OACE,qBAAC,YAAD;EAAY,GAAI;EAAO,MAAM;YAA7B;GACE,qBAAC,eAAD,EAAA,UAAA,CACE,oBAAC,WAAD;IAAW,IAAI,EAAE,SAAS,EAAE;cAAI;GAAiB,CAAA,GAChD,YACC,qBAAC,YAAD;IAAY,SAAS;IAAc,cAAW;cAA9C,CACG,WAAW,QACZ,oBAAC,eAAD,EAAe,IAAI,EAAE,UAAU,GAAG,EAAI,CAAA,CAC5B;QACV,IACS,EAAA,CAAA;GACf,oBAAC,aAAD,EAAA,UACG,eACC,oBAAC,UAAD;IAAU,SAAQ;IAAU,QAAA;IAAsB,IAAI,EAAE,MAAM,EAAE;GAAI,CAAA,IAEpE,oBAAC,YAAD;IACE,MAAK;IACL,cAAa;IACb,aAAY;IACZ,YAAY,EAAE,WAAW,UAAU;IACnC,UAAU;IACV,QAAQ;IACR,OAAO;IACP,MAAK;IACL,UAAU,CAAC;GACZ,CAAA,EAEQ,CAAA;GACb,oBAAC,mBAAD;IACE,UAAU;IACV,UAAU,CAAC,aAAa,oBAAoB,KAAA;GAC7C,CAAA;EACS;;AAEhB"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Box, Button, ButtonBase, Typography, styled } from "@mui/material";
|
|
2
|
+
//#region src/components/LimitPriceCard/LimitPriceCard.style.tsx
|
|
3
|
+
/**
|
|
4
|
+
* Quick-set preset pill. Mirrors the percentage chips' pill look but is always
|
|
5
|
+
* visible (no reveal-on-hover), since presets are the card's primary control.
|
|
6
|
+
*/
|
|
7
|
+
const PriceChip = styled(Button)(({ theme }) => ({
|
|
8
|
+
minWidth: 0,
|
|
9
|
+
padding: theme.spacing(.5, 1.5),
|
|
10
|
+
fontSize: 12,
|
|
11
|
+
fontWeight: 700,
|
|
12
|
+
lineHeight: 1.3334,
|
|
13
|
+
backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,
|
|
14
|
+
color: theme.vars.palette.text.primary,
|
|
15
|
+
transition: "background-color 200ms ease",
|
|
16
|
+
"&:hover": { backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 8%, transparent)` }
|
|
17
|
+
}));
|
|
18
|
+
/**
|
|
19
|
+
* Editable variant of the preset pill: looks identical to {@link PriceChip} but
|
|
20
|
+
* wraps an inline input so the user can type an arbitrary percentage premium
|
|
21
|
+
* (`+__%`) instead of picking a fixed preset.
|
|
22
|
+
*/
|
|
23
|
+
const EditablePriceChip = styled(Box)(({ theme }) => ({
|
|
24
|
+
display: "inline-flex",
|
|
25
|
+
alignItems: "center",
|
|
26
|
+
minWidth: 0,
|
|
27
|
+
padding: theme.spacing(.5, 1.5),
|
|
28
|
+
borderRadius: theme.vars.shape.borderRadius,
|
|
29
|
+
fontSize: 12,
|
|
30
|
+
fontWeight: 700,
|
|
31
|
+
lineHeight: 1.3334,
|
|
32
|
+
color: theme.vars.palette.text.primary,
|
|
33
|
+
backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,
|
|
34
|
+
transition: "background-color 200ms ease",
|
|
35
|
+
cursor: "text",
|
|
36
|
+
"&:hover, &:focus-within": { backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 8%, transparent)` },
|
|
37
|
+
"&[aria-disabled=\"true\"]": {
|
|
38
|
+
opacity: .5,
|
|
39
|
+
pointerEvents: "none"
|
|
40
|
+
}
|
|
41
|
+
}));
|
|
42
|
+
/** Bare input inside {@link EditablePriceChip}; inherits the pill's typography. */
|
|
43
|
+
const PriceChipInput = styled("input")(({ theme }) => ({
|
|
44
|
+
appearance: "none",
|
|
45
|
+
border: "none",
|
|
46
|
+
outline: "none",
|
|
47
|
+
background: "transparent",
|
|
48
|
+
padding: 0,
|
|
49
|
+
margin: 0,
|
|
50
|
+
font: "inherit",
|
|
51
|
+
color: "inherit",
|
|
52
|
+
textAlign: "right",
|
|
53
|
+
minWidth: "1ch",
|
|
54
|
+
"&::placeholder": {
|
|
55
|
+
color: theme.vars.palette.text.secondary,
|
|
56
|
+
opacity: 1
|
|
57
|
+
}
|
|
58
|
+
}));
|
|
59
|
+
styled(Typography)(({ theme }) => ({
|
|
60
|
+
fontSize: 18,
|
|
61
|
+
fontWeight: 600,
|
|
62
|
+
lineHeight: 1.4,
|
|
63
|
+
whiteSpace: "nowrap",
|
|
64
|
+
color: theme.vars.palette.text.primary
|
|
65
|
+
}));
|
|
66
|
+
/**
|
|
67
|
+
* Invert affordance anchored to the far right of the lead row. Styled as a
|
|
68
|
+
* pill to mirror the percentage chips, but always visible (no reveal-on-hover).
|
|
69
|
+
*/
|
|
70
|
+
const InvertChip = styled(ButtonBase)(({ theme }) => ({
|
|
71
|
+
display: "flex",
|
|
72
|
+
alignItems: "center",
|
|
73
|
+
justifyContent: "center",
|
|
74
|
+
gap: theme.spacing(.5),
|
|
75
|
+
minWidth: 0,
|
|
76
|
+
padding: theme.spacing(.5, 1.5),
|
|
77
|
+
borderRadius: "9999px",
|
|
78
|
+
fontSize: 12,
|
|
79
|
+
fontWeight: 700,
|
|
80
|
+
lineHeight: 1.3334,
|
|
81
|
+
color: theme.vars.palette.text.primary,
|
|
82
|
+
backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,
|
|
83
|
+
transition: "background-color 200ms ease",
|
|
84
|
+
"&:hover": { backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 8%, transparent)` }
|
|
85
|
+
}));
|
|
86
|
+
//#endregion
|
|
87
|
+
export { EditablePriceChip, InvertChip, PriceChip, PriceChipInput };
|
|
88
|
+
|
|
89
|
+
//# sourceMappingURL=LimitPriceCard.style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LimitPriceCard.style.js","names":[],"sources":["../../../../src/components/LimitPriceCard/LimitPriceCard.style.tsx"],"sourcesContent":["import { Box, Button, ButtonBase, styled, Typography } from '@mui/material'\nimport type React from 'react'\n\n/**\n * Quick-set preset pill. Mirrors the percentage chips' pill look but is always\n * visible (no reveal-on-hover), since presets are the card's primary control.\n */\nexport const PriceChip: React.FC<React.ComponentProps<typeof Button>> = styled(\n Button\n)(({ theme }) => ({\n minWidth: 0,\n padding: theme.spacing(0.5, 1.5),\n fontSize: 12,\n fontWeight: 700,\n lineHeight: 1.3334,\n backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,\n color: theme.vars.palette.text.primary,\n transition: 'background-color 200ms ease',\n '&:hover': {\n backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 8%, transparent)`,\n },\n}))\n\n/**\n * Editable variant of the preset pill: looks identical to {@link PriceChip} but\n * wraps an inline input so the user can type an arbitrary percentage premium\n * (`+__%`) instead of picking a fixed preset.\n */\nexport const EditablePriceChip: React.FC<React.ComponentProps<typeof Box>> =\n styled(Box)(({ theme }) => ({\n display: 'inline-flex',\n alignItems: 'center',\n minWidth: 0,\n padding: theme.spacing(0.5, 1.5),\n borderRadius: theme.vars.shape.borderRadius,\n fontSize: 12,\n fontWeight: 700,\n lineHeight: 1.3334,\n color: theme.vars.palette.text.primary,\n backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,\n transition: 'background-color 200ms ease',\n cursor: 'text',\n '&:hover, &:focus-within': {\n backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 8%, transparent)`,\n },\n '&[aria-disabled=\"true\"]': {\n opacity: 0.5,\n pointerEvents: 'none',\n },\n }))\n\n/** Bare input inside {@link EditablePriceChip}; inherits the pill's typography. */\nexport const PriceChipInput: React.FC<React.ComponentProps<'input'>> = styled(\n 'input'\n)(({ theme }) => ({\n appearance: 'none',\n border: 'none',\n outline: 'none',\n background: 'transparent',\n padding: 0,\n margin: 0,\n font: 'inherit',\n color: 'inherit',\n textAlign: 'right',\n minWidth: '1ch',\n '&::placeholder': {\n color: theme.vars.palette.text.secondary,\n opacity: 1,\n },\n}))\n\nexport const UnitSymbol: React.FC<React.ComponentProps<typeof Typography>> =\n styled(Typography)(({ theme }) => ({\n fontSize: 18,\n fontWeight: 600,\n lineHeight: 1.4,\n whiteSpace: 'nowrap',\n color: theme.vars.palette.text.primary,\n }))\n\n/**\n * Invert affordance anchored to the far right of the lead row. Styled as a\n * pill to mirror the percentage chips, but always visible (no reveal-on-hover).\n */\nexport const InvertChip: React.FC<React.ComponentProps<typeof ButtonBase>> =\n styled(ButtonBase)(({ theme }) => ({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: theme.spacing(0.5),\n minWidth: 0,\n padding: theme.spacing(0.5, 1.5),\n borderRadius: '9999px',\n fontSize: 12,\n fontWeight: 700,\n lineHeight: 1.3334,\n color: theme.vars.palette.text.primary,\n backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 4%, transparent)`,\n transition: 'background-color 200ms ease',\n '&:hover': {\n backgroundColor: `color-mix(in srgb, ${theme.vars.palette.common.onBackground} 8%, transparent)`,\n },\n }))\n"],"mappings":";;;;;;AAOA,MAAa,YAA2D,OACtE,MACF,CAAC,EAAE,EAAE,aAAa;CAChB,UAAU;CACV,SAAS,MAAM,QAAQ,IAAK,GAAG;CAC/B,UAAU;CACV,YAAY;CACZ,YAAY;CACZ,iBAAiB,sBAAsB,MAAM,KAAK,QAAQ,OAAO,aAAa;CAC9E,OAAO,MAAM,KAAK,QAAQ,KAAK;CAC/B,YAAY;CACZ,WAAW,EACT,iBAAiB,sBAAsB,MAAM,KAAK,QAAQ,OAAO,aAAa,mBAChF;AACF,EAAE;;;;;;AAOF,MAAa,oBACX,OAAO,GAAG,CAAC,EAAE,EAAE,aAAa;CAC1B,SAAS;CACT,YAAY;CACZ,UAAU;CACV,SAAS,MAAM,QAAQ,IAAK,GAAG;CAC/B,cAAc,MAAM,KAAK,MAAM;CAC/B,UAAU;CACV,YAAY;CACZ,YAAY;CACZ,OAAO,MAAM,KAAK,QAAQ,KAAK;CAC/B,iBAAiB,sBAAsB,MAAM,KAAK,QAAQ,OAAO,aAAa;CAC9E,YAAY;CACZ,QAAQ;CACR,2BAA2B,EACzB,iBAAiB,sBAAsB,MAAM,KAAK,QAAQ,OAAO,aAAa,mBAChF;CACA,6BAA2B;EACzB,SAAS;EACT,eAAe;CACjB;AACF,EAAE;;AAGJ,MAAa,iBAA0D,OACrE,OACF,CAAC,EAAE,EAAE,aAAa;CAChB,YAAY;CACZ,QAAQ;CACR,SAAS;CACT,YAAY;CACZ,SAAS;CACT,QAAQ;CACR,MAAM;CACN,OAAO;CACP,WAAW;CACX,UAAU;CACV,kBAAkB;EAChB,OAAO,MAAM,KAAK,QAAQ,KAAK;EAC/B,SAAS;CACX;AACF,EAAE;AAGA,OAAO,UAAU,CAAC,EAAE,EAAE,aAAa;CACjC,UAAU;CACV,YAAY;CACZ,YAAY;CACZ,YAAY;CACZ,OAAO,MAAM,KAAK,QAAQ,KAAK;AACjC,EAAE;;;;;AAMJ,MAAa,aACX,OAAO,UAAU,CAAC,EAAE,EAAE,aAAa;CACjC,SAAS;CACT,YAAY;CACZ,gBAAgB;CAChB,KAAK,MAAM,QAAQ,EAAG;CACtB,UAAU;CACV,SAAS,MAAM,QAAQ,IAAK,GAAG;CAC/B,cAAc;CACd,UAAU;CACV,YAAY;CACZ,YAAY;CACZ,OAAO,MAAM,KAAK,QAAQ,KAAK;CAC/B,iBAAiB,sBAAsB,MAAM,KAAK,QAAQ,OAAO,aAAa;CAC9E,YAAY;CACZ,WAAW,EACT,iBAAiB,sBAAsB,MAAM,KAAK,QAAQ,OAAO,aAAa,mBAChF;AACF,EAAE"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { EditablePriceChip, PriceChip, PriceChipInput } from "./LimitPriceCard.style.js";
|
|
2
|
+
import { ChipContainer } from "../AmountInputCard/PercentageChips.style.js";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import { useTranslation } from "react-i18next";
|
|
6
|
+
//#region src/components/LimitPriceCard/LimitPricePresets.tsx
|
|
7
|
+
/** Quick-set presets, as percentage offsets from market (0 = at market). */
|
|
8
|
+
const PRESETS = [
|
|
9
|
+
0,
|
|
10
|
+
1,
|
|
11
|
+
5,
|
|
12
|
+
10
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Row of quick-set price controls: fixed percentage presets plus a free-form
|
|
16
|
+
* "custom %" pill that collapses to a button until engaged. Owns its own
|
|
17
|
+
* custom-input state so the card only deals with applying an offset.
|
|
18
|
+
*/
|
|
19
|
+
const LimitPricePresets = ({ onSelect, disabled }) => {
|
|
20
|
+
const { t } = useTranslation();
|
|
21
|
+
const customInputRef = useRef(null);
|
|
22
|
+
const [customPct, setCustomPct] = useState("");
|
|
23
|
+
const [customFocused, setCustomFocused] = useState(false);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (customFocused) customInputRef.current?.focus();
|
|
26
|
+
}, [customFocused]);
|
|
27
|
+
const handlePresetClick = (pct) => {
|
|
28
|
+
setCustomPct("");
|
|
29
|
+
setCustomFocused(false);
|
|
30
|
+
onSelect(pct);
|
|
31
|
+
};
|
|
32
|
+
const handleCustomChange = (event) => {
|
|
33
|
+
const raw = event.target.value;
|
|
34
|
+
if (raw && !/^[+-]?\d*\.?\d*$/.test(raw)) return;
|
|
35
|
+
setCustomPct(raw);
|
|
36
|
+
const numeric = Number(raw);
|
|
37
|
+
if (raw === "" || Number.isNaN(numeric)) return;
|
|
38
|
+
onSelect(numeric);
|
|
39
|
+
};
|
|
40
|
+
const handleCustomKeyDown = (event) => {
|
|
41
|
+
if (event.key === "Enter") event.currentTarget.blur();
|
|
42
|
+
};
|
|
43
|
+
const handleCustomBlur = () => {
|
|
44
|
+
setCustomFocused(false);
|
|
45
|
+
setCustomPct((prev) => {
|
|
46
|
+
if (!prev || prev.startsWith("+") || prev.startsWith("-")) return prev;
|
|
47
|
+
return Number.isNaN(Number(prev)) ? prev : `+${prev}`;
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
const showCustomLabel = customPct === "" && !customFocused;
|
|
51
|
+
return /* @__PURE__ */ jsxs(ChipContainer, {
|
|
52
|
+
sx: {
|
|
53
|
+
marginTop: .5,
|
|
54
|
+
flexWrap: "wrap"
|
|
55
|
+
},
|
|
56
|
+
children: [PRESETS.map((pct) => /* @__PURE__ */ jsx(PriceChip, {
|
|
57
|
+
onClick: () => handlePresetClick(pct),
|
|
58
|
+
children: pct === 0 ? t("limitOrder.market") : `+${pct}%`
|
|
59
|
+
}, pct)), showCustomLabel ? /* @__PURE__ */ jsx(PriceChip, {
|
|
60
|
+
disabled,
|
|
61
|
+
onClick: () => setCustomFocused(true),
|
|
62
|
+
children: t("limitOrder.custom")
|
|
63
|
+
}) : /* @__PURE__ */ jsxs(EditablePriceChip, {
|
|
64
|
+
"aria-disabled": disabled,
|
|
65
|
+
onClick: (event) => {
|
|
66
|
+
if (disabled) return;
|
|
67
|
+
event.currentTarget.querySelector("input")?.focus();
|
|
68
|
+
const numeric = Number(customPct);
|
|
69
|
+
if (customPct && !Number.isNaN(numeric)) onSelect(numeric);
|
|
70
|
+
},
|
|
71
|
+
children: [/* @__PURE__ */ jsx(PriceChipInput, {
|
|
72
|
+
ref: customInputRef,
|
|
73
|
+
inputMode: "decimal",
|
|
74
|
+
autoComplete: "off",
|
|
75
|
+
placeholder: "0",
|
|
76
|
+
"aria-label": t("limitOrder.custom"),
|
|
77
|
+
value: customPct,
|
|
78
|
+
onChange: handleCustomChange,
|
|
79
|
+
onKeyDown: handleCustomKeyDown,
|
|
80
|
+
onFocus: () => setCustomFocused(true),
|
|
81
|
+
onBlur: handleCustomBlur,
|
|
82
|
+
disabled,
|
|
83
|
+
style: { width: `${Math.max(customPct.length, 1)}ch` }
|
|
84
|
+
}), "%"]
|
|
85
|
+
})]
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
//#endregion
|
|
89
|
+
export { LimitPricePresets };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=LimitPricePresets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LimitPricePresets.js","names":[],"sources":["../../../../src/components/LimitPriceCard/LimitPricePresets.tsx"],"sourcesContent":["import { type ChangeEvent, type JSX, useEffect, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { ChipContainer } from '../AmountInputCard/PercentageChips.style.js'\nimport {\n EditablePriceChip,\n PriceChip,\n PriceChipInput,\n} from './LimitPriceCard.style.js'\n\n/** Quick-set presets, as percentage offsets from market (0 = at market). */\nconst PRESETS = [0, 1, 5, 10] as const\n\ninterface LimitPricePresetsProps {\n /** Apply a percentage offset from the market rate (0 = at market). */\n onSelect: (pct: number) => void\n /** Disables the free-form custom pill (no tokens / no market rate yet). */\n disabled: boolean\n}\n\n/**\n * Row of quick-set price controls: fixed percentage presets plus a free-form\n * \"custom %\" pill that collapses to a button until engaged. Owns its own\n * custom-input state so the card only deals with applying an offset.\n */\nexport const LimitPricePresets = ({\n onSelect,\n disabled,\n}: LimitPricePresetsProps): JSX.Element => {\n const { t } = useTranslation()\n const customInputRef = useRef<HTMLInputElement>(null)\n const [customPct, setCustomPct] = useState('')\n const [customFocused, setCustomFocused] = useState(false)\n\n // The custom pill renders as a plain button until engaged; when it flips into\n // edit mode (focused), move the caret into the freshly-mounted input.\n useEffect(() => {\n if (customFocused) {\n customInputRef.current?.focus()\n }\n }, [customFocused])\n\n // Picking a fixed preset clears any custom value so the custom pill collapses\n // back to its \"Custom\" button instead of showing a stale, no-longer-applied %.\n const handlePresetClick = (pct: number): void => {\n setCustomPct('')\n setCustomFocused(false)\n onSelect(pct)\n }\n\n // Free-form offset pill: apply the typed percentage live so the price input\n // tracks the offset as the user types. Accepts an optional leading +/- sign\n // so the price can be set above (premium) or below (discount) market.\n const handleCustomChange = (event: ChangeEvent<HTMLInputElement>): void => {\n const raw = event.target.value\n if (raw && !/^[+-]?\\d*\\.?\\d*$/.test(raw)) {\n return\n }\n setCustomPct(raw)\n const numeric = Number(raw)\n // Bare '', '+', '-', '.' etc. aren't applicable numbers yet — Number() maps\n // the signs/dot to NaN, and '' to 0, so guard both.\n if (raw === '' || Number.isNaN(numeric)) {\n return\n }\n onSelect(numeric)\n }\n\n const handleCustomKeyDown = (\n event: React.KeyboardEvent<HTMLInputElement>\n ): void => {\n if (event.key === 'Enter') {\n event.currentTarget.blur()\n }\n }\n\n // On blur, surface an explicit sign: a bare positive entry like \"10\" reads as\n // \"+10\" so the direction (above/below market) is unambiguous once collapsed.\n const handleCustomBlur = (): void => {\n setCustomFocused(false)\n setCustomPct((prev) => {\n if (!prev || prev.startsWith('+') || prev.startsWith('-')) {\n return prev\n }\n return Number.isNaN(Number(prev)) ? prev : `+${prev}`\n })\n }\n\n // Collapse to a plain \"Custom\" label only while empty and unfocused; once the\n // user engages (focus) or has typed a value, reveal the `+__%` affixes.\n const showCustomLabel = customPct === '' && !customFocused\n\n return (\n <ChipContainer sx={{ marginTop: 0.5, flexWrap: 'wrap' }}>\n {PRESETS.map((pct) => (\n <PriceChip key={pct} onClick={() => handlePresetClick(pct)}>\n {pct === 0 ? t('limitOrder.market') : `+${pct}%`}\n </PriceChip>\n ))}\n {showCustomLabel ? (\n <PriceChip disabled={disabled} onClick={() => setCustomFocused(true)}>\n {t('limitOrder.custom')}\n </PriceChip>\n ) : (\n <EditablePriceChip\n aria-disabled={disabled}\n onClick={(event) => {\n // `aria-disabled` only announces the state; enforce it so a\n // programmatic click can't apply an offset while disabled.\n if (disabled) {\n return\n }\n const input = event.currentTarget.querySelector('input')\n input?.focus()\n const numeric = Number(customPct)\n if (customPct && !Number.isNaN(numeric)) {\n onSelect(numeric)\n }\n }}\n >\n <PriceChipInput\n ref={customInputRef}\n inputMode=\"decimal\"\n autoComplete=\"off\"\n placeholder=\"0\"\n aria-label={t('limitOrder.custom')}\n value={customPct}\n onChange={handleCustomChange}\n onKeyDown={handleCustomKeyDown}\n onFocus={() => setCustomFocused(true)}\n onBlur={handleCustomBlur}\n disabled={disabled}\n style={{ width: `${Math.max(customPct.length, 1)}ch` }}\n />\n %\n </EditablePriceChip>\n )}\n </ChipContainer>\n )\n}\n"],"mappings":";;;;;;;AAUA,MAAM,UAAU;CAAC;CAAG;CAAG;CAAG;AAAE;;;;;;AAc5B,MAAa,qBAAqB,EAChC,UACA,eACyC;CACzC,MAAM,EAAE,MAAM,eAAe;CAC7B,MAAM,iBAAiB,OAAyB,IAAI;CACpD,MAAM,CAAC,WAAW,gBAAgB,SAAS,EAAE;CAC7C,MAAM,CAAC,eAAe,oBAAoB,SAAS,KAAK;CAIxD,gBAAgB;EACd,IAAI,eACF,eAAe,SAAS,MAAM;CAElC,GAAG,CAAC,aAAa,CAAC;CAIlB,MAAM,qBAAqB,QAAsB;EAC/C,aAAa,EAAE;EACf,iBAAiB,KAAK;EACtB,SAAS,GAAG;CACd;CAKA,MAAM,sBAAsB,UAA+C;EACzE,MAAM,MAAM,MAAM,OAAO;EACzB,IAAI,OAAO,CAAC,mBAAmB,KAAK,GAAG,GACrC;EAEF,aAAa,GAAG;EAChB,MAAM,UAAU,OAAO,GAAG;EAG1B,IAAI,QAAQ,MAAM,OAAO,MAAM,OAAO,GACpC;EAEF,SAAS,OAAO;CAClB;CAEA,MAAM,uBACJ,UACS;EACT,IAAI,MAAM,QAAQ,SAChB,MAAM,cAAc,KAAK;CAE7B;CAIA,MAAM,yBAA+B;EACnC,iBAAiB,KAAK;EACtB,cAAc,SAAS;GACrB,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,GACtD,OAAO;GAET,OAAO,OAAO,MAAM,OAAO,IAAI,CAAC,IAAI,OAAO,IAAI;EACjD,CAAC;CACH;CAIA,MAAM,kBAAkB,cAAc,MAAM,CAAC;CAE7C,OACE,qBAAC,eAAD;EAAe,IAAI;GAAE,WAAW;GAAK,UAAU;EAAO;YAAtD,CACG,QAAQ,KAAK,QACZ,oBAAC,WAAD;GAAqB,eAAe,kBAAkB,GAAG;aACtD,QAAQ,IAAI,EAAE,mBAAmB,IAAI,IAAI,IAAI;EACrC,GAFK,GAEL,CACZ,GACA,kBACC,oBAAC,WAAD;GAAqB;GAAU,eAAe,iBAAiB,IAAI;aAChE,EAAE,mBAAmB;EACb,CAAA,IAEX,qBAAC,mBAAD;GACE,iBAAe;GACf,UAAU,UAAU;IAGlB,IAAI,UACF;IAGF,MADoB,cAAc,cAAc,OAC5C,CAAC,EAAE,MAAM;IACb,MAAM,UAAU,OAAO,SAAS;IAChC,IAAI,aAAa,CAAC,OAAO,MAAM,OAAO,GACpC,SAAS,OAAO;GAEpB;aAdF,CAgBE,oBAAC,gBAAD;IACE,KAAK;IACL,WAAU;IACV,cAAa;IACb,aAAY;IACZ,cAAY,EAAE,mBAAmB;IACjC,OAAO;IACP,UAAU;IACV,WAAW;IACX,eAAe,iBAAiB,IAAI;IACpC,QAAQ;IACE;IACV,OAAO,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,QAAQ,CAAC,EAAE,IAAI;GACtD,CAAA,GAAC,GAEe;IAER;;AAEnB"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Card as Card$1 } from "../Card/Card.js";
|
|
2
|
+
import { getAccumulatedFeeCostsBreakdown } from "../../utils/fees.js";
|
|
3
|
+
import { SmallAvatar } from "../Avatar/SmallAvatar.js";
|
|
4
|
+
import { IconTypography } from "../IconTypography.js";
|
|
5
|
+
import { Box, Skeleton, Typography } from "@mui/material";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { useTranslation } from "react-i18next";
|
|
8
|
+
import LocalGasStationRounded from "@mui/icons-material/LocalGasStationRounded";
|
|
9
|
+
//#region src/components/RouteCard/RouteProviderCard.tsx
|
|
10
|
+
const RouteProviderCard = ({ route, active, onClick, ...other }) => {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
const provider = route.steps[0]?.toolDetails;
|
|
13
|
+
const { gasCostUSD } = getAccumulatedFeeCostsBreakdown(route);
|
|
14
|
+
return /* @__PURE__ */ jsx(Card$1, {
|
|
15
|
+
type: active ? "selected" : "default",
|
|
16
|
+
selectionColor: "secondary",
|
|
17
|
+
onClick,
|
|
18
|
+
sx: {
|
|
19
|
+
py: 1.5,
|
|
20
|
+
px: 2,
|
|
21
|
+
...onClick ? { cursor: "pointer" } : {}
|
|
22
|
+
},
|
|
23
|
+
...other,
|
|
24
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
25
|
+
sx: {
|
|
26
|
+
display: "flex",
|
|
27
|
+
alignItems: "center",
|
|
28
|
+
justifyContent: "space-between",
|
|
29
|
+
gap: 1
|
|
30
|
+
},
|
|
31
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
32
|
+
sx: {
|
|
33
|
+
display: "flex",
|
|
34
|
+
alignItems: "center",
|
|
35
|
+
gap: 1,
|
|
36
|
+
minWidth: 0
|
|
37
|
+
},
|
|
38
|
+
children: [/* @__PURE__ */ jsx(SmallAvatar, {
|
|
39
|
+
src: provider?.logoURI,
|
|
40
|
+
alt: provider?.name,
|
|
41
|
+
size: 20,
|
|
42
|
+
children: provider?.name?.[0]
|
|
43
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
44
|
+
noWrap: true,
|
|
45
|
+
sx: {
|
|
46
|
+
fontSize: 14,
|
|
47
|
+
fontWeight: 600,
|
|
48
|
+
color: "text.primary"
|
|
49
|
+
},
|
|
50
|
+
children: provider?.name
|
|
51
|
+
})]
|
|
52
|
+
}), /* @__PURE__ */ jsxs(Box, {
|
|
53
|
+
sx: {
|
|
54
|
+
display: "flex",
|
|
55
|
+
alignItems: "center"
|
|
56
|
+
},
|
|
57
|
+
children: [/* @__PURE__ */ jsx(IconTypography, {
|
|
58
|
+
component: "span",
|
|
59
|
+
sx: {
|
|
60
|
+
mr: .5,
|
|
61
|
+
fontSize: 16
|
|
62
|
+
},
|
|
63
|
+
children: /* @__PURE__ */ jsx(LocalGasStationRounded, { fontSize: "inherit" })
|
|
64
|
+
}), /* @__PURE__ */ jsx(Typography, {
|
|
65
|
+
"data-value": gasCostUSD,
|
|
66
|
+
sx: {
|
|
67
|
+
fontSize: 14,
|
|
68
|
+
color: "text.primary",
|
|
69
|
+
fontWeight: 600,
|
|
70
|
+
lineHeight: 1
|
|
71
|
+
},
|
|
72
|
+
children: !gasCostUSD ? t("main.fees.free") : t("format.currency", { value: gasCostUSD })
|
|
73
|
+
})]
|
|
74
|
+
})]
|
|
75
|
+
})
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const RouteProviderCardSkeleton = (props) => {
|
|
79
|
+
return /* @__PURE__ */ jsx(Card$1, {
|
|
80
|
+
sx: {
|
|
81
|
+
py: 1.5,
|
|
82
|
+
px: 2
|
|
83
|
+
},
|
|
84
|
+
...props,
|
|
85
|
+
children: /* @__PURE__ */ jsxs(Box, {
|
|
86
|
+
sx: {
|
|
87
|
+
display: "flex",
|
|
88
|
+
alignItems: "center",
|
|
89
|
+
justifyContent: "space-between",
|
|
90
|
+
gap: 1
|
|
91
|
+
},
|
|
92
|
+
children: [/* @__PURE__ */ jsxs(Box, {
|
|
93
|
+
sx: {
|
|
94
|
+
display: "flex",
|
|
95
|
+
alignItems: "center",
|
|
96
|
+
gap: 1
|
|
97
|
+
},
|
|
98
|
+
children: [/* @__PURE__ */ jsx(Skeleton, {
|
|
99
|
+
variant: "circular",
|
|
100
|
+
width: 20,
|
|
101
|
+
height: 20
|
|
102
|
+
}), /* @__PURE__ */ jsx(Skeleton, {
|
|
103
|
+
variant: "text",
|
|
104
|
+
width: 80,
|
|
105
|
+
height: 20
|
|
106
|
+
})]
|
|
107
|
+
}), /* @__PURE__ */ jsx(Skeleton, {
|
|
108
|
+
variant: "text",
|
|
109
|
+
width: 56,
|
|
110
|
+
height: 20
|
|
111
|
+
})]
|
|
112
|
+
})
|
|
113
|
+
});
|
|
114
|
+
};
|
|
115
|
+
//#endregion
|
|
116
|
+
export { RouteProviderCard, RouteProviderCardSkeleton };
|
|
117
|
+
|
|
118
|
+
//# sourceMappingURL=RouteProviderCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RouteProviderCard.js","names":["Card"],"sources":["../../../../src/components/RouteCard/RouteProviderCard.tsx"],"sourcesContent":["import type { Route } from '@lifi/sdk'\nimport LocalGasStationRounded from '@mui/icons-material/LocalGasStationRounded'\nimport { Box, Skeleton, Typography } from '@mui/material'\nimport { useTranslation } from 'react-i18next'\nimport { getAccumulatedFeeCostsBreakdown } from '../../utils/fees.js'\nimport { SmallAvatar } from '../Avatar/SmallAvatar.js'\nimport type { CardProps } from '../Card/Card.js'\nimport { Card } from '../Card/Card.js'\nimport { IconTypography } from '../IconTypography.js'\n\ninterface RouteProviderCardProps {\n route: Route\n active?: boolean\n}\n\nexport const RouteProviderCard: React.FC<\n RouteProviderCardProps & Omit<CardProps, 'variant'>\n> = ({ route, active, onClick, ...other }) => {\n const { t } = useTranslation()\n\n const provider = route.steps[0]?.toolDetails\n const { gasCostUSD } = getAccumulatedFeeCostsBreakdown(route)\n\n return (\n <Card\n type={active ? 'selected' : 'default'}\n selectionColor=\"secondary\"\n onClick={onClick}\n sx={{ py: 1.5, px: 2, ...(onClick ? { cursor: 'pointer' } : {}) }}\n {...other}\n >\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 1,\n }}\n >\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n gap: 1,\n minWidth: 0,\n }}\n >\n <SmallAvatar src={provider?.logoURI} alt={provider?.name} size={20}>\n {provider?.name?.[0]}\n </SmallAvatar>\n <Typography\n noWrap\n sx={{\n fontSize: 14,\n fontWeight: 600,\n color: 'text.primary',\n }}\n >\n {provider?.name}\n </Typography>\n </Box>\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n }}\n >\n <IconTypography component=\"span\" sx={{ mr: 0.5, fontSize: 16 }}>\n <LocalGasStationRounded fontSize=\"inherit\" />\n </IconTypography>\n <Typography\n data-value={gasCostUSD}\n sx={{\n fontSize: 14,\n color: 'text.primary',\n fontWeight: 600,\n lineHeight: 1,\n }}\n >\n {!gasCostUSD\n ? t('main.fees.free')\n : t('format.currency', {\n value: gasCostUSD,\n })}\n </Typography>\n </Box>\n </Box>\n </Card>\n )\n}\n\nexport const RouteProviderCardSkeleton: React.FC<Omit<CardProps, 'variant'>> = (\n props\n) => {\n return (\n <Card sx={{ py: 1.5, px: 2 }} {...props}>\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 1,\n }}\n >\n <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>\n <Skeleton variant=\"circular\" width={20} height={20} />\n <Skeleton variant=\"text\" width={80} height={20} />\n </Box>\n <Skeleton variant=\"text\" width={56} height={20} />\n </Box>\n </Card>\n )\n}\n"],"mappings":";;;;;;;;;AAeA,MAAa,qBAER,EAAE,OAAO,QAAQ,SAAS,GAAG,YAAY;CAC5C,MAAM,EAAE,MAAM,eAAe;CAE7B,MAAM,WAAW,MAAM,MAAM,EAAE,EAAE;CACjC,MAAM,EAAE,eAAe,gCAAgC,KAAK;CAE5D,OACE,oBAACA,QAAD;EACE,MAAM,SAAS,aAAa;EAC5B,gBAAe;EACN;EACT,IAAI;GAAE,IAAI;GAAK,IAAI;GAAG,GAAI,UAAU,EAAE,QAAQ,UAAU,IAAI,CAAC;EAAG;EAChE,GAAI;YAEJ,qBAAC,KAAD;GACE,IAAI;IACF,SAAS;IACT,YAAY;IACZ,gBAAgB;IAChB,KAAK;GACP;aANF,CAQE,qBAAC,KAAD;IACE,IAAI;KACF,SAAS;KACT,YAAY;KACZ,KAAK;KACL,UAAU;IACZ;cANF,CAQE,oBAAC,aAAD;KAAa,KAAK,UAAU;KAAS,KAAK,UAAU;KAAM,MAAM;eAC7D,UAAU,OAAO;IACP,CAAA,GACb,oBAAC,YAAD;KACE,QAAA;KACA,IAAI;MACF,UAAU;MACV,YAAY;MACZ,OAAO;KACT;eAEC,UAAU;IACD,CAAA,CACT;OACL,qBAAC,KAAD;IACE,IAAI;KACF,SAAS;KACT,YAAY;IACd;cAJF,CAME,oBAAC,gBAAD;KAAgB,WAAU;KAAO,IAAI;MAAE,IAAI;MAAK,UAAU;KAAG;eAC3D,oBAAC,wBAAD,EAAwB,UAAS,UAAW,CAAA;IAC9B,CAAA,GAChB,oBAAC,YAAD;KACE,cAAY;KACZ,IAAI;MACF,UAAU;MACV,OAAO;MACP,YAAY;MACZ,YAAY;KACd;eAEC,CAAC,aACE,EAAE,gBAAgB,IAClB,EAAE,mBAAmB,EACnB,OAAO,WACT,CAAC;IACK,CAAA,CACT;KACF;;CACD,CAAA;AAEV;AAEA,MAAa,6BACX,UACG;CACH,OACE,oBAACA,QAAD;EAAM,IAAI;GAAE,IAAI;GAAK,IAAI;EAAE;EAAG,GAAI;YAChC,qBAAC,KAAD;GACE,IAAI;IACF,SAAS;IACT,YAAY;IACZ,gBAAgB;IAChB,KAAK;GACP;aANF,CAQE,qBAAC,KAAD;IAAK,IAAI;KAAE,SAAS;KAAQ,YAAY;KAAU,KAAK;IAAE;cAAzD,CACE,oBAAC,UAAD;KAAU,SAAQ;KAAW,OAAO;KAAI,QAAQ;IAAK,CAAA,GACrD,oBAAC,UAAD;KAAU,SAAQ;KAAO,OAAO;KAAI,QAAQ;IAAK,CAAA,CAC9C;OACL,oBAAC,UAAD;IAAU,SAAQ;IAAO,OAAO;IAAI,QAAQ;GAAK,CAAA,CAC9C;;CACD,CAAA;AAEV"}
|