Redesign and expose styling and theming (#25)
This commit is contained in:
parent
99a49ab334
commit
004938274b
|
@ -6,6 +6,7 @@
|
||||||
"homepage": "https://github.com/project-serum/swap-ui",
|
"homepage": "https://github.com/project-serum/swap-ui",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource/roboto": "^4.3.0",
|
||||||
"@project-serum/serum": "^0.13.34",
|
"@project-serum/serum": "^0.13.34",
|
||||||
"@project-serum/swap": "^0.1.0-alpha.14",
|
"@project-serum/swap": "^0.1.0-alpha.14",
|
||||||
"@solana/spl-token": "^0.1.4"
|
"@solana/spl-token": "^0.1.4"
|
||||||
|
|
|
@ -13,24 +13,18 @@ import { useSwapContext, useSwapFair } from "../context/Swap";
|
||||||
import { useMint } from "../context/Token";
|
import { useMint } from "../context/Token";
|
||||||
import { useRoute, useMarketName, useBbo } from "../context/Dex";
|
import { useRoute, useMarketName, useBbo } from "../context/Dex";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles(() => ({
|
||||||
infoLabel: {
|
infoLabel: {
|
||||||
marginTop: "10px",
|
marginTop: "20px",
|
||||||
marginBottom: "10px",
|
marginBottom: "20px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "flex-end",
|
||||||
marginLeft: "5px",
|
alignItems: "center",
|
||||||
marginRight: "5px",
|
|
||||||
},
|
|
||||||
fairPriceLabel: {
|
|
||||||
marginRight: "10px",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
},
|
},
|
||||||
infoButton: {
|
infoButton: {
|
||||||
|
marginLeft: "5px",
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
fontSize: "14px",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -47,18 +41,15 @@ export function InfoLabel() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.infoLabel}>
|
<div className={styles.infoLabel}>
|
||||||
<Typography color="textSecondary"></Typography>
|
<Typography color="textSecondary" style={{ fontSize: "14px" }}>
|
||||||
<div style={{ display: "flex" }}>
|
|
||||||
<div className={styles.fairPriceLabel}>
|
|
||||||
{fair !== undefined && toTokenInfo && fromTokenInfo
|
{fair !== undefined && toTokenInfo && fromTokenInfo
|
||||||
? `1 ${toTokenInfo.symbol} = ${fair.toFixed(
|
? `1 ${toTokenInfo.symbol} = ${fair.toFixed(
|
||||||
fromMintInfo?.decimals
|
fromMintInfo?.decimals
|
||||||
)} ${fromTokenInfo.symbol}`
|
)} ${fromTokenInfo.symbol}`
|
||||||
: `-`}
|
: `-`}
|
||||||
</div>
|
</Typography>
|
||||||
<InfoButton />
|
<InfoButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +65,7 @@ function InfoButton() {
|
||||||
{...bindTrigger(popupState)}
|
{...bindTrigger(popupState)}
|
||||||
className={styles.infoButton}
|
className={styles.infoButton}
|
||||||
>
|
>
|
||||||
<Info />
|
<Info fontSize="small" />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Popover
|
<Popover
|
||||||
{...bindPopover(popupState)}
|
{...bindPopover(popupState)}
|
||||||
|
|
|
@ -28,8 +28,8 @@ import { useMint, useOwnedTokenAccount } from "../context/Token";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
table: {},
|
table: {},
|
||||||
closeAccountSwitchLabel: {
|
closeAccount: {
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.error.main,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -40,6 +40,8 @@ export default function OpenOrdersDialog({
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
maxWidth="lg"
|
maxWidth="lg"
|
||||||
|
@ -51,21 +53,16 @@ export default function OpenOrdersDialog({
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
height: "24px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "flex-end",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
style={{
|
style={{
|
||||||
padding: 0,
|
padding: 10,
|
||||||
position: "absolute",
|
|
||||||
right: "8px",
|
|
||||||
top: "8px",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Close />
|
<Close />
|
||||||
|
@ -74,7 +71,6 @@ export default function OpenOrdersDialog({
|
||||||
<DialogContent style={{ paddingTop: 0 }}>
|
<DialogContent style={{ paddingTop: 0 }}>
|
||||||
<OpenOrdersAccounts />
|
<OpenOrdersAccounts />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</div>
|
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -126,6 +122,8 @@ function OpenOrdersRow({
|
||||||
market: PublicKey;
|
market: PublicKey;
|
||||||
openOrders: Array<OpenOrders>;
|
openOrders: Array<OpenOrders>;
|
||||||
}) {
|
}) {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
const [ooAccount, setOoAccount] = useState(openOrders[0]);
|
const [ooAccount, setOoAccount] = useState(openOrders[0]);
|
||||||
const { swapClient } = useDexContext();
|
const { swapClient } = useDexContext();
|
||||||
const marketClient = useMarket(market);
|
const marketClient = useMarket(market);
|
||||||
|
@ -235,9 +233,9 @@ function OpenOrdersRow({
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell align="center">
|
<TableCell align="center">
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
|
||||||
disabled={closeDisabled}
|
disabled={closeDisabled}
|
||||||
onClick={closeOpenOrders}
|
onClick={closeOpenOrders}
|
||||||
|
className={styles.closeAccount}
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { ToggleButton } from "@material-ui/lab";
|
|
||||||
import { SettingsOutlined as Settings } from "@material-ui/icons";
|
import { SettingsOutlined as Settings } from "@material-ui/icons";
|
||||||
import PopupState, { bindTrigger, bindPopover } from "material-ui-popup-state";
|
import PopupState, { bindTrigger, bindPopover } from "material-ui-popup-state";
|
||||||
import { useSwapContext, useSwapFair } from "../context/Swap";
|
import { useSwapContext, useSwapFair } from "../context/Swap";
|
||||||
|
@ -25,10 +24,27 @@ const useStyles = makeStyles((theme) => ({
|
||||||
table: {},
|
table: {},
|
||||||
settingsButton: {
|
settingsButton: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
|
color: theme.palette.primary.main,
|
||||||
},
|
},
|
||||||
closeAccountSwitchLabel: {
|
closeAccountSwitchLabel: {
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
},
|
},
|
||||||
|
fairAutoSelected: {
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
padding: "3px 5px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
color: theme.palette.primary.contrastText,
|
||||||
|
fontWeight: 700,
|
||||||
|
},
|
||||||
|
fairAuto: {
|
||||||
|
backgroundColor:
|
||||||
|
theme.palette.type === "dark"
|
||||||
|
? theme.palette.secondary.light
|
||||||
|
: theme.palette.secondary.main,
|
||||||
|
padding: "3px 5px",
|
||||||
|
borderRadius: "10px",
|
||||||
|
boxShadow: "none",
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export function SettingsButton() {
|
export function SettingsButton() {
|
||||||
|
@ -56,7 +72,12 @@ export function SettingsButton() {
|
||||||
vertical: "top",
|
vertical: "top",
|
||||||
horizontal: "right",
|
horizontal: "right",
|
||||||
}}
|
}}
|
||||||
PaperProps={{ style: { borderRadius: "10px" } }}
|
PaperProps={{
|
||||||
|
style: {
|
||||||
|
borderRadius: "10px",
|
||||||
|
boxShadow: "0px 0px 30px 5px rgba(0,0,0,0.075)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SettingsDetails />
|
<SettingsDetails />
|
||||||
</Popover>
|
</Popover>
|
||||||
|
@ -68,6 +89,8 @@ export function SettingsButton() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function SettingsDetails() {
|
function SettingsDetails() {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
const { slippage, setSlippage, fairOverride, setFairOverride } =
|
const { slippage, setSlippage, fairOverride, setFairOverride } =
|
||||||
useSwapContext();
|
useSwapContext();
|
||||||
const [showSettingsDialog, setShowSettingsDialog] = useState(false);
|
const [showSettingsDialog, setShowSettingsDialog] = useState(false);
|
||||||
|
@ -80,12 +103,12 @@ function SettingsDetails() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: "15px", width: "305px" }}>
|
<div style={{ padding: "15px", width: "305px" }}>
|
||||||
<Typography color="textSecondary" style={{ fontWeight: "bold" }}>
|
<Typography style={{ fontWeight: "bold" }}>Settings</Typography>
|
||||||
Settings
|
|
||||||
</Typography>
|
|
||||||
<div style={{ marginTop: "10px" }}>
|
|
||||||
<div>
|
<div>
|
||||||
<Typography color="textSecondary">Slippage tolerance</Typography>
|
<div style={{ marginTop: "10px" }}>
|
||||||
|
<Typography color="textSecondary" style={{ fontSize: "12px" }}>
|
||||||
|
Slippage tolerance
|
||||||
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="Error tolerance percentage"
|
placeholder="Error tolerance percentage"
|
||||||
|
@ -101,8 +124,10 @@ function SettingsDetails() {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: "5px" }}>
|
<div style={{ marginTop: "10px" }}>
|
||||||
<Typography color="textSecondary">Fair price</Typography>
|
<Typography color="textSecondary" style={{ fontSize: "12px" }}>
|
||||||
|
Fair price
|
||||||
|
</Typography>
|
||||||
<div style={{ display: "flex" }}>
|
<div style={{ display: "flex" }}>
|
||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
|
@ -118,9 +143,9 @@ function SettingsDetails() {
|
||||||
}}
|
}}
|
||||||
disabled={fairOverride === null}
|
disabled={fairOverride === null}
|
||||||
/>
|
/>
|
||||||
<ToggleButton
|
<Button
|
||||||
value="bold"
|
component="div"
|
||||||
selected={fairOverride === null}
|
variant="contained"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (fair === undefined) {
|
if (fair === undefined) {
|
||||||
console.error("Fair is undefined");
|
console.error("Fair is undefined");
|
||||||
|
@ -132,27 +157,22 @@ function SettingsDetails() {
|
||||||
setFairOverride(null);
|
setFairOverride(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
className={
|
||||||
paddingTop: "3px",
|
fairOverride === null
|
||||||
paddingBottom: "3px",
|
? styles.fairAutoSelected
|
||||||
paddingLeft: "5px",
|
: styles.fairAuto
|
||||||
paddingRight: "5px",
|
}
|
||||||
borderRadius: "20px",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Auto
|
Auto
|
||||||
</ToggleButton>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: "5px" }}>
|
<div style={{ margin: "10px 0px" }}>
|
||||||
<CloseNewAccountsSwitch />
|
<CloseNewAccountsSwitch />
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
style={{
|
variant="contained"
|
||||||
width: "100%",
|
fullWidth
|
||||||
marginTop: "10px",
|
|
||||||
background: "#e0e0e0",
|
|
||||||
}}
|
|
||||||
disabled={swapClient.program.provider.wallet.publicKey === null}
|
disabled={swapClient.program.provider.wallet.publicKey === null}
|
||||||
onClick={() => setShowSettingsDialog(true)}
|
onClick={() => setShowSettingsDialog(true)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -5,11 +5,11 @@ import {
|
||||||
makeStyles,
|
makeStyles,
|
||||||
Card,
|
Card,
|
||||||
Button,
|
Button,
|
||||||
Paper,
|
|
||||||
Typography,
|
Typography,
|
||||||
TextField,
|
TextField,
|
||||||
|
useTheme,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { ExpandMore } from "@material-ui/icons";
|
import { ExpandMore, ImportExportRounded } from "@material-ui/icons";
|
||||||
import { useSwapContext, useSwapFair } from "../context/Swap";
|
import { useSwapContext, useSwapFair } from "../context/Swap";
|
||||||
import {
|
import {
|
||||||
useDexContext,
|
useDexContext,
|
||||||
|
@ -25,16 +25,12 @@ import TokenDialog from "./TokenDialog";
|
||||||
import { SettingsButton } from "./Settings";
|
import { SettingsButton } from "./Settings";
|
||||||
import { InfoLabel } from "./Info";
|
import { InfoLabel } from "./Info";
|
||||||
|
|
||||||
const useStyles = makeStyles(() => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
card: {
|
card: {
|
||||||
width: "450px",
|
width: "450px",
|
||||||
borderRadius: "10px",
|
borderRadius: "16px",
|
||||||
border: "solid 1pt #e0e0e0",
|
boxShadow: "0px 0px 30px 5px rgba(0,0,0,0.075)",
|
||||||
},
|
padding: "16px",
|
||||||
cardContent: {
|
|
||||||
marginLeft: "6px",
|
|
||||||
marginRight: "6px",
|
|
||||||
marginBottom: "6px",
|
|
||||||
},
|
},
|
||||||
tab: {
|
tab: {
|
||||||
width: "50%",
|
width: "50%",
|
||||||
|
@ -44,30 +40,78 @@ const useStyles = makeStyles(() => ({
|
||||||
},
|
},
|
||||||
swapButton: {
|
swapButton: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
borderRadius: "15px",
|
borderRadius: "10px",
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
color: theme.palette.primary.contrastText,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 700,
|
||||||
|
padding: "10px",
|
||||||
},
|
},
|
||||||
swapToFromButton: {
|
swapToFromButton: {
|
||||||
display: "block",
|
display: "block",
|
||||||
marginLeft: "auto",
|
margin: "10px auto 10px auto",
|
||||||
marginRight: "auto",
|
cursor: "pointer",
|
||||||
|
},
|
||||||
|
amountInput: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
textAlign: "right",
|
||||||
|
},
|
||||||
|
swapTokenFormContainer: {
|
||||||
|
borderRadius: "10px",
|
||||||
|
boxShadow: "0px 0px 15px 2px rgba(33,150,243,0.1)",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
padding: "10px",
|
||||||
|
},
|
||||||
|
swapTokenSelectorContainer: {
|
||||||
|
marginLeft: "5px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
},
|
||||||
|
balanceContainer: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
fontSize: "14px",
|
||||||
|
},
|
||||||
|
maxButton: {
|
||||||
|
marginLeft: 10,
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: "12px",
|
||||||
|
cursor: "pointer",
|
||||||
|
},
|
||||||
|
tokenButton: {
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
marginBottom: "10px",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function SwapCard({ style }: { style?: any }) {
|
export default function SwapCard({
|
||||||
|
containerStyle,
|
||||||
|
contentStyle,
|
||||||
|
swapTokenContainerStyle,
|
||||||
|
}: {
|
||||||
|
containerStyle?: any;
|
||||||
|
contentStyle?: any;
|
||||||
|
swapTokenContainerStyle?: any;
|
||||||
|
}) {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
return (
|
return (
|
||||||
<div style={style}>
|
<Card className={styles.card} style={containerStyle}>
|
||||||
<Card className={styles.card}>
|
|
||||||
<SwapHeader />
|
<SwapHeader />
|
||||||
<div className={styles.cardContent}>
|
<div style={contentStyle}>
|
||||||
<SwapFromForm />
|
<SwapFromForm style={swapTokenContainerStyle} />
|
||||||
<ArrowButton />
|
<ArrowButton />
|
||||||
<SwapToForm />
|
<SwapToForm style={swapTokenContainerStyle} />
|
||||||
<InfoLabel />
|
<InfoLabel />
|
||||||
<SwapButton />
|
<SwapButton />
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,18 +121,17 @@ function SwapHeader() {
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
margin: "8px",
|
marginBottom: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
style={{
|
style={{
|
||||||
fontWeight: "bold",
|
fontSize: 18,
|
||||||
display: "flex",
|
fontWeight: 700,
|
||||||
justifyContent: "center",
|
fontFamily: "Roboto Condensed",
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Swap
|
SWAP
|
||||||
</Typography>
|
</Typography>
|
||||||
<SettingsButton />
|
<SettingsButton />
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,18 +140,24 @@ function SwapHeader() {
|
||||||
|
|
||||||
export function ArrowButton() {
|
export function ArrowButton() {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
const { swapToFromMints } = useSwapContext();
|
const { swapToFromMints } = useSwapContext();
|
||||||
return (
|
return (
|
||||||
<Button className={styles.swapToFromButton} onClick={swapToFromMints}>
|
<ImportExportRounded
|
||||||
⇅
|
className={styles.swapToFromButton}
|
||||||
</Button>
|
fontSize="large"
|
||||||
|
htmlColor={theme.palette.primary.main}
|
||||||
|
onClick={swapToFromMints}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SwapFromForm() {
|
function SwapFromForm({ style }: { style?: any }) {
|
||||||
const { fromMint, setFromMint, fromAmount, setFromAmount } = useSwapContext();
|
const { fromMint, setFromMint, fromAmount, setFromAmount } = useSwapContext();
|
||||||
return (
|
return (
|
||||||
<SwapTokenForm
|
<SwapTokenForm
|
||||||
|
from
|
||||||
|
style={style}
|
||||||
mint={fromMint}
|
mint={fromMint}
|
||||||
setMint={setFromMint}
|
setMint={setFromMint}
|
||||||
amount={fromAmount}
|
amount={fromAmount}
|
||||||
|
@ -117,10 +166,12 @@ function SwapFromForm() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SwapToForm() {
|
function SwapToForm({ style }: { style?: any }) {
|
||||||
const { toMint, setToMint, toAmount, setToAmount } = useSwapContext();
|
const { toMint, setToMint, toAmount, setToAmount } = useSwapContext();
|
||||||
return (
|
return (
|
||||||
<SwapTokenForm
|
<SwapTokenForm
|
||||||
|
from={false}
|
||||||
|
style={style}
|
||||||
mint={toMint}
|
mint={toMint}
|
||||||
setMint={setToMint}
|
setMint={setToMint}
|
||||||
amount={toAmount}
|
amount={toAmount}
|
||||||
|
@ -130,57 +181,75 @@ function SwapToForm() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function SwapTokenForm({
|
function SwapTokenForm({
|
||||||
|
from,
|
||||||
|
style,
|
||||||
mint,
|
mint,
|
||||||
setMint,
|
setMint,
|
||||||
amount,
|
amount,
|
||||||
setAmount,
|
setAmount,
|
||||||
}: {
|
}: {
|
||||||
|
from: boolean;
|
||||||
|
style?: any;
|
||||||
mint: PublicKey;
|
mint: PublicKey;
|
||||||
setMint: (m: PublicKey) => void;
|
setMint: (m: PublicKey) => void;
|
||||||
amount: number;
|
amount: number;
|
||||||
setAmount: (a: number) => void;
|
setAmount: (a: number) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
const [showTokenDialog, setShowTokenDialog] = useState(false);
|
const [showTokenDialog, setShowTokenDialog] = useState(false);
|
||||||
const tokenAccount = useOwnedTokenAccount(mint);
|
const tokenAccount = useOwnedTokenAccount(mint);
|
||||||
const mintAccount = useMint(mint);
|
const mintAccount = useMint(mint);
|
||||||
|
|
||||||
|
const balance =
|
||||||
|
tokenAccount &&
|
||||||
|
mintAccount &&
|
||||||
|
tokenAccount.account.amount.toNumber() / 10 ** mintAccount.decimals;
|
||||||
|
|
||||||
|
const formattedAmount =
|
||||||
|
mintAccount && amount
|
||||||
|
? amount.toLocaleString("fullwide", {
|
||||||
|
maximumFractionDigits: mintAccount.decimals,
|
||||||
|
useGrouping: false,
|
||||||
|
})
|
||||||
|
: amount;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper elevation={0} variant="outlined" style={{ borderRadius: "10px" }}>
|
<div className={styles.swapTokenFormContainer} style={style}>
|
||||||
<div
|
<div className={styles.swapTokenSelectorContainer}>
|
||||||
style={{
|
|
||||||
height: "50px",
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TokenButton mint={mint} onClick={() => setShowTokenDialog(true)} />
|
<TokenButton mint={mint} onClick={() => setShowTokenDialog(true)} />
|
||||||
<TextField
|
<Typography color="textSecondary" className={styles.balanceContainer}>
|
||||||
type="number"
|
|
||||||
value={amount}
|
|
||||||
onChange={(e) => setAmount(parseFloat(e.target.value))}
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
flexDirection: "column",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style={{ marginLeft: "10px", height: "30px" }}>
|
|
||||||
<Typography color="textSecondary" style={{ fontSize: "14px" }}>
|
|
||||||
{tokenAccount && mintAccount
|
{tokenAccount && mintAccount
|
||||||
? `Balance: ${(
|
? `Balance: ${balance?.toFixed(mintAccount.decimals)}`
|
||||||
tokenAccount.account.amount.toNumber() /
|
|
||||||
10 ** mintAccount.decimals
|
|
||||||
).toFixed(mintAccount.decimals)}`
|
|
||||||
: `-`}
|
: `-`}
|
||||||
|
{from && !!balance ? (
|
||||||
|
<span
|
||||||
|
className={styles.maxButton}
|
||||||
|
onClick={() => setAmount(balance)}
|
||||||
|
>
|
||||||
|
MAX
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
<TextField
|
||||||
|
type="number"
|
||||||
|
value={formattedAmount}
|
||||||
|
onChange={(e) => setAmount(parseFloat(e.target.value))}
|
||||||
|
InputProps={{
|
||||||
|
disableUnderline: true,
|
||||||
|
classes: {
|
||||||
|
root: styles.amountInput,
|
||||||
|
input: styles.input,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<TokenDialog
|
<TokenDialog
|
||||||
setMint={setMint}
|
setMint={setMint}
|
||||||
open={showTokenDialog}
|
open={showTokenDialog}
|
||||||
onClose={() => setShowTokenDialog(false)}
|
onClose={() => setShowTokenDialog(false)}
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,12 +260,14 @@ function TokenButton({
|
||||||
mint: PublicKey;
|
mint: PublicKey;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
const styles = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={onClick} style={{ minWidth: "116px" }}>
|
<div onClick={onClick} className={styles.tokenButton}>
|
||||||
<TokenIcon mint={mint} style={{ width: "25px", borderRadius: "13px" }} />
|
<TokenIcon mint={mint} style={{ width: "30px" }} />
|
||||||
<TokenName mint={mint} />
|
<TokenName mint={mint} style={{ fontSize: 14, fontWeight: 700 }} />
|
||||||
<ExpandMore />
|
<ExpandMore />
|
||||||
</Button>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,11 +291,13 @@ export function TokenIcon({ mint, style }: { mint: PublicKey; style: any }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function TokenName({ mint }: { mint: PublicKey }) {
|
function TokenName({ mint, style }: { mint: PublicKey; style: any }) {
|
||||||
const tokenMap = useTokenMap();
|
const tokenMap = useTokenMap();
|
||||||
let tokenInfo = tokenMap.get(mint.toString());
|
let tokenInfo = tokenMap.get(mint.toString());
|
||||||
return (
|
return (
|
||||||
<Typography style={{ marginLeft: "5px" }}>{tokenInfo?.symbol}</Typography>
|
<Typography style={{ marginLeft: "10px", marginRight: "5px", ...style }}>
|
||||||
|
{tokenInfo?.symbol}
|
||||||
|
</Typography>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,20 +17,25 @@ import {
|
||||||
import { TokenIcon } from "./Swap";
|
import { TokenIcon } from "./Swap";
|
||||||
import { useSwappableTokens } from "../context/TokenList";
|
import { useSwappableTokens } from "../context/TokenList";
|
||||||
|
|
||||||
const useStyles = makeStyles(() => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
dialogContent: {
|
dialogContent: {
|
||||||
paddingTop: 0,
|
padding: 0,
|
||||||
paddingBottom: 0,
|
|
||||||
},
|
},
|
||||||
textField: {
|
textField: {
|
||||||
width: "100%",
|
|
||||||
border: "solid 1pt #ccc",
|
|
||||||
borderRadius: "10px",
|
|
||||||
marginBottom: "8px",
|
marginBottom: "8px",
|
||||||
},
|
},
|
||||||
tab: {
|
tab: {
|
||||||
minWidth: "134px",
|
minWidth: "134px",
|
||||||
},
|
},
|
||||||
|
tabSelected: {
|
||||||
|
color: theme.palette.primary.contrastText,
|
||||||
|
fontWeight: 700,
|
||||||
|
backgroundColor: theme.palette.primary.main,
|
||||||
|
borderRadius: "10px",
|
||||||
|
},
|
||||||
|
tabIndicator: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function TokenDialog({
|
export default function TokenDialog({
|
||||||
|
@ -83,11 +88,9 @@ export default function TokenDialog({
|
||||||
className={styles.textField}
|
className={styles.textField}
|
||||||
placeholder={"Search name"}
|
placeholder={"Search name"}
|
||||||
value={tokenFilter}
|
value={tokenFilter}
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
onChange={(e) => setTokenFilter(e.target.value)}
|
onChange={(e) => setTokenFilter(e.target.value)}
|
||||||
InputProps={{
|
|
||||||
disableUnderline: true,
|
|
||||||
style: { padding: "10px" },
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent className={styles.dialogContent} dividers={true}>
|
<DialogContent className={styles.dialogContent} dividers={true}>
|
||||||
|
@ -105,10 +108,31 @@ export default function TokenDialog({
|
||||||
</List>
|
</List>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Tabs value={tabSelection} onChange={(e, v) => setTabSelection(v)}>
|
<Tabs
|
||||||
<Tab value={0} className={styles.tab} label="Main" />
|
value={tabSelection}
|
||||||
<Tab value={1} className={styles.tab} label="Wormhole" />
|
onChange={(e, v) => setTabSelection(v)}
|
||||||
<Tab value={2} className={styles.tab} label="Sollet" />
|
classes={{
|
||||||
|
indicator: styles.tabIndicator,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tab
|
||||||
|
value={0}
|
||||||
|
className={styles.tab}
|
||||||
|
classes={{ selected: styles.tabSelected }}
|
||||||
|
label="Main"
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
value={1}
|
||||||
|
className={styles.tab}
|
||||||
|
classes={{ selected: styles.tabSelected }}
|
||||||
|
label="Wormhole"
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
value={2}
|
||||||
|
className={styles.tab}
|
||||||
|
classes={{ selected: styles.tabSelected }}
|
||||||
|
label="Sollet"
|
||||||
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -124,7 +148,11 @@ function TokenListItem({
|
||||||
}) {
|
}) {
|
||||||
const mint = new PublicKey(tokenInfo.address);
|
const mint = new PublicKey(tokenInfo.address);
|
||||||
return (
|
return (
|
||||||
<ListItem button onClick={() => onClick(mint)}>
|
<ListItem
|
||||||
|
button
|
||||||
|
onClick={() => onClick(mint)}
|
||||||
|
style={{ padding: "10px 20px" }}
|
||||||
|
>
|
||||||
<TokenIcon mint={mint} style={{ width: "30px", borderRadius: "15px" }} />
|
<TokenIcon mint={mint} style={{ width: "30px", borderRadius: "15px" }} />
|
||||||
<TokenName tokenInfo={tokenInfo} />
|
<TokenName tokenInfo={tokenInfo} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
@import url("https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;700&display=swap");
|
|
@ -1,3 +1,5 @@
|
||||||
|
import "@fontsource/roboto";
|
||||||
|
import "./index.css";
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from "@solana/web3.js";
|
||||||
import { TokenListContainer } from "@solana/spl-token-registry";
|
import { TokenListContainer } from "@solana/spl-token-registry";
|
||||||
|
@ -8,6 +10,11 @@ import { DexContextProvider } from "./context/Dex";
|
||||||
import { TokenListContextProvider } from "./context/TokenList";
|
import { TokenListContextProvider } from "./context/TokenList";
|
||||||
import { TokenContextProvider } from "./context/Token";
|
import { TokenContextProvider } from "./context/Token";
|
||||||
import SwapCard from "./components/Swap";
|
import SwapCard from "./components/Swap";
|
||||||
|
import {
|
||||||
|
createMuiTheme,
|
||||||
|
ThemeOptions,
|
||||||
|
ThemeProvider,
|
||||||
|
} from "@material-ui/core/styles";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A`Swap` component that can be embedded into applications. To use,
|
* A`Swap` component that can be embedded into applications. To use,
|
||||||
|
@ -26,7 +33,10 @@ import SwapCard from "./components/Swap";
|
||||||
*/
|
*/
|
||||||
export function Swap(props: SwapProps): ReactElement {
|
export function Swap(props: SwapProps): ReactElement {
|
||||||
const {
|
const {
|
||||||
style,
|
containerStyle,
|
||||||
|
contentStyle,
|
||||||
|
swapTokenContainerStyle,
|
||||||
|
materialTheme,
|
||||||
provider,
|
provider,
|
||||||
tokenList,
|
tokenList,
|
||||||
fromMint,
|
fromMint,
|
||||||
|
@ -36,7 +46,25 @@ export function Swap(props: SwapProps): ReactElement {
|
||||||
referral,
|
referral,
|
||||||
} = props;
|
} = props;
|
||||||
const swapClient = new SwapClient(provider, tokenList);
|
const swapClient = new SwapClient(provider, tokenList);
|
||||||
|
const theme = createMuiTheme(
|
||||||
|
materialTheme || {
|
||||||
|
palette: {
|
||||||
|
primary: {
|
||||||
|
main: "#2196F3",
|
||||||
|
contrastText: "#FFFFFF",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
main: "#E0E0E0",
|
||||||
|
light: "#595959",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
main: "#ff6b6b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
<TokenListContextProvider tokenList={tokenList}>
|
<TokenListContextProvider tokenList={tokenList}>
|
||||||
<TokenContextProvider provider={provider}>
|
<TokenContextProvider provider={provider}>
|
||||||
<DexContextProvider swapClient={swapClient}>
|
<DexContextProvider swapClient={swapClient}>
|
||||||
|
@ -47,11 +75,16 @@ export function Swap(props: SwapProps): ReactElement {
|
||||||
toAmount={toAmount}
|
toAmount={toAmount}
|
||||||
referral={referral}
|
referral={referral}
|
||||||
>
|
>
|
||||||
<SwapCard style={style} />
|
<SwapCard
|
||||||
|
containerStyle={containerStyle}
|
||||||
|
contentStyle={contentStyle}
|
||||||
|
swapTokenContainerStyle={swapTokenContainerStyle}
|
||||||
|
/>
|
||||||
</SwapContextProvider>
|
</SwapContextProvider>
|
||||||
</DexContextProvider>
|
</DexContextProvider>
|
||||||
</TokenContextProvider>
|
</TokenContextProvider>
|
||||||
</TokenListContextProvider>
|
</TokenListContextProvider>
|
||||||
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,9 +134,24 @@ export type SwapProps = {
|
||||||
toAmount?: number;
|
toAmount?: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Style properties to pass through to the component.
|
* Provide custom material-ui theme.
|
||||||
*/
|
*/
|
||||||
style?: any;
|
materialTheme?: ThemeOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styling properties for the main container.
|
||||||
|
*/
|
||||||
|
containerStyle?: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styling properties for the content container.
|
||||||
|
*/
|
||||||
|
contentStyle?: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styling properties for the from and to token containers.
|
||||||
|
*/
|
||||||
|
swapTokenContainerStyle?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Swap;
|
export default Swap;
|
||||||
|
|
|
@ -1255,6 +1255,11 @@
|
||||||
minimatch "^3.0.4"
|
minimatch "^3.0.4"
|
||||||
strip-json-comments "^3.1.1"
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
|
"@fontsource/roboto@^4.3.0":
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@fontsource/roboto/-/roboto-4.3.0.tgz#00f1cceca43eff85bb0e1d424311751ee41f6aa6"
|
||||||
|
integrity sha512-WeFWCWYutLWyEtRmBhn+bLbW4/km0l+HhTpR8wWDxJLiGiMOhVLO/Z0q5w6l20ZOkWnf6Z1rN3o3W2HjvYN6Rg==
|
||||||
|
|
||||||
"@hapi/address@2.x.x":
|
"@hapi/address@2.x.x":
|
||||||
version "2.1.4"
|
version "2.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
|
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
|
||||||
|
|
Loading…
Reference in New Issue