diff --git a/eslint.config.js b/eslint.config.js index 092408a..82ee3d1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -19,6 +19,8 @@ export default tseslint.config( }, rules: { ...reactHooks.configs.recommended.rules, + 'no-empty-pattern': 'off', + '@typescript-eslint/no-empty-object-type': 'off', 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, diff --git a/src/App.styled.ts b/src/App.styled.ts index 98b23f3..8f8ee95 100644 --- a/src/App.styled.ts +++ b/src/App.styled.ts @@ -27,6 +27,7 @@ export const Content = styled.div<{ isReady: boolean }>` box-sizing: border-box; display: flex; + flex-direction: column; justify-content: center; align-items: center; diff --git a/src/App.tsx b/src/App.tsx index 1f449a8..c436289 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,7 @@ import useSessionAppStore from '@stores/session-app' import useAppStore from '@stores/app' import * as Styled from './App.styled' +import SearchBar from '@components/SearchBar' function App() { const isReady = useSessionAppStore((store) => store.isReady) @@ -51,6 +52,7 @@ function App() { + diff --git a/src/components/Box.styled.ts b/src/components/Box.styled.ts new file mode 100644 index 0000000..8fe91cd --- /dev/null +++ b/src/components/Box.styled.ts @@ -0,0 +1,7 @@ +import styled from '@emotion/styled' + +export const Root = styled.div` + border-radius: 4px; + + background-color: rgba(255, 255, 255, 0.3); +` diff --git a/src/components/Box.tsx b/src/components/Box.tsx new file mode 100644 index 0000000..e6a550a --- /dev/null +++ b/src/components/Box.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +import * as Styled from './Box.styled' + +interface BoxProps extends React.HTMLAttributes {} + +function Box(props: BoxProps) { + return +} + +export default Box diff --git a/src/components/IconTab.styled.ts b/src/components/IconTab.styled.ts new file mode 100644 index 0000000..03b7356 --- /dev/null +++ b/src/components/IconTab.styled.ts @@ -0,0 +1,25 @@ +import styled from '@emotion/styled' + +export const Root = styled.div` + display: flex; +` + +export const Item = styled.button<{ selected: boolean }>` + margin: 0; + padding: ${({ theme }) => theme.spacing(0.75)}; + border: 0; + + background-color: rgba(255, 255, 255, 0.3); + + cursor: pointer; + opacity: ${({ selected }) => (selected ? 1 : 0.5)}; + + > svg { + width: 20px; + height: 20px; + + display: block; + + fill: white; + } +` diff --git a/src/components/IconTab.tsx b/src/components/IconTab.tsx new file mode 100644 index 0000000..88393b7 --- /dev/null +++ b/src/components/IconTab.tsx @@ -0,0 +1,36 @@ +import React from 'react' + +import * as Styled from './IconTab.styled' + +export interface IconTabItem { + value: TValue + icon: React.ComponentType +} + +interface IconTabProps { + value: TValue + onChange: (value: TValue) => void + items: IconTabItem[] +} + +function IconTab({ + value: currentValue, + items, + onChange, +}: IconTabProps) { + return ( + + {items.map(({ icon: Icon, value }) => ( + onChange(value)} + selected={value === currentValue} + > + + + ))} + + ) +} + +export default IconTab diff --git a/src/components/SearchBar.styled.ts b/src/components/SearchBar.styled.ts new file mode 100644 index 0000000..faf2714 --- /dev/null +++ b/src/components/SearchBar.styled.ts @@ -0,0 +1,48 @@ +import styled from '@emotion/styled' + +import Box from '@components/Box' + +export const Root = styled(Box)` + width: 100%; + max-width: 764px; + + margin-top: ${({ theme }) => theme.spacing(6)}; + border-top-left-radius: 0; + + position: relative; +` + +export const Input = styled.input` + width: 100%; + + margin: 0; + padding: ${({ theme }) => theme.spacing(1.5, 2)}; + border: 0; + + display: block; + box-sizing: border-box; + + font-family: 'SUITE Variable', sans-serif; + + color: white; + background-color: transparent; + + outline: none; + + ::placeholder { + color: rgba(255, 255, 255, 0.5); + } +` + +export const IconTabWrapper = styled.div` + border-top-left-radius: 4px; + border-top-right-radius: 4px; + + position: absolute; + top: 0; + left: 0; + + overflow: hidden; + + transform: translateY(-100%); +` diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx new file mode 100644 index 0000000..d4a7815 --- /dev/null +++ b/src/components/SearchBar.tsx @@ -0,0 +1,64 @@ +import React from 'react' +import { RiOpenaiFill, RiGoogleFill } from 'react-icons/ri' + +import IconTab, { IconTabItem } from '@components/IconTab' + +import * as Styled from './SearchBar.styled' + +type SearchEngine = 'google' | 'chatgpt' + +const SEARCH_ENGiNE_ITEMS: IconTabItem[] = [ + { + value: 'google', + icon: RiGoogleFill, + }, + { + value: 'chatgpt', + icon: RiOpenaiFill, + }, +] + +const PLACEHOLDER_TEXT: Record = { + google: 'Search Google...', + chatgpt: 'Ask ChatGPT...', +} + +function SearchBar() { + const [query, setQuery] = React.useState('') + const [searchEngine, setSearchEngine] = React.useState('google') + + const handleChange = (event: React.ChangeEvent) => { + setQuery(event.target.value) + } + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + if (searchEngine === 'google') { + location.href = `https://www.google.com/search?q=${query}` + } else if (searchEngine === 'chatgpt') { + location.href = `https://chat.openai.com/?q=${query}` + } + } + } + + return ( + + + + + + + ) +} + +export default SearchBar diff --git a/tsconfig.app.json b/tsconfig.app.json index c95cefd..0bee672 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -9,7 +9,7 @@ /* Bundler mode */ "moduleResolution": "Bundler", - "allowImportingTsExtensions": true, + "allowImportingTsExtensions": false, "isolatedModules": true, "moduleDetection": "force", "noEmit": true,