Skip to content

Commit f728383

Browse files
committed
Add first UI
1 parent c7d6f17 commit f728383

File tree

8 files changed

+415
-0
lines changed

8 files changed

+415
-0
lines changed

src/assets/svg/ic_send.svg

Lines changed: 4 additions & 0 deletions
Loading

src/assets/svg/kai_avatar.svg

Lines changed: 7 additions & 0 deletions
Loading

src/assets/svg/kai_avatar2.svg

Lines changed: 7 additions & 0 deletions
Loading

src/components/Kai/KaiContent.tsx

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { ChangeEvent, KeyboardEvent, useState } from 'react'
2+
3+
import { ReactComponent as KaiAvatar } from 'assets/svg/kai_avatar2.svg'
4+
5+
import {
6+
ActionButton,
7+
ActionPanel,
8+
ChatInput,
9+
ChatWrapper,
10+
Divider,
11+
KaiHeaderWrapper,
12+
Loader,
13+
LoadingWrapper,
14+
SendIcon,
15+
SubTextSpan,
16+
WelcomeText,
17+
} from './KaiStyledComponents'
18+
import { KaiAction, MAIN_MENU } from './actions'
19+
20+
const DEFAULT_LOADING_TEXT = 'KAI is checking the data ...'
21+
const DEFAULT_CHAT_PLACEHOLDER_TEXT = 'Ask me anything or select'
22+
23+
const KaiContent = () => {
24+
const [chatPlaceHolderText, _setChatPlaceHolderText] = useState(DEFAULT_CHAT_PLACEHOLDER_TEXT)
25+
const [loading, _setLoading] = useState(false)
26+
const [loadingText, _setLoadingText] = useState(DEFAULT_LOADING_TEXT)
27+
28+
const onSubmitChat = (text: string) => {
29+
console.log('Submitted chat: ' + text)
30+
}
31+
32+
return (
33+
<>
34+
<KaiHeader />
35+
<WelcomeText>GM! What can I do for you today? 👋</WelcomeText>
36+
<ActionPanel>
37+
{MAIN_MENU.map((action: KaiAction, index: number) => (
38+
<ActionButton key={index} width={action.space}>
39+
{action.title}
40+
</ActionButton>
41+
))}
42+
</ActionPanel>
43+
{loading && <KaiLoading loadingText={loadingText} />}
44+
<KaiChat disabled={loading} chatPlaceHolderText={chatPlaceHolderText} onSubmitChat={onSubmitChat} />
45+
</>
46+
)
47+
}
48+
49+
const KaiHeader = () => {
50+
return (
51+
<>
52+
<KaiHeaderWrapper>
53+
<KaiAvatar />
54+
<span>I&apos;m KAI</span>
55+
<SubTextSpan>Kyber Assistant Interface</SubTextSpan>
56+
</KaiHeaderWrapper>
57+
<Divider />
58+
</>
59+
)
60+
}
61+
62+
const KaiLoading = ({ loadingText }: { loadingText: string }) => {
63+
return (
64+
<LoadingWrapper>
65+
<Loader />
66+
<span>{loadingText}</span>
67+
</LoadingWrapper>
68+
)
69+
}
70+
71+
const KaiChat = ({
72+
chatPlaceHolderText,
73+
onSubmitChat,
74+
disabled = false,
75+
}: {
76+
chatPlaceHolderText: string
77+
onSubmitChat: (text: string) => void
78+
disabled?: boolean
79+
}) => {
80+
const [chatInput, setChatInput] = useState('')
81+
82+
const onChangeChatInput = (e: ChangeEvent<HTMLInputElement>) => setChatInput(e.target.value)
83+
84+
const handleEnter = (e: KeyboardEvent<HTMLInputElement>) => {
85+
if (e.key !== 'Enter') return
86+
onSubmitChat(chatInput)
87+
}
88+
89+
return (
90+
<ChatWrapper disabled={disabled}>
91+
<ChatInput
92+
type="text"
93+
id="token-search-input"
94+
data-testid="token-search-input"
95+
placeholder={chatPlaceHolderText}
96+
value={chatInput}
97+
onChange={onChangeChatInput}
98+
onKeyDown={handleEnter}
99+
autoComplete="off"
100+
/>
101+
<SendIcon onClick={() => onSubmitChat(chatInput)} />
102+
</ChatWrapper>
103+
)
104+
}
105+
106+
export default KaiContent
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { rgba } from 'polished'
2+
import styled, { css, keyframes } from 'styled-components'
3+
4+
import { ReactComponent as Send } from 'assets/svg/ic_send.svg'
5+
6+
import { Space } from './actions'
7+
8+
export const KaiHeaderWrapper = styled.div`
9+
display: flex;
10+
flex-wrap: wrap;
11+
gap: 6px;
12+
align-items: center;
13+
`
14+
15+
export const SubTextSpan = styled.span`
16+
color: ${({ theme }) => theme.subText};
17+
`
18+
19+
export const Divider = styled.div`
20+
background-color: #505050;
21+
height: 1px;
22+
width: 100%;
23+
margin: 10px 0 14px;
24+
`
25+
26+
export const WelcomeText = styled.div`
27+
margin-bottom: 16px;
28+
`
29+
30+
export const ChatWrapper = styled.div<{ disabled: boolean }>`
31+
position: relative;
32+
height: 36px;
33+
margin-top: 16px;
34+
35+
${({ disabled }) =>
36+
disabled &&
37+
css`
38+
opacity: 0.4;
39+
`}
40+
`
41+
42+
export const ChatInput = styled.input`
43+
position: absolute;
44+
display: flex;
45+
padding: 10px 30px 13px 16px;
46+
align-items: center;
47+
width: 100%;
48+
white-space: nowrap;
49+
background: none;
50+
border: none;
51+
outline: none;
52+
border-radius: 8px;
53+
color: ${({ theme }) => theme.text};
54+
border-style: solid;
55+
border: 1px solid ${({ theme }) => theme.buttonBlack};
56+
background: ${({ theme }) => theme.buttonBlack};
57+
transition: border 100ms;
58+
appearance: none;
59+
-webkit-appearance: none;
60+
61+
::placeholder {
62+
color: ${({ theme }) => theme.border};
63+
font-size: 13.5px;
64+
${({ theme }) => theme.mediaWidth.upToSmall`
65+
font-size: 12.5px;
66+
`};
67+
}
68+
69+
:focus {
70+
border: 1px solid ${({ theme }) => theme.primary};
71+
outline: none;
72+
}
73+
`
74+
75+
export const SendIcon = styled(Send)`
76+
position: absolute;
77+
right: 12px;
78+
top: 12px;
79+
color: transparent;
80+
transition: 0.1s ease-in-out;
81+
cursor: pointer;
82+
83+
:hover {
84+
color: ${({ theme }) => theme.primary};
85+
border-color: transparent;
86+
}
87+
`
88+
89+
export const LoadingWrapper = styled.div`
90+
color: ${({ theme }) => theme.subText};
91+
display: flex;
92+
align-items: center;
93+
gap: 6px;
94+
`
95+
96+
const loadingKeyFrame = keyframes`
97+
100%{transform: rotate(.5turn)}
98+
`
99+
100+
export const Loader = styled.div`
101+
width: 16px;
102+
aspect-ratio: 1;
103+
--c: ${({ theme }) => `no-repeat radial-gradient(farthest-side, ${theme.subText} 92%, #0000)`};
104+
background: var(--c) 50% 0, var(--c) 50% 100%, var(--c) 100% 50%, var(--c) 0 50%;
105+
background-size: 3px 3px;
106+
animation: ${loadingKeyFrame} 1s infinite;
107+
position: relative;
108+
109+
::before {
110+
content: '';
111+
position: absolute;
112+
inset: 0;
113+
margin: 1px;
114+
background: ${({ theme }) => `repeating-conic-gradient(#0000 0 35deg, ${theme.subText} 0 90deg)`};
115+
mask: radial-gradient(farthest-side, #0000 calc(100% - 1px), #000 0);
116+
-webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - 1px), #000 0);
117+
border-radius: 50%;
118+
}
119+
`
120+
121+
export const ActionPanel = styled.div`
122+
display: flex;
123+
flex-wrap: wrap;
124+
gap: 12px;
125+
width: 100%;
126+
`
127+
128+
export const ActionButton = styled.div<{ width: number }>`
129+
display: flex;
130+
align-items: center;
131+
justify-content: center;
132+
border-radius: 8px;
133+
color: #fafafa;
134+
height: 36px;
135+
background-color: ${({ theme }) => rgba(theme.white, 0.04)};
136+
transition: 0.1s ease-in-out;
137+
cursor: pointer;
138+
139+
:hover {
140+
background-color: ${({ theme }) => rgba(theme.white, 0.08)};
141+
}
142+
143+
${({ width }) =>
144+
css`
145+
width: ${width === Space.FULL_WIDTH ? width + '%' : `calc(${width}% - 6px)`};
146+
`}
147+
`

src/components/Kai/actions.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
export enum Space {
2+
HALF_WIDTH = 50,
3+
FULL_WIDTH = 100,
4+
}
5+
6+
export interface KaiAction {
7+
title: string
8+
space: Space
9+
}
10+
11+
interface ListActions {
12+
[actionKey: string]: KaiAction
13+
}
14+
15+
export const LIST_ACTIONS: ListActions = {
16+
CHECK_TOKEN_PRICE: {
17+
title: 'Check the token price',
18+
space: Space.FULL_WIDTH,
19+
},
20+
SEE_MARKET_TRENDS: {
21+
title: 'See market trends',
22+
space: Space.FULL_WIDTH,
23+
},
24+
FIND_HIGH_APY_POOLS: {
25+
title: 'Find high APY pools',
26+
space: Space.FULL_WIDTH,
27+
},
28+
BUY_TOKENS: {
29+
title: 'Buy tokens',
30+
space: Space.HALF_WIDTH,
31+
},
32+
SELL_TOKENS: {
33+
title: 'Sell tokens',
34+
space: Space.HALF_WIDTH,
35+
},
36+
ADD_LIQUIDITY: {
37+
title: 'Add liquidity',
38+
space: Space.FULL_WIDTH,
39+
},
40+
}
41+
42+
export const MAIN_MENU: KaiAction[] = [
43+
LIST_ACTIONS.CHECK_TOKEN_PRICE,
44+
LIST_ACTIONS.SEE_MARKET_TRENDS,
45+
LIST_ACTIONS.FIND_HIGH_APY_POOLS,
46+
LIST_ACTIONS.BUY_TOKENS,
47+
LIST_ACTIONS.SELL_TOKENS,
48+
LIST_ACTIONS.ADD_LIQUIDITY,
49+
]

0 commit comments

Comments
 (0)