Skip to content

Commit

Permalink
add component to search stocks
Browse files Browse the repository at this point in the history
  • Loading branch information
iamping committed Dec 26, 2024
1 parent 8753592 commit 076e595
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 4 deletions.
3 changes: 2 additions & 1 deletion src/components/app/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { DropdownProps, SelectOption } from '../../models/common';
import { useAtom, useAtomValue } from 'jotai';
import { appDropdownAtom, dropdownFnAtom, manualFilterAtom } from '../../state/atom';
import { useMediaQuery } from 'usehooks-ts';
import { mobileMediaQuery } from '../../utils/constant';

export const Dropdown: FC<DropdownProps> = ({ optionList, type }) => {
const filterChanged = useAtomValue(manualFilterAtom);
Expand All @@ -31,7 +32,7 @@ export const Dropdown: FC<DropdownProps> = ({ optionList, type }) => {
setOpen(false);
};

const isMobile = useMediaQuery('(max-width: 768px)');
const isMobile = useMediaQuery(mobileMediaQuery);

useEffect(() => {
if (type === 'Preset') {
Expand Down
4 changes: 3 additions & 1 deletion src/components/app/filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -215,14 +215,16 @@ export const CheckboxFilter: FC<CheckboxFilterProps> = ({ id, valueList, initial
<InputGroup
flex="1"
endElement={keyword.length === 0 ? <PiMagnifyingGlass /> : <PiXDuotone color="black" onClick={onClear} />}
endElementProps={{ paddingRight: 2 }}
width="100%">
<Input
ref={inputRef}
_focus={{ borderColor: 'gray.300' }}
id={`${id}-checkbox-search`}
placeholder="Search items"
value={keyword}
size="xs"
focusRing="none"
_focus={{ borderColor: 'gray.300' }}
onChange={onInputChange}
/>
</InputGroup>
Expand Down
106 changes: 106 additions & 0 deletions src/components/app/search-box.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { IconButton, Input, Show } from '@chakra-ui/react';
import { ChangeEvent, CSSProperties, useRef, useState } from 'react';
import { PiMagnifyingGlassBold, PiArrowCounterClockwiseDuotone, PiXDuotone } from 'react-icons/pi';
import { InputGroup } from '../ui/input-group';
import { useEventListener, useMediaQuery, useOnClickOutside } from 'usehooks-ts';
import { mobileMediaQuery } from '../../utils/constant';

export const SearchBox = () => {
const [open, setOpen] = useState(false);
const [keyword, setKeyword] = useState('');
const divRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);

const isMobile = useMediaQuery(mobileMediaQuery);
const styleForMobile: CSSProperties = isMobile
? { position: 'absolute', left: 78, right: 44, top: 8 }
: { flexGrow: 0, minWidth: 250 };

const openSearch = () => {
setOpen((val) => !val);
setTimeout(() => {
inputRef.current?.focus();
}, 0);
};

const clearSearch = () => {
setOpen(true);
setKeyword('');
setTimeout(() => {
inputRef.current?.focus();
}, 0);
};

const onInputChange = (event: ChangeEvent<HTMLInputElement>) => {
setKeyword(event.target.value);
};

useEventListener('keydown', (event) => {
const id = (event.target as HTMLElement)?.id ?? '';
if (id === 'search-stocks' || id === '') {
if (/^[A-Za-z0-9 \-,.]$/.test(event.key)) {
setOpen((val) => {
if (!val) {
setKeyword(event.key);
}
return true;
});
setTimeout(() => {
inputRef.current?.focus();
}, 0);
}
if (event.key === 'Escape') {
setOpen(false);
setKeyword('');
}
}
});

useOnClickOutside(divRef, () => {
if (keyword === '') {
setOpen(false);
}
});

const MagnifyIcon = (
<>
<Show when={open}>
<div title="Clear search" onClick={clearSearch} style={{ cursor: 'pointer' }}>
<PiArrowCounterClockwiseDuotone color="black" />
</div>
<IconButton size="xs" variant="outline" border={0} onClick={() => setOpen(false)}>
{open && <PiXDuotone title="Close" />}
</IconButton>
</Show>
<Show when={!open}>
<IconButton size="xs" variant="outline" border={0} onClick={openSearch}>
{!open && <PiMagnifyingGlassBold title="Search stocks" />}
</IconButton>
</Show>
</>
);

return (
<>
<Show when={!open}>{MagnifyIcon}</Show>
<Show when={open}>
<div ref={divRef}>
<InputGroup flex="1" style={styleForMobile} endElement={MagnifyIcon} endElementProps={{ padding: 0 }}>
<Input
ref={inputRef}
id="search-stocks"
variant="subtle"
placeholder="Search stocks"
size="xs"
focusRing="none"
border={0}
value={keyword}
maxLength={30}
onChange={onInputChange}
/>
</InputGroup>
</div>
</Show>
</>
);
};
7 changes: 5 additions & 2 deletions src/components/app/topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { Dropdown } from './dropdown';
import { Settings } from './settings';
import { useMediaQuery } from 'usehooks-ts';
import { useMemo } from 'react';
import { SearchBox } from './search-box';
import { mobileMediaQuery } from '../../utils/constant';

export const Topbar = () => {
const rowCount = useAtomValue(rowCountAtom);

const isSmallScreen = useMediaQuery('(max-width: 600px)');
const isSmallScreen = useMediaQuery(mobileMediaQuery);
const rowCountFormat = useMemo(() => {
return {
prefix: isSmallScreen ? '' : 'Total - ',
Expand All @@ -27,7 +29,7 @@ export const Topbar = () => {
}, [isSmallScreen, rowCount]);

return (
<HStack gap={1} paddingY={2} paddingLeft={1} paddingRight={2}>
<HStack gap={1} paddingY={2} paddingLeft={1} paddingRight={2} position="relative">
<Heading paddingX={1} title="US Stock Screener" lineHeight="10px" paddingTop="12px" className="borel-regular">
{rowCount < 0 && 'Loading...'}
{rowCount >= 0 && (
Expand All @@ -42,6 +44,7 @@ export const Topbar = () => {
<Dropdown type="View" optionList={viewOptions} />
<Separator orientation="vertical" height={5} />
<Spacer />
<SearchBox />
<Settings />
</HStack>
);
Expand Down
1 change: 1 addition & 0 deletions src/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

body {
padding: 0;
min-width: 380px;
}

.borel-regular {
Expand Down
1 change: 1 addition & 0 deletions src/utils/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const mobileMediaQuery = '(max-width: 680px)';

0 comments on commit 076e595

Please sign in to comment.