├── .gitignore ├── README.md ├── example ├── .DS_Store ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── .DS_Store │ ├── App.tsx │ ├── assets │ │ ├── .DS_Store │ │ ├── codeBlocks.ts │ │ ├── images │ │ │ ├── logo.svg │ │ │ └── usage.gif │ │ └── theme │ │ │ └── index.ts │ ├── components │ │ ├── DataDialog.tsx │ │ └── Navbar.tsx │ ├── index.css │ ├── index.tsx │ └── react-app-env.d.ts ├── tsconfig.json ├── yarn-error.log └── yarn.lock ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── actions │ └── symbols.ts ├── components │ ├── Loading │ │ └── SkeletonLoading.tsx │ ├── Markets │ │ ├── Market.tsx │ │ └── index.tsx │ ├── Search │ │ ├── Input.tsx │ │ └── index.tsx │ └── Selector │ │ ├── Symbol.tsx │ │ └── index.tsx ├── config.ts ├── container │ ├── DraggableWrapper.tsx │ └── TickerSymbolSearch.tsx ├── hooks │ └── useSearchSymbols.ts ├── index.ts ├── react-app-env.d.ts └── types │ ├── config.ts │ ├── markets.ts │ ├── params.ts │ ├── request.ts │ ├── symbol.ts │ └── theme.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | dist 4 | .log 5 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Logo 4 | 5 | 6 |

Ticker Symbol Search

7 | 8 |

9 | Seamlessly integrate a search engine to find live ticker symbols into your web app 10 |
11 |
12 | View Demo 13 | · 14 | Report Bug 15 | · 16 | Request Feature 17 |

18 |

19 | 20 | ## Usage 21 | 22 |

23 | usage 24 |

25 | 26 | ## Installation 27 | 28 | `npm install ticker-symbol-search` 29 | 30 | or 31 | 32 | `yarn add ticker-symbol-search` 33 | 34 | ## Quick Start 35 | 36 | ### Search Component 37 | 38 | ```JSX 39 | import { TickerSymbolSearch } from ticker-symbol-search 40 | 41 | export default () => { 42 | const customTheme = { 43 | paper: { 44 | background: "rgba(128, 128, 128, 0.75)", 45 | color: "white", 46 | }, 47 | search: { 48 | icon: { 49 | color: "rgba(188, 204, 221, 0.25)", 50 | }, 51 | input: { 52 | color: "white", 53 | placeholderColor: "rgba(188, 204, 221, 0.25)", 54 | }, 55 | }, 56 | markets: { 57 | background: "rgba(0, 0, 0, 0.25)", 58 | color: "white", 59 | }, 60 | selector: { 61 | color: "white", 62 | }, 63 | } 64 | 65 | return console.log(data)} 67 | theme={customTheme} // optional 68 | />; 69 | } 70 | ``` 71 | 72 | ### Hook 73 | 74 | ```javascript 75 | import { useSearchSymbols } from ticker-symbol-search 76 | 77 | export default () => { 78 | const { symbols, 79 | isSuccess, 80 | isLoading, 81 | isError } = useSearchSymbols(search, market); 82 | } 83 | ``` 84 | 85 | where, 86 | 87 | `search` ticker symbol query 88 | 89 | `market` "ALL" | "Futures" | "Forex" | "CFD" | "Crypto" | "Index" | "Economic" 90 | 91 | ### Symbol Data Fetched 92 | 93 | #### Stock Data Example 94 | 95 | ```json 96 | { 97 | "symbol": "AAPL", 98 | "description": "APPLE INC", 99 | "type": "stock", 100 | "exchange": "NASDAQ", 101 | "provider_id": "ice", 102 | "typespecs": ["common"], 103 | "country": "US" 104 | } 105 | ``` 106 | 107 | ### Types 108 | 109 | ```javascript 110 | import { 111 | StockSymbol, 112 | FuturesSymbol, 113 | ForexSymbol, 114 | CFDSymbol, 115 | CryptoSymbol, 116 | IndexSymbol, 117 | EconomicSymbol, 118 | SymbolData, // union type of all symbols 119 | } from "ticker-symbol-search"; 120 | ``` 121 | 122 | ## Development 123 | 124 | To develop this package you need to install its dependencies and the dependencies of the example to test your changes 125 | 126 | - Run `npm install` in `root` and `/example` 127 | 128 | To concurrently develop and watch for your changes do the following: 129 | 130 | - `yarn build:watch` in `root` to build your package 131 | - `yarn start`in `/example`to run the React app to test 132 | 133 | ## Support 134 | 135 | Please use the issues section to let us know about any bugs or issues. We will try our best to find a fix/solution for you! 136 | -------------------------------------------------------------------------------- /example/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsohailB/ticker-symbol-search/5d1611b0d2d2571efd0ca5e87ae8e6fcf7ca4d18/example/.DS_Store -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ticker-symbol-search-example", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "@material-ui/core": "^4.11.3", 6 | "@material-ui/icons": "^4.11.2", 7 | "@material-ui/lab": "^4.0.0-alpha.57", 8 | "@testing-library/jest-dom": "^5.11.4", 9 | "@testing-library/react": "^11.1.0", 10 | "@testing-library/user-event": "^12.1.10", 11 | "@types/jest": "^26.0.15", 12 | "@types/node": "^12.0.0", 13 | "@types/react": "^17.0.0", 14 | "@types/react-dom": "^17.0.0", 15 | "react": "^17.0.2", 16 | "react-code-blocks": "^0.0.8", 17 | "react-dom": "^17.0.2", 18 | "react-scripts": "4.0.3", 19 | "ticker-symbol-search": "^1.0.1", 20 | "typescript": "^4.1.2", 21 | "web-vitals": "^1.0.1" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test", 27 | "eject": "react-scripts eject" 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "react-app", 32 | "react-app/jest" 33 | ] 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsohailB/ticker-symbol-search/5d1611b0d2d2571efd0ca5e87ae8e6fcf7ca4d18/example/public/favicon.ico -------------------------------------------------------------------------------- /example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 21 | 22 | 31 | ticker-symbol-search 32 | 33 | 34 | 35 | 36 |
37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsohailB/ticker-symbol-search/5d1611b0d2d2571efd0ca5e87ae8e6fcf7ca4d18/example/public/logo192.png -------------------------------------------------------------------------------- /example/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsohailB/ticker-symbol-search/5d1611b0d2d2571efd0ca5e87ae8e6fcf7ca4d18/example/public/logo512.png -------------------------------------------------------------------------------- /example/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /example/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /example/src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsohailB/ticker-symbol-search/5d1611b0d2d2571efd0ca5e87ae8e6fcf7ca4d18/example/src/.DS_Store -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Navbar from "./components/Navbar"; 3 | import theme from "./assets/theme"; 4 | import { 5 | Button, 6 | createStyles, 7 | Grid, 8 | makeStyles, 9 | Theme, 10 | ThemeProvider, 11 | Typography, 12 | } from "@material-ui/core"; 13 | import { CodeBlock, dracula } from "react-code-blocks"; 14 | import { installCode, usageCode } from "./assets/codeBlocks"; 15 | import { TickerSymbolSearch } from "ticker-symbol-search"; 16 | import DataDialog from "./components/DataDialog"; 17 | 18 | import logo from "./assets/images/logo.svg"; 19 | 20 | const useStyles = makeStyles((theme: Theme) => 21 | createStyles({ 22 | image: { 23 | width: "80%", 24 | marginBottom: "30px", 25 | "@media only screen and (min-width: 500px)": { 26 | width: "20%", 27 | marginTop: "-8vh", 28 | }, 29 | }, 30 | button: { 31 | margin: "50px 0px 20px 0px", 32 | fontWeight: "bolder", 33 | }, 34 | codeTitle: { 35 | margin: "30px 0px 5px 0px", 36 | }, 37 | webOnly: { 38 | "@media only screen and (max-width: 500px)": { 39 | display: "none", 40 | }, 41 | }, 42 | }) 43 | ); 44 | 45 | function App() { 46 | const classes = useStyles(); 47 | 48 | const [demoActivated, setDemoActivated] = useState(false); 49 | const [demoData, setDemoData] = useState(""); 50 | 51 | return ( 52 |
53 | 54 | 55 | 56 | 62 | logo 63 | 64 | ticker-symbol-search 65 | 66 | 67 | seamlessly integrate a search engine to find live ticker symbols 68 | into your web app 69 | 70 | 71 | 76 | fast • draggable • intuitive 77 | 78 | 79 | 88 | 89 | 94 | install using npm or yarn 95 | 96 | 97 |
98 | 110 |
111 | 112 | 117 | simply import and pass in a callback 118 | 119 | 120 |
121 | 135 |
136 | 137 | {demoActivated && ( 138 | 140 | setDemoData(JSON.stringify(data, null, 2)) 141 | } 142 | /> 143 | )} 144 | 0} 146 | data={demoData} 147 | setData={setDemoData} 148 | /> 149 |
150 |
151 |
152 | ); 153 | } 154 | 155 | export default App; 156 | -------------------------------------------------------------------------------- /example/src/assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsohailB/ticker-symbol-search/5d1611b0d2d2571efd0ca5e87ae8e6fcf7ca4d18/example/src/assets/.DS_Store -------------------------------------------------------------------------------- /example/src/assets/codeBlocks.ts: -------------------------------------------------------------------------------- 1 | export const installCode: string = "npm install ticker-symbol-search --save"; 2 | 3 | export const usageCode: string = `import { TickerSymbolSearch } from ticker-symbol-search 4 | 5 | export default () => { 6 | return console.log(data) } 8 | />; 9 | }`; 10 | -------------------------------------------------------------------------------- /example/src/assets/images/usage.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsohailB/ticker-symbol-search/5d1611b0d2d2571efd0ca5e87ae8e6fcf7ca4d18/example/src/assets/images/usage.gif -------------------------------------------------------------------------------- /example/src/assets/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { createMuiTheme } from "@material-ui/core"; 2 | 3 | // overrides and typography will eventually also be here 4 | const theme = createMuiTheme({ 5 | palette: { 6 | // type: "dark", 7 | primary: { 8 | main: "#000000", 9 | light: "#ade498", 10 | dark: "#35CD99", 11 | contrastText: "#fff", 12 | }, 13 | secondary: { 14 | main: "#ffe0f7", 15 | light: "#fe91ca", 16 | dark: "#d3dbff", 17 | contrastText: "#fff", 18 | }, 19 | background: { 20 | default: "#FFFFFF", 21 | }, 22 | text: { 23 | primary: "#35CD99", 24 | secondary: "#fff", 25 | }, 26 | }, 27 | typography: { 28 | fontFamily: "Noto Sans, sans-serif", 29 | button: { 30 | textTransform: "none", 31 | fontWeight: "normal", 32 | }, 33 | h1: { 34 | fontWeight: "bold", 35 | fontSize: "5em", 36 | fontFamily: "Playfair Display, serif", 37 | "@media only screen and (max-width: 900px)": { 38 | fontSize: "9vw", 39 | }, 40 | }, 41 | h3: { 42 | fontSize: "2em", 43 | fontWeight: "lighter", 44 | "@media only screen and (max-width: 1250px)": { 45 | fontSize: "2.5vw", 46 | }, 47 | "@media only screen and (max-width: 768px)": { 48 | fontSize: "1em", 49 | textAlign: "center", 50 | }, 51 | }, 52 | h4: { 53 | fontSize: "1.5em", 54 | fontWeight: "lighter", 55 | }, 56 | h5: { 57 | fontWeight: "bold", 58 | fontSize: "2em", 59 | fontFamily: "Playfair Display, serif", 60 | }, 61 | }, 62 | // Overrides 63 | overrides: { 64 | MuiButton: { 65 | root: { 66 | borderRadius: 50, 67 | color: "#fff", 68 | padding: "0px 25px 0px 25px", 69 | }, 70 | textPrimary: { 71 | color: "#35CD99", 72 | }, 73 | }, 74 | MuiDialog: { 75 | paper: { 76 | backgroundColor: "#131313", 77 | color: "white", 78 | }, 79 | }, 80 | }, 81 | }); 82 | 83 | export default theme; 84 | -------------------------------------------------------------------------------- /example/src/components/DataDialog.tsx: -------------------------------------------------------------------------------- 1 | import { Dialog, DialogTitle, Typography } from "@material-ui/core"; 2 | import React from "react"; 3 | 4 | const DataDialog = (props: { 5 | open: boolean; 6 | data: string; 7 | setData: React.Dispatch>; 8 | }) => { 9 | return ( 10 | props.setData("")}> 11 | 12 | Example Data 13 | 14 |
{props.data}
15 |
16 | ); 17 | }; 18 | 19 | export default DataDialog; 20 | -------------------------------------------------------------------------------- /example/src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | AppBar, 3 | createStyles, 4 | Grid, 5 | IconButton, 6 | makeStyles, 7 | Theme, 8 | Toolbar, 9 | } from "@material-ui/core"; 10 | import GitHubIcon from "@material-ui/icons/GitHub"; 11 | import React from "react"; 12 | 13 | const useStyles = makeStyles((theme: Theme) => 14 | createStyles({ 15 | root: { 16 | flexGrow: 1, 17 | marginTop: "1vh", 18 | marginBottom: "3vh", 19 | backgroundColor: theme.palette.background.default, 20 | }, 21 | button: { 22 | backgroundColor: theme.palette.primary.main, 23 | fontSize: "18px", 24 | }, 25 | }) 26 | ); 27 | 28 | const Navbar = () => { 29 | const classes = useStyles(); 30 | 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ); 42 | }; 43 | 44 | export default Navbar; 45 | -------------------------------------------------------------------------------- /example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById("root") 11 | ); 12 | -------------------------------------------------------------------------------- /example/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ticker-symbol-search", 3 | "description": "Seamlessly integrate a search engine to find live ticker symbols into your web app", 4 | "version": "1.0.1", 5 | "main": "./dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "license": "MIT", 8 | "contributors": [ 9 | { 10 | "name": "Harsohail Brar", 11 | "email": "harsohail.brar1@gmail.com" 12 | }, 13 | { 14 | "name": "David Kim", 15 | "email": "youup99@gmail.com" 16 | } 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/harsohailB/ticker-symbol-search" 21 | }, 22 | "keywords": [ 23 | "react", 24 | "typescript", 25 | "library", 26 | "stocks", 27 | "tickers", 28 | "fintech", 29 | "crypto", 30 | "finance", 31 | "search", 32 | "symbol" 33 | ], 34 | "bugs": "https://github.com/harsohailB/ticker-symbol-search/issues", 35 | "dependencies": { 36 | "@material-ui/core": "^4.11.3", 37 | "@material-ui/icons": "^4.11.2", 38 | "@material-ui/lab": "^4.0.0-alpha.57", 39 | "@types/node": "^12.0.0", 40 | "@types/react": "^17.0.0", 41 | "@types/react-dom": "^17.0.0", 42 | "@types/styled-components": "^5.1.9", 43 | "axios": "^0.21.1", 44 | "react-device-detect": "^1.17.0", 45 | "react-draggable": "^4.4.3", 46 | "react-scripts": "4.0.3", 47 | "styled-components": "^5.2.3", 48 | "typescript": "^4.1.2" 49 | }, 50 | "devDependencies": { 51 | "@testing-library/jest-dom": "^5.11.4", 52 | "@testing-library/react": "^11.1.0", 53 | "@testing-library/user-event": "^12.1.10", 54 | "@types/jest": "^26.0.15", 55 | "concurrently": "^6.0.2", 56 | "rimraf": "^3.0.2" 57 | }, 58 | "peerDependencies": { 59 | "react": "16 || 17", 60 | "react-dom": "16 || 17" 61 | }, 62 | "files": [ 63 | "dist" 64 | ], 65 | "scripts": { 66 | "build": "rimraf ./dist && tsc", 67 | "build:watch": "rimraf ./dist && tsc --watch", 68 | "test": "react-scripts test", 69 | "eject": "react-scripts eject" 70 | }, 71 | "eslintConfig": { 72 | "extends": [ 73 | "react-app", 74 | "react-app/jest" 75 | ] 76 | }, 77 | "browserslist": { 78 | "production": [ 79 | ">0.2%", 80 | "not dead", 81 | "not op_mini all" 82 | ], 83 | "development": [ 84 | "last 1 chrome version", 85 | "last 1 firefox version", 86 | "last 1 safari version" 87 | ] 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsohailB/ticker-symbol-search/5d1611b0d2d2571efd0ca5e87ae8e6fcf7ca4d18/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 25 | ticker-symbol-search 26 | 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsohailB/ticker-symbol-search/5d1611b0d2d2571efd0ca5e87ae8e6fcf7ca4d18/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harsohailB/ticker-symbol-search/5d1611b0d2d2571efd0ca5e87ae8e6fcf7ca4d18/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/actions/symbols.ts: -------------------------------------------------------------------------------- 1 | import { SymbolQueryParams } from "../types/params"; 2 | import { config } from "../config"; 3 | import { Routes } from "../types/config"; 4 | import axios from "axios"; 5 | import { MarketTypes } from "../types/markets"; 6 | 7 | export const getSymbols = async (queryParams: SymbolQueryParams) => { 8 | const targetParams: string = `?type=${ 9 | queryParams.type === MarketTypes.ALL ? "" : queryParams.type.toLowerCase() 10 | }&text=${queryParams.text}&hl=${ 11 | queryParams.hl 12 | }&exchange=&lang=en&domain=production`; 13 | 14 | const symbolsEndpoint: string = `${config.endpoint}/${Routes.SYMBOL_SEARCH}`; 15 | 16 | const response = await axios.get(config.proxyEndpoint, { 17 | params: { url: symbolsEndpoint + targetParams }, 18 | }); 19 | 20 | if (response.status !== 200) { 21 | throw "getSymbols failed " + response.status; 22 | } 23 | 24 | return response.data; 25 | }; 26 | -------------------------------------------------------------------------------- /src/components/Loading/SkeletonLoading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Skeleton } from "@material-ui/lab"; 3 | 4 | const ContentLoading = () => { 5 | return ( 6 |
7 | 8 | 9 | 10 |
11 | ); 12 | }; 13 | 14 | export default ContentLoading; 15 | -------------------------------------------------------------------------------- /src/components/Markets/Market.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { MarketTypes } from "../../types/markets"; 3 | import styled from "styled-components"; 4 | 5 | interface TextProps { 6 | selected: boolean; 7 | } 8 | 9 | const Market = (props: { 10 | market: string; 11 | selected: boolean; 12 | updateMarket: (newMarket: MarketTypes) => void; 13 | }) => { 14 | return ( 15 | props.updateMarket(props.market as MarketTypes)}> 16 | {props.market} 17 | 18 | ); 19 | }; 20 | 21 | const Wrapper = styled.div` 22 | background: ${({ theme }) => theme.markets.background}; 23 | margin: 5px 12px 5px 0px; 24 | border-radius: 20px; 25 | cursor: pointer; 26 | width: max-content; 27 | 28 | @media (max-width: 768px): { 29 | font-size: 10px; 30 | }, 31 | `; 32 | 33 | const Text = styled.p` 34 | margin: 0; 35 | color: ${({ theme }) => theme.markets.color}; 36 | padding: 6px 8px 6px 8px; 37 | font-weight: ${(props: TextProps) => (props.selected ? "bold" : "normal")}; 38 | `; 39 | 40 | export default Market; 41 | -------------------------------------------------------------------------------- /src/components/Markets/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { MarketTypes } from "../../types/markets"; 4 | import Market from "./Market"; 5 | 6 | const Markets = (props: { 7 | markets: string[]; 8 | selectedMarket: string; 9 | updateMarket: (newMarket: MarketTypes) => void; 10 | }) => { 11 | return ( 12 | 13 | {props.markets.map((market: string) => ( 14 | 20 | ))} 21 | 22 | ); 23 | }; 24 | 25 | const Wrapper = styled.div` 26 | border-top: 1px solid rgba(255, 255, 255, 0.25); 27 | padding: 10px 20px 10px 20px; 28 | display: flex; 29 | align-items: center; 30 | flex-wrap: wrap; 31 | 32 | @media (max-width: 875px) { 33 | font-size: 12px; 34 | padding: 5px 20px 5px 20px; 35 | justify-content: center; 36 | }, 37 | `; 38 | 39 | export default Markets; 40 | -------------------------------------------------------------------------------- /src/components/Search/Input.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | const Input = (props: { 5 | search: string; 6 | setSearch: (newSearch: string) => void; 7 | }) => { 8 | return ( 9 | props.setSearch(evt.target.value)} 13 | autoFocus 14 | type="text" 15 | /> 16 | ); 17 | }; 18 | 19 | const StyledInput = styled.input` 20 | background: none; 21 | border: none; 22 | outline: none; 23 | font-size: 18px; 24 | width: 75%; 25 | margin-left: 10px; 26 | color: ${({ theme }) => theme.search.input.color}; 27 | 28 | &:placeholder { 29 | color: ${({ theme }) => theme.search.input.placeholderColor}; 30 | } 31 | `; 32 | 33 | export default Input; 34 | -------------------------------------------------------------------------------- /src/components/Search/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import SearchIcon from "@material-ui/icons/Search"; 3 | import Input from "./Input"; 4 | import styled from "styled-components"; 5 | 6 | const Search = (props: { 7 | search: string; 8 | setSearch: (newSearch: string) => void; 9 | }) => { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | const Wrapper = styled.div` 21 | padding: 10px; 22 | background-color: transparent; 23 | margin: 0; 24 | display: flex; 25 | align-items: center; 26 | `; 27 | 28 | const Icon = styled.div` 29 | color: ${({ theme }) => theme.search.icon.color}; 30 | `; 31 | 32 | export default Search; 33 | -------------------------------------------------------------------------------- /src/components/Selector/Symbol.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { SymbolData } from "../../types/symbol"; 3 | import styled from "styled-components"; 4 | 5 | const Symbol = (props: { 6 | symbol: SymbolData; 7 | callback: (symbolData: SymbolData) => void; 8 | }) => { 9 | return ( 10 | props.callback(props.symbol)}> 11 | 15 | 20 | 21 | {props.symbol.type} 22 | {props.symbol.exchange} 23 | 24 | 25 | ); 26 | }; 27 | 28 | interface TableDataCellProps { 29 | webOnly?: boolean; 30 | align?: string; 31 | switch?: boolean; 32 | } 33 | 34 | const TableRow = styled.tr` 35 | cursor: pointer; 36 | width: 100%; 37 | border: none; 38 | transition: all 1s ease; 39 | 40 | &:hover { 41 | background: ${({ theme }) => theme.markets.background}; 42 | } 43 | `; 44 | 45 | const TableDataCell = styled.td` 46 | border-bottom: 1px solid rgba(255, 255, 255, 0.25); 47 | padding: 8px 5px 8px 5px; 48 | text-align: ${(props: TableDataCellProps) => props.align}; 49 | color: ${({ theme }) => theme.selector.color}; 50 | font-weight: 300; 51 | 52 | & > em { 53 | font-style: normal; 54 | font-weight: 900; 55 | } 56 | 57 | ${(props: TableDataCellProps) => 58 | props.webOnly && 59 | ` 60 | @media (max-width: 1000px) { 61 | display: none; 62 | }; 63 | `} 64 | 65 | ${(props: TableDataCellProps) => 66 | props.switch && 67 | ` 68 | padding-left: 20px; 69 | @media (max-width: 1000px) { 70 | text-align: right; 71 | max-width: 100px; 72 | white-space: nowrap; 73 | overflow: hidden; 74 | text-overflow: ellipsis; 75 | }; 76 | `} 77 | `; 78 | 79 | const Type = styled.span` 80 | margin-right: 5px; 81 | color: rgba(255, 255, 255, 0.5); 82 | `; 83 | 84 | export default Symbol; 85 | -------------------------------------------------------------------------------- /src/components/Selector/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { SymbolData } from "../../types/symbol"; 4 | import Symbol from "./Symbol"; 5 | import styled from "styled-components"; 6 | 7 | const Selector = (props: { 8 | symbols: SymbolData[]; 9 | callback: (symbolData: SymbolData) => void; 10 | }) => { 11 | return ( 12 | 13 | 14 | {props.symbols.map((symbol: SymbolData) => ( 15 | 20 | ))} 21 |
22 |
23 | ); 24 | }; 25 | 26 | const Wrapper = styled.div` 27 | max-height: 400px; 28 | overflow-y: scroll; 29 | overflow-x: hidden; 30 | width: 100%; 31 | 32 | ::-webkit-scrollbar { 33 | width: 0px; 34 | } 35 | `; 36 | 37 | const Table = styled.table` 38 | width: 100%; 39 | border-spacing: 0px; 40 | `; 41 | 42 | export default Selector; 43 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import { APIConfiguration } from "./types/config"; 2 | 3 | const protocol: string = "https"; 4 | const baseUrl: string = "symbol-search.tradingview.com"; 5 | const proxy: string = "api.allorigins.win/raw"; 6 | 7 | export const config: APIConfiguration = { 8 | protocol, 9 | baseUrl, 10 | endpoint: protocol + "://" + baseUrl, 11 | proxyEndpoint: protocol + "://" + proxy, 12 | }; 13 | -------------------------------------------------------------------------------- /src/container/DraggableWrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled, { ThemeProvider } from "styled-components"; 3 | import Draggable from "react-draggable"; 4 | import { isMobile } from "react-device-detect"; 5 | 6 | import { Theme } from "../types/theme"; 7 | 8 | const defaultTheme: Theme = { 9 | paper: { 10 | background: "rgba(128, 128, 128, 0.75)", 11 | color: "white", 12 | }, 13 | search: { 14 | icon: { 15 | color: "rgba(188, 204, 221, 0.25)", 16 | }, 17 | input: { 18 | color: "white", 19 | placeholderColor: "rgba(188, 204, 221, 0.25)", 20 | }, 21 | }, 22 | markets: { 23 | background: "rgba(0, 0, 0, 0.25)", 24 | color: "white", 25 | }, 26 | selector: { 27 | color: "white", 28 | }, 29 | }; 30 | 31 | const DraggableWrapper = (props: { theme?: Theme; children: JSX.Element }) => { 32 | const selectedTheme: Theme = props.theme 33 | ? { ...defaultTheme, ...props.theme } 34 | : defaultTheme; 35 | 36 | return ( 37 | 40 | 41 | {props.children} 42 | 43 | 44 | ); 45 | }; 46 | 47 | export default DraggableWrapper; 48 | 49 | const Wrapper = styled.div` 50 | position: absolute; 51 | top: 10%; 52 | z-index: 100; 53 | display: flex; 54 | flex-direction: row; 55 | justify-content: center; 56 | align-items: center; 57 | width: 50%; 58 | 59 | @media (max-width: 600px) { 60 | width: 90%; 61 | } ; 62 | `; 63 | -------------------------------------------------------------------------------- /src/container/TickerSymbolSearch.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useState } from "react"; 3 | import styled from "styled-components"; 4 | 5 | import DraggableWrapper from "./DraggableWrapper"; 6 | import Search from "../components/Search"; 7 | import Markets from "../components/Markets"; 8 | import { MarketTypes } from "../types/markets"; 9 | import { useSearchSymbols } from "../hooks/useSearchSymbols"; 10 | import Selector from "../components/Selector"; 11 | import SkeletonLoading from "../components/Loading/SkeletonLoading"; 12 | import { SymbolData } from "../types/symbol"; 13 | import { Theme } from "../types/theme"; 14 | 15 | interface Query { 16 | search: string; 17 | market: MarketTypes; 18 | } 19 | 20 | export const TickerSymbolSearch = (props: { 21 | callback: (symbolData: SymbolData) => void; 22 | theme?: Theme; 23 | }) => { 24 | const [query, setQuery] = useState({ 25 | search: "", 26 | market: MarketTypes.ALL, 27 | }); 28 | const { symbols, isSuccess, isLoading, isError } = useSearchSymbols( 29 | query.search, 30 | query.market 31 | ); 32 | 33 | const updateSearchInput = (newSearch: string) => { 34 | setQuery((prevQuery: Query) => ({ 35 | ...prevQuery, 36 | search: newSearch, 37 | })); 38 | }; 39 | 40 | const updateMarket = (newMarket: MarketTypes) => { 41 | setQuery((prevQuery: Query) => ({ 42 | ...prevQuery, 43 | market: newMarket, 44 | })); 45 | }; 46 | 47 | return ( 48 | 49 | 50 | 51 | 52 | {query.search.length !== 0 && ( 53 |
54 | 59 | 60 | 61 | {isLoading && } 62 | {isSuccess && ( 63 | 64 | )} 65 | {isSuccess && symbols.length === 0 && ( 66 | No symbols found... 67 | )} 68 | {isError && There was an error fetching symbols...} 69 | 70 |
71 | )} 72 |
73 |
74 | ); 75 | }; 76 | 77 | const Wrapper = styled.div` 78 | display: flex; 79 | flex-direction: column; 80 | background: ${({ theme }) => theme.paper.background}; 81 | backdrop-filter: blur(20px); 82 | border-radius: 10px; 83 | width: 100%; 84 | `; 85 | 86 | const Body = styled.div` 87 | margin: 0px 20px 0px 20px; 88 | padding-bottom: 20px; 89 | `; 90 | 91 | const Text = styled.p` 92 | color: ${({ theme }) => theme.paper.color}; 93 | `; 94 | -------------------------------------------------------------------------------- /src/hooks/useSearchSymbols.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useReducer, useState } from "react"; 2 | 3 | import { getSymbols } from "../actions/symbols"; 4 | import { MarketTypes } from "../types/markets"; 5 | import { SymbolQueryParams } from "../types/params"; 6 | import { SymbolData } from "../types/symbol"; 7 | import { RequestStatus } from "../types/request"; 8 | 9 | enum Types { 10 | LOADING = "loading", 11 | SUCCESS = "success", 12 | ERROR = "error", 13 | } 14 | 15 | const initialState: RequestStatus = { 16 | isLoading: false, 17 | isError: false, 18 | isSuccess: false, 19 | }; 20 | 21 | const reducer = (state: RequestStatus, action: Types) => { 22 | switch (action) { 23 | case Types.LOADING: 24 | return { 25 | isLoading: true, 26 | isError: false, 27 | isSuccess: false, 28 | }; 29 | case Types.SUCCESS: 30 | return { isLoading: false, isError: false, isSuccess: true }; 31 | case Types.ERROR: 32 | return { isLoading: false, isError: true, isSuccess: false }; 33 | default: 34 | return state; 35 | } 36 | }; 37 | 38 | export const useSearchSymbols = (search: string, market: MarketTypes) => { 39 | const [symbols, setSymbols] = useState([]); 40 | const [status, dispatchStatus] = useReducer(reducer, initialState); 41 | const [timerId, setTimerId] = useState(); 42 | 43 | // Debounces API call to fetch symbols 44 | useEffect(() => { 45 | dispatchStatus(Types.LOADING); 46 | 47 | const params: SymbolQueryParams = { 48 | text: search, 49 | hl: 1, 50 | type: market, 51 | }; 52 | 53 | const newTimerId: NodeJS.Timeout = setTimeout(() => { 54 | getSymbols(params) 55 | .then((symbolsResponse: SymbolData[]) => { 56 | setSymbols(symbolsResponse); 57 | dispatchStatus(Types.SUCCESS); 58 | }) 59 | .catch(() => { 60 | dispatchStatus(Types.ERROR); 61 | }); 62 | }, 500); 63 | 64 | if (timerId == null) { 65 | setTimerId(newTimerId); 66 | } else { 67 | // Clears queued API call with new one with 500ms timeout 68 | clearTimeout(timerId); 69 | setTimerId(newTimerId); 70 | } 71 | }, [search, market]); 72 | 73 | return { symbols, ...status }; 74 | }; 75 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { TickerSymbolSearch } from "./container/TickerSymbolSearch"; 2 | export { useSearchSymbols } from "./hooks/useSearchSymbols"; 3 | export * from "./types/symbol"; 4 | export * from "./types/theme"; 5 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/types/config.ts: -------------------------------------------------------------------------------- 1 | export enum Routes { 2 | SYMBOL_SEARCH = "symbol_search", 3 | } 4 | 5 | export interface APIConfiguration { 6 | protocol: string; 7 | baseUrl: string; 8 | endpoint: string; 9 | proxyEndpoint: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/types/markets.ts: -------------------------------------------------------------------------------- 1 | export enum MarketTypes { 2 | ALL = "ALL", 3 | STOCK = "Stock", 4 | FUTURES = "Futures", 5 | FOREX = "Forex", 6 | CFD = "CFD", 7 | CRYPTO = "Crypto", 8 | INDEX = "Index", 9 | ECONOMIC = "Economic", 10 | } 11 | -------------------------------------------------------------------------------- /src/types/params.ts: -------------------------------------------------------------------------------- 1 | import { MarketTypes } from "./markets"; 2 | 3 | export interface SymbolQueryParams { 4 | text: string; 5 | hl: 0 | 1; 6 | type: MarketTypes; 7 | } 8 | -------------------------------------------------------------------------------- /src/types/request.ts: -------------------------------------------------------------------------------- 1 | export interface RequestStatus { 2 | isLoading: boolean; 3 | isSuccess: boolean; 4 | isError: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /src/types/symbol.ts: -------------------------------------------------------------------------------- 1 | export interface Symbol { 2 | symbol: string; 3 | description: string; 4 | type: string; 5 | exchange: string; 6 | provider_id: string; 7 | } 8 | 9 | export interface Contract { 10 | symbol: string; 11 | description: string; 12 | } 13 | 14 | export interface StockSymbol extends Symbol { 15 | typespecs: string[]; 16 | country: string; 17 | } 18 | 19 | export interface FuturesSymbol extends Symbol { 20 | country: string; 21 | contracts: Contract[]; 22 | } 23 | 24 | export interface ForexSymbol extends Symbol { 25 | prefix: string; 26 | } 27 | 28 | export interface CFDSymbol extends Symbol { 29 | country: string; 30 | } 31 | 32 | export interface CryptoSymbol extends Symbol {} 33 | 34 | export interface IndexSymbol extends Symbol { 35 | country: string; 36 | } 37 | 38 | export interface EconomicSymbol extends Symbol { 39 | params: string[]; 40 | } 41 | 42 | export type SymbolData = 43 | | StockSymbol 44 | | FuturesSymbol 45 | | ForexSymbol 46 | | CFDSymbol 47 | | CryptoSymbol 48 | | IndexSymbol 49 | | EconomicSymbol; 50 | -------------------------------------------------------------------------------- /src/types/theme.ts: -------------------------------------------------------------------------------- 1 | interface Styles { 2 | background?: string; 3 | color?: string; 4 | fontFamily?: string; 5 | } 6 | 7 | export interface Theme { 8 | paper?: Styles; 9 | search?: { 10 | icon?: { 11 | color: string; 12 | }; 13 | input?: { 14 | color?: string; 15 | placeholderColor?: string; 16 | }; 17 | }; 18 | markets?: Styles; 19 | selector?: Styles; 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "sourceMap": true, 6 | "outDir": "./dist", 7 | "declaration": true, 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "downlevelIteration": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "jsx": "react", 21 | "noEmit": false 22 | }, 23 | "exclude": ["node_modules"], 24 | "include": [ 25 | "./src/**/*.tsx", 26 | "./src/**/*.ts", 27 | "./src/**/*.jsx", 28 | "./src/**/*.js" 29 | ] 30 | } 31 | --------------------------------------------------------------------------------