Redesign and expose styling and theming (#25)

This commit is contained in:
philippe-ftx 2021-05-26 18:47:53 +02:00 committed by GitHub
parent 99a49ab334
commit 004938274b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 342 additions and 177 deletions

View File

@ -6,6 +6,7 @@
"homepage": "https://github.com/project-serum/swap-ui",
"license": "Apache-2.0",
"dependencies": {
"@fontsource/roboto": "^4.3.0",
"@project-serum/serum": "^0.13.34",
"@project-serum/swap": "^0.1.0-alpha.14",
"@solana/spl-token": "^0.1.4"

View File

@ -13,24 +13,18 @@ import { useSwapContext, useSwapFair } from "../context/Swap";
import { useMint } from "../context/Token";
import { useRoute, useMarketName, useBbo } from "../context/Dex";
const useStyles = makeStyles((theme) => ({
const useStyles = makeStyles(() => ({
infoLabel: {
marginTop: "10px",
marginBottom: "10px",
marginTop: "20px",
marginBottom: "20px",
display: "flex",
justifyContent: "space-between",
marginLeft: "5px",
marginRight: "5px",
},
fairPriceLabel: {
marginRight: "10px",
display: "flex",
justifyContent: "center",
flexDirection: "column",
color: theme.palette.text.secondary,
justifyContent: "flex-end",
alignItems: "center",
},
infoButton: {
marginLeft: "5px",
padding: 0,
fontSize: "14px",
},
}));
@ -47,17 +41,14 @@ export function InfoLabel() {
return (
<div className={styles.infoLabel}>
<Typography color="textSecondary"></Typography>
<div style={{ display: "flex" }}>
<div className={styles.fairPriceLabel}>
{fair !== undefined && toTokenInfo && fromTokenInfo
? `1 ${toTokenInfo.symbol} = ${fair.toFixed(
fromMintInfo?.decimals
)} ${fromTokenInfo.symbol}`
: `-`}
</div>
<InfoButton />
</div>
<Typography color="textSecondary" style={{ fontSize: "14px" }}>
{fair !== undefined && toTokenInfo && fromTokenInfo
? `1 ${toTokenInfo.symbol} = ${fair.toFixed(
fromMintInfo?.decimals
)} ${fromTokenInfo.symbol}`
: `-`}
</Typography>
<InfoButton />
</div>
);
}
@ -74,7 +65,7 @@ function InfoButton() {
{...bindTrigger(popupState)}
className={styles.infoButton}
>
<Info />
<Info fontSize="small" />
</IconButton>
<Popover
{...bindPopover(popupState)}

View File

@ -28,8 +28,8 @@ import { useMint, useOwnedTokenAccount } from "../context/Token";
const useStyles = makeStyles((theme) => ({
table: {},
closeAccountSwitchLabel: {
color: theme.palette.text.secondary,
closeAccount: {
color: theme.palette.error.main,
},
}));
@ -40,6 +40,8 @@ export default function OpenOrdersDialog({
open: boolean;
onClose: () => void;
}) {
const styles = useStyles();
return (
<Dialog
maxWidth="lg"
@ -51,30 +53,24 @@ export default function OpenOrdersDialog({
},
}}
>
<div>
<div
<div
style={{
display: "flex",
justifyContent: "flex-end",
}}
>
<IconButton
onClick={onClose}
style={{
height: "24px",
display: "flex",
justifyContent: "space-between",
padding: 10,
}}
>
<IconButton
onClick={onClose}
style={{
padding: 0,
position: "absolute",
right: "8px",
top: "8px",
}}
>
<Close />
</IconButton>
</div>
<DialogContent style={{ paddingTop: 0 }}>
<OpenOrdersAccounts />
</DialogContent>
<Close />
</IconButton>
</div>
<DialogContent style={{ paddingTop: 0 }}>
<OpenOrdersAccounts />
</DialogContent>
</Dialog>
);
}
@ -126,6 +122,8 @@ function OpenOrdersRow({
market: PublicKey;
openOrders: Array<OpenOrders>;
}) {
const styles = useStyles();
const [ooAccount, setOoAccount] = useState(openOrders[0]);
const { swapClient } = useDexContext();
const marketClient = useMarket(market);
@ -235,9 +233,9 @@ function OpenOrdersRow({
</TableCell>
<TableCell align="center">
<Button
color="secondary"
disabled={closeDisabled}
onClick={closeOpenOrders}
className={styles.closeAccount}
>
Close
</Button>

View File

@ -11,7 +11,6 @@ import {
FormControlLabel,
FormGroup,
} from "@material-ui/core";
import { ToggleButton } from "@material-ui/lab";
import { SettingsOutlined as Settings } from "@material-ui/icons";
import PopupState, { bindTrigger, bindPopover } from "material-ui-popup-state";
import { useSwapContext, useSwapFair } from "../context/Swap";
@ -25,10 +24,27 @@ const useStyles = makeStyles((theme) => ({
table: {},
settingsButton: {
padding: 0,
color: theme.palette.primary.main,
},
closeAccountSwitchLabel: {
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() {
@ -56,7 +72,12 @@ export function SettingsButton() {
vertical: "top",
horizontal: "right",
}}
PaperProps={{ style: { borderRadius: "10px" } }}
PaperProps={{
style: {
borderRadius: "10px",
boxShadow: "0px 0px 30px 5px rgba(0,0,0,0.075)",
},
}}
>
<SettingsDetails />
</Popover>
@ -68,6 +89,8 @@ export function SettingsButton() {
}
function SettingsDetails() {
const styles = useStyles();
const { slippage, setSlippage, fairOverride, setFairOverride } =
useSwapContext();
const [showSettingsDialog, setShowSettingsDialog] = useState(false);
@ -80,12 +103,12 @@ function SettingsDetails() {
return (
<div style={{ padding: "15px", width: "305px" }}>
<Typography color="textSecondary" style={{ fontWeight: "bold" }}>
Settings
</Typography>
<div style={{ marginTop: "10px" }}>
<div>
<Typography color="textSecondary">Slippage tolerance</Typography>
<Typography style={{ fontWeight: "bold" }}>Settings</Typography>
<div>
<div style={{ marginTop: "10px" }}>
<Typography color="textSecondary" style={{ fontSize: "12px" }}>
Slippage tolerance
</Typography>
<TextField
type="number"
placeholder="Error tolerance percentage"
@ -101,8 +124,10 @@ function SettingsDetails() {
}}
/>
</div>
<div style={{ marginTop: "5px" }}>
<Typography color="textSecondary">Fair price</Typography>
<div style={{ marginTop: "10px" }}>
<Typography color="textSecondary" style={{ fontSize: "12px" }}>
Fair price
</Typography>
<div style={{ display: "flex" }}>
<TextField
type="number"
@ -118,9 +143,9 @@ function SettingsDetails() {
}}
disabled={fairOverride === null}
/>
<ToggleButton
value="bold"
selected={fairOverride === null}
<Button
component="div"
variant="contained"
onClick={() => {
if (fair === undefined) {
console.error("Fair is undefined");
@ -132,27 +157,22 @@ function SettingsDetails() {
setFairOverride(null);
}
}}
style={{
paddingTop: "3px",
paddingBottom: "3px",
paddingLeft: "5px",
paddingRight: "5px",
borderRadius: "20px",
}}
className={
fairOverride === null
? styles.fairAutoSelected
: styles.fairAuto
}
>
Auto
</ToggleButton>
</Button>
</div>
</div>
<div style={{ marginTop: "5px" }}>
<div style={{ margin: "10px 0px" }}>
<CloseNewAccountsSwitch />
</div>
<Button
style={{
width: "100%",
marginTop: "10px",
background: "#e0e0e0",
}}
variant="contained"
fullWidth
disabled={swapClient.program.provider.wallet.publicKey === null}
onClick={() => setShowSettingsDialog(true)}
>

View File

@ -5,11 +5,11 @@ import {
makeStyles,
Card,
Button,
Paper,
Typography,
TextField,
useTheme,
} from "@material-ui/core";
import { ExpandMore } from "@material-ui/icons";
import { ExpandMore, ImportExportRounded } from "@material-ui/icons";
import { useSwapContext, useSwapFair } from "../context/Swap";
import {
useDexContext,
@ -25,16 +25,12 @@ import TokenDialog from "./TokenDialog";
import { SettingsButton } from "./Settings";
import { InfoLabel } from "./Info";
const useStyles = makeStyles(() => ({
const useStyles = makeStyles((theme) => ({
card: {
width: "450px",
borderRadius: "10px",
border: "solid 1pt #e0e0e0",
},
cardContent: {
marginLeft: "6px",
marginRight: "6px",
marginBottom: "6px",
borderRadius: "16px",
boxShadow: "0px 0px 30px 5px rgba(0,0,0,0.075)",
padding: "16px",
},
tab: {
width: "50%",
@ -44,30 +40,78 @@ const useStyles = makeStyles(() => ({
},
swapButton: {
width: "100%",
borderRadius: "15px",
borderRadius: "10px",
backgroundColor: theme.palette.primary.main,
color: theme.palette.primary.contrastText,
fontSize: 16,
fontWeight: 700,
padding: "10px",
},
swapToFromButton: {
display: "block",
marginLeft: "auto",
marginRight: "auto",
margin: "10px auto 10px 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();
return (
<div style={style}>
<Card className={styles.card}>
<SwapHeader />
<div className={styles.cardContent}>
<SwapFromForm />
<ArrowButton />
<SwapToForm />
<InfoLabel />
<SwapButton />
</div>
</Card>
</div>
<Card className={styles.card} style={containerStyle}>
<SwapHeader />
<div style={contentStyle}>
<SwapFromForm style={swapTokenContainerStyle} />
<ArrowButton />
<SwapToForm style={swapTokenContainerStyle} />
<InfoLabel />
<SwapButton />
</div>
</Card>
);
}
@ -77,18 +121,17 @@ function SwapHeader() {
style={{
display: "flex",
justifyContent: "space-between",
margin: "8px",
marginBottom: "20px",
}}
>
<Typography
style={{
fontWeight: "bold",
display: "flex",
justifyContent: "center",
flexDirection: "column",
fontSize: 18,
fontWeight: 700,
fontFamily: "Roboto Condensed",
}}
>
Swap
SWAP
</Typography>
<SettingsButton />
</div>
@ -97,18 +140,24 @@ function SwapHeader() {
export function ArrowButton() {
const styles = useStyles();
const theme = useTheme();
const { swapToFromMints } = useSwapContext();
return (
<Button className={styles.swapToFromButton} onClick={swapToFromMints}>
</Button>
<ImportExportRounded
className={styles.swapToFromButton}
fontSize="large"
htmlColor={theme.palette.primary.main}
onClick={swapToFromMints}
/>
);
}
function SwapFromForm() {
function SwapFromForm({ style }: { style?: any }) {
const { fromMint, setFromMint, fromAmount, setFromAmount } = useSwapContext();
return (
<SwapTokenForm
from
style={style}
mint={fromMint}
setMint={setFromMint}
amount={fromAmount}
@ -117,10 +166,12 @@ function SwapFromForm() {
);
}
function SwapToForm() {
function SwapToForm({ style }: { style?: any }) {
const { toMint, setToMint, toAmount, setToAmount } = useSwapContext();
return (
<SwapTokenForm
from={false}
style={style}
mint={toMint}
setMint={setToMint}
amount={toAmount}
@ -130,57 +181,75 @@ function SwapToForm() {
}
function SwapTokenForm({
from,
style,
mint,
setMint,
amount,
setAmount,
}: {
from: boolean;
style?: any;
mint: PublicKey;
setMint: (m: PublicKey) => void;
amount: number;
setAmount: (a: number) => void;
}) {
const styles = useStyles();
const [showTokenDialog, setShowTokenDialog] = useState(false);
const tokenAccount = useOwnedTokenAccount(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 (
<Paper elevation={0} variant="outlined" style={{ borderRadius: "10px" }}>
<div
style={{
height: "50px",
display: "flex",
justifyContent: "space-between",
}}
>
<div className={styles.swapTokenFormContainer} style={style}>
<div className={styles.swapTokenSelectorContainer}>
<TokenButton mint={mint} onClick={() => setShowTokenDialog(true)} />
<TextField
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" }}>
<Typography color="textSecondary" className={styles.balanceContainer}>
{tokenAccount && mintAccount
? `Balance: ${(
tokenAccount.account.amount.toNumber() /
10 ** mintAccount.decimals
).toFixed(mintAccount.decimals)}`
? `Balance: ${balance?.toFixed(mintAccount.decimals)}`
: `-`}
{from && !!balance ? (
<span
className={styles.maxButton}
onClick={() => setAmount(balance)}
>
MAX
</span>
) : null}
</Typography>
</div>
<TextField
type="number"
value={formattedAmount}
onChange={(e) => setAmount(parseFloat(e.target.value))}
InputProps={{
disableUnderline: true,
classes: {
root: styles.amountInput,
input: styles.input,
},
}}
/>
<TokenDialog
setMint={setMint}
open={showTokenDialog}
onClose={() => setShowTokenDialog(false)}
/>
</Paper>
</div>
);
}
@ -191,12 +260,14 @@ function TokenButton({
mint: PublicKey;
onClick: () => void;
}) {
const styles = useStyles();
return (
<Button onClick={onClick} style={{ minWidth: "116px" }}>
<TokenIcon mint={mint} style={{ width: "25px", borderRadius: "13px" }} />
<TokenName mint={mint} />
<div onClick={onClick} className={styles.tokenButton}>
<TokenIcon mint={mint} style={{ width: "30px" }} />
<TokenName mint={mint} style={{ fontSize: 14, fontWeight: 700 }} />
<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();
let tokenInfo = tokenMap.get(mint.toString());
return (
<Typography style={{ marginLeft: "5px" }}>{tokenInfo?.symbol}</Typography>
<Typography style={{ marginLeft: "10px", marginRight: "5px", ...style }}>
{tokenInfo?.symbol}
</Typography>
);
}

View File

@ -17,20 +17,25 @@ import {
import { TokenIcon } from "./Swap";
import { useSwappableTokens } from "../context/TokenList";
const useStyles = makeStyles(() => ({
const useStyles = makeStyles((theme) => ({
dialogContent: {
paddingTop: 0,
paddingBottom: 0,
padding: 0,
},
textField: {
width: "100%",
border: "solid 1pt #ccc",
borderRadius: "10px",
marginBottom: "8px",
},
tab: {
minWidth: "134px",
},
tabSelected: {
color: theme.palette.primary.contrastText,
fontWeight: 700,
backgroundColor: theme.palette.primary.main,
borderRadius: "10px",
},
tabIndicator: {
opacity: 0,
},
}));
export default function TokenDialog({
@ -83,11 +88,9 @@ export default function TokenDialog({
className={styles.textField}
placeholder={"Search name"}
value={tokenFilter}
fullWidth
variant="outlined"
onChange={(e) => setTokenFilter(e.target.value)}
InputProps={{
disableUnderline: true,
style: { padding: "10px" },
}}
/>
</DialogTitle>
<DialogContent className={styles.dialogContent} dividers={true}>
@ -105,10 +108,31 @@ export default function TokenDialog({
</List>
</DialogContent>
<DialogActions>
<Tabs value={tabSelection} onChange={(e, v) => setTabSelection(v)}>
<Tab value={0} className={styles.tab} label="Main" />
<Tab value={1} className={styles.tab} label="Wormhole" />
<Tab value={2} className={styles.tab} label="Sollet" />
<Tabs
value={tabSelection}
onChange={(e, v) => setTabSelection(v)}
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>
</DialogActions>
</Dialog>
@ -124,7 +148,11 @@ function TokenListItem({
}) {
const mint = new PublicKey(tokenInfo.address);
return (
<ListItem button onClick={() => onClick(mint)}>
<ListItem
button
onClick={() => onClick(mint)}
style={{ padding: "10px 20px" }}
>
<TokenIcon mint={mint} style={{ width: "30px", borderRadius: "15px" }} />
<TokenName tokenInfo={tokenInfo} />
</ListItem>

1
src/index.css Normal file
View File

@ -0,0 +1 @@
@import url("https://fonts.googleapis.com/css2?family=Roboto+Condensed:wght@300;400;700&display=swap");

View File

@ -1,3 +1,5 @@
import "@fontsource/roboto";
import "./index.css";
import { ReactElement } from "react";
import { PublicKey } from "@solana/web3.js";
import { TokenListContainer } from "@solana/spl-token-registry";
@ -8,6 +10,11 @@ import { DexContextProvider } from "./context/Dex";
import { TokenListContextProvider } from "./context/TokenList";
import { TokenContextProvider } from "./context/Token";
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,
@ -26,7 +33,10 @@ import SwapCard from "./components/Swap";
*/
export function Swap(props: SwapProps): ReactElement {
const {
style,
containerStyle,
contentStyle,
swapTokenContainerStyle,
materialTheme,
provider,
tokenList,
fromMint,
@ -36,22 +46,45 @@ export function Swap(props: SwapProps): ReactElement {
referral,
} = props;
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 (
<TokenListContextProvider tokenList={tokenList}>
<TokenContextProvider provider={provider}>
<DexContextProvider swapClient={swapClient}>
<SwapContextProvider
fromMint={fromMint}
toMint={toMint}
fromAmount={fromAmount}
toAmount={toAmount}
referral={referral}
>
<SwapCard style={style} />
</SwapContextProvider>
</DexContextProvider>
</TokenContextProvider>
</TokenListContextProvider>
<ThemeProvider theme={theme}>
<TokenListContextProvider tokenList={tokenList}>
<TokenContextProvider provider={provider}>
<DexContextProvider swapClient={swapClient}>
<SwapContextProvider
fromMint={fromMint}
toMint={toMint}
fromAmount={fromAmount}
toAmount={toAmount}
referral={referral}
>
<SwapCard
containerStyle={containerStyle}
contentStyle={contentStyle}
swapTokenContainerStyle={swapTokenContainerStyle}
/>
</SwapContextProvider>
</DexContextProvider>
</TokenContextProvider>
</TokenListContextProvider>
</ThemeProvider>
);
}
@ -101,9 +134,24 @@ export type SwapProps = {
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;

View File

@ -1255,6 +1255,11 @@
minimatch "^3.0.4"
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":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"