Skip to content

Commit 4d59b14

Browse files
committed
wip
1 parent 692dbd3 commit 4d59b14

File tree

13 files changed

+861
-6
lines changed

13 files changed

+861
-6
lines changed

src/components/Icon.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import {
109109
ShowIcon,
110110
SocialLoginIcon,
111111
SocialXIcon,
112+
SolIcon,
112113
SparklesIcon,
113114
SpeechBubbleIcon,
114115
StarIcon,
@@ -288,6 +289,7 @@ export enum IconName {
288289
Withdraw = 'Withdraw',
289290
XCircle = 'XCircle',
290291
SocialX = 'SocialX',
292+
Sol = 'Sol',
291293
}
292294

293295
const icons = {
@@ -430,6 +432,7 @@ const icons = {
430432
[IconName.Withdraw]: WithdrawIcon,
431433
[IconName.XCircle]: XCircleIcon,
432434
[IconName.SocialX]: SocialXIcon,
435+
[IconName.Sol]: SolIcon,
433436
} as Record<IconName, ElementType | undefined>;
434437

435438
// we load reward-start async because it's gigantic for some reason

src/constants/styles/colors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ type BaseColors = {
3232
green: string;
3333
red: string;
3434

35+
redFaded: string;
36+
greenFaded: string;
3537
whiteFaded: string;
3638
};
3739

src/icons/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export { default as WithdrawIcon } from './withdraw.svg';
126126
export { default as XCircleIcon } from './x-circle.svg';
127127

128128
// Wallets
129+
export { default as SolIcon } from './sol.svg';
129130
export { default as WalletIcon } from './wallet.svg';
130131
export { default as AppleIcon } from './wallets/apple.svg';
131132
export { default as BitkeepIcon } from './wallets/bitkeep.svg';

src/icons/sol.svg

Lines changed: 19 additions & 0 deletions
Loading

src/pages/spot/QuickButtons.tsx

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { useEffect, useState } from 'react';
2+
3+
import { NumericFormat } from 'react-number-format';
4+
import styled, { css } from 'styled-components';
5+
6+
import { Icon, IconName } from '@/components/Icon';
7+
8+
type QuickButtonProps = {
9+
options: string[];
10+
onSelect?: (value: string) => void;
11+
onOptionsEdit?: (options: string[]) => void;
12+
currentValue?: string;
13+
disabled?: boolean;
14+
};
15+
16+
export const QuickButtons = ({
17+
options,
18+
onSelect,
19+
onOptionsEdit,
20+
currentValue,
21+
disabled,
22+
}: QuickButtonProps) => {
23+
const [isEditing, setIsEditing] = useState(false);
24+
const [editValues, setEditValues] = useState<string[]>(options);
25+
26+
const handleEdit = () => {
27+
setEditValues(options.map((o) => o.toString()));
28+
setIsEditing(true);
29+
};
30+
31+
const handleConfirmEdit = () => {
32+
const uniqueValues = editValues.map((value, i) => {
33+
const isUnique = !options.includes(value) || options.indexOf(value) === i;
34+
const isValid = !Number.isNaN(parseFloat(value));
35+
36+
if (isUnique && isValid) {
37+
return value;
38+
}
39+
40+
return options[i]!;
41+
});
42+
43+
onOptionsEdit?.(uniqueValues);
44+
setIsEditing(false);
45+
};
46+
47+
useEffect(() => {
48+
setEditValues(options);
49+
}, [options]);
50+
51+
const handleInputChange = (index: number, value: string) => {
52+
const newEditValues = [...editValues];
53+
newEditValues[index] = value;
54+
setEditValues(newEditValues);
55+
};
56+
57+
return (
58+
<$QuickButtonsContainer>
59+
{options.map((option, i) =>
60+
isEditing ? (
61+
<$InputQuickButtonContainer key={option}>
62+
<$QuickButtonInput
63+
allowNegative={false}
64+
decimalScale={2}
65+
disabled={disabled}
66+
value={editValues[i]}
67+
onChange={(e) => handleInputChange(i, e.target.value)}
68+
/>
69+
</$InputQuickButtonContainer>
70+
) : (
71+
<$QuickButtonContainer
72+
key={option}
73+
onClick={() => onSelect?.(option)}
74+
type="button"
75+
isSelected={currentValue === option}
76+
disabled={disabled}
77+
>
78+
<span tw="truncate">{option}</span>
79+
</$QuickButtonContainer>
80+
)
81+
)}
82+
<$QuickButtonContainer
83+
aria-label={isEditing ? 'Confirm' : 'Edit'}
84+
type="button"
85+
onClick={isEditing ? handleConfirmEdit : handleEdit}
86+
disabled={disabled}
87+
>
88+
<Icon iconName={isEditing ? IconName.Check : IconName.Pencil2} size="1rem" />
89+
</$QuickButtonContainer>
90+
</$QuickButtonsContainer>
91+
);
92+
};
93+
94+
const $QuickButtonInput = styled(NumericFormat)`
95+
outline: 0;
96+
border: 0;
97+
min-width: 0;
98+
background-color: transparent;
99+
text-align: center;
100+
`;
101+
102+
const $QuickButtonsContainer = styled.div`
103+
display: flex;
104+
flex-direction: row;
105+
align-items: center;
106+
height: 2.3125rem;
107+
gap: 0.75rem;
108+
109+
& > *:not(:last-child) {
110+
flex: 1;
111+
}
112+
113+
& > *:last-child {
114+
aspect-ratio: 1;
115+
flex-shrink: 0;
116+
}
117+
`;
118+
119+
const QuickButtonContainerStyles = css`
120+
display: inline-flex;
121+
flex-direction: row;
122+
justify-content: center;
123+
align-items: center;
124+
height: 100%;
125+
justify-content: center;
126+
border-radius: 0.5rem;
127+
border: 1px solid var(--color-layer-4);
128+
padding: 0 0.5rem;
129+
font: var(--font-base-medium);
130+
color: var(--color-text);
131+
min-width: 0;
132+
133+
@media (prefers-reduced-motion: no-preference) {
134+
transition: 0.3s var(--ease-out-expo);
135+
}
136+
`;
137+
138+
const $InputQuickButtonContainer = styled.div`
139+
${QuickButtonContainerStyles}
140+
141+
background-color: var(--color-layer-3);
142+
143+
&:focus-within {
144+
background-color: var(--color-layer-4);
145+
}
146+
`;
147+
148+
const $QuickButtonContainer = styled.button<{ isSelected?: boolean }>`
149+
${QuickButtonContainerStyles}
150+
151+
${({ isSelected }) =>
152+
isSelected &&
153+
css`
154+
background-color: var(--color-layer-4);
155+
`}
156+
157+
@media (hover: hover) {
158+
&:hover {
159+
background-color: var(--color-layer-3);
160+
}
161+
}
162+
`;

src/pages/spot/Spot.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { SpotTvChart } from '@/views/charts/TradingView/SpotTvChart';
1111
import { useAppSelector } from '@/state/appTypes';
1212
import { getSelectedTradeLayout } from '@/state/layoutSelectors';
1313

14+
import { SpotTradeForm } from './SpotTradeForm';
15+
1416
const SpotPage = () => {
1517
const { symbol } = useParams<{ symbol: string }>();
1618
const tradeLayout = useAppSelector(getSelectedTradeLayout);
@@ -21,8 +23,8 @@ const SpotPage = () => {
2123
<div tw="p-1">Spot Market Selector (Coming Soon)</div>
2224
</header>
2325

24-
<$GridSection gridArea="Side">
25-
<div tw="p-1">Spot Side Panel (Coming Soon)</div>
26+
<$GridSection gridArea="Side" tw="p-1">
27+
<SpotTradeForm />
2628
</$GridSection>
2729

2830
<$GridSection gridArea="Inner">
@@ -45,15 +47,15 @@ const $SpotLayout = styled.article<{
4547
--layout-default:
4648
'Top Top' auto
4749
'Inner Side' minmax(0, 1fr)
48-
'Horizontal Side' 200px
49-
/ 1fr var(--sidebar-width);
50+
'Horizontal Side' 300px
51+
/ 1fr 400px;
5052
5153
/* prettier-ignore */
5254
--layout-default-desktopMedium:
5355
'Top Side' auto
5456
'Inner Side' minmax(0, 1fr)
55-
'Horizontal Side' 200px
56-
/ 1fr var(--sidebar-width);
57+
'Horizontal Side' 300px
58+
/ 1fr 400px;
5759
5860
// Props/defaults
5961
--layout: var(--layout-default);

src/pages/spot/SpotFormInput.tsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import React, { forwardRef, useId } from 'react';
2+
3+
import styled from 'styled-components';
4+
5+
import { AlertType } from '@/constants/alerts';
6+
7+
import { layoutMixins } from '@/styles/layoutMixins';
8+
9+
import { AlertMessage } from '@/components/AlertMessage';
10+
import { Input, InputProps } from '@/components/Input';
11+
12+
type StyleProps = {
13+
className?: string;
14+
};
15+
16+
type ElementProps = {
17+
label?: React.ReactNode;
18+
slotRight?: React.ReactNode;
19+
slotBottom?: React.ReactNode;
20+
validationConfig?: {
21+
type: AlertType;
22+
message: string;
23+
};
24+
};
25+
26+
export type SpotFormInputProps = ElementProps & StyleProps & InputProps;
27+
28+
export const SpotFormInput = forwardRef<HTMLInputElement, SpotFormInputProps>(
29+
({ label, slotRight, slotBottom, className, validationConfig, ...inputProps }, ref) => {
30+
const id = useId();
31+
32+
return (
33+
<$SpotFormInputContainer>
34+
<$Container className={className}>
35+
<$InputContainer>
36+
{label && <$Label htmlFor={id}>{label}</$Label>}
37+
<$Input ref={ref} {...inputProps} id={id} />
38+
{slotBottom && <$SlotBottomContainer>{slotBottom}</$SlotBottomContainer>}
39+
</$InputContainer>
40+
{slotRight}
41+
</$Container>
42+
{validationConfig && (
43+
<AlertMessage type={validationConfig.type}>{validationConfig.message}</AlertMessage>
44+
)}
45+
</$SpotFormInputContainer>
46+
);
47+
}
48+
);
49+
50+
const $SpotFormInputContainer = styled.div`
51+
display: flex;
52+
flex-direction: column;
53+
gap: 0.75rem;
54+
`;
55+
56+
const $Container = styled.div`
57+
display: flex;
58+
align-items: center;
59+
background-color: var(--color-layer-3);
60+
padding: 0.75rem;
61+
border-radius: 0.5rem;
62+
border: 1px solid var(--color-layer-4);
63+
gap: 0.5rem;
64+
`;
65+
66+
const $Label = styled.label`
67+
${layoutMixins.textTruncate}
68+
69+
font: var(--font-small-medium);
70+
color: var(--color-text-0);
71+
`;
72+
73+
const $InputContainer = styled.div`
74+
flex: 1;
75+
display: flex;
76+
gap: 0.25rem;
77+
flex-direction: column;
78+
min-width: 0;
79+
`;
80+
81+
const $SlotBottomContainer = styled.div`
82+
${layoutMixins.textTruncate}
83+
font: var(--font-mini-medium);
84+
color: var(--color-text-0);
85+
`;
86+
87+
const $Input = styled(Input)`
88+
--input-font: var(--font-medium-medium);
89+
`;

0 commit comments

Comments
 (0)