hyper/lib/components/searchBox.tsx

236 lines
6 KiB
TypeScript
Raw Normal View History

2023-07-23 11:21:04 -08:00
import React, {useCallback, useRef, useEffect, forwardRef} from 'react';
2023-06-26 01:29:50 -08:00
import type {SearchBoxProps} from '../hyper';
2022-12-30 23:17:19 -09:00
import {VscArrowUp} from '@react-icons/all-files/vsc/VscArrowUp';
import {VscArrowDown} from '@react-icons/all-files/vsc/VscArrowDown';
import {VscClose} from '@react-icons/all-files/vsc/VscClose';
import {VscCaseSensitive} from '@react-icons/all-files/vsc/VscCaseSensitive';
import {VscRegex} from '@react-icons/all-files/vsc/VscRegex';
import {VscWholeWord} from '@react-icons/all-files/vsc/VscWholeWord';
import clsx from 'clsx';
2022-12-30 23:17:19 -09:00
type SearchButtonColors = {
foregroundColor: string;
selectionColor: string;
backgroundColor: string;
};
2022-12-30 23:17:19 -09:00
type SearchButtonProps = React.PropsWithChildren<
{
onClick: () => void;
active: boolean;
title: string;
} & SearchButtonColors
>;
2022-12-30 23:17:19 -09:00
const SearchButton = ({
onClick,
active,
title,
foregroundColor,
backgroundColor,
selectionColor,
children
}: SearchButtonProps) => {
const handleKeyUp = useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === 'Enter' || event.key === ' ') {
onClick();
}
},
[onClick]
);
return (
<div
onClick={onClick}
className={clsx('search-button', {'search-button-active': active})}
tabIndex={0}
onKeyUp={handleKeyUp}
title={title}
>
{children}
<style jsx>
{`
.search-button {
cursor: pointer;
color: ${foregroundColor};
padding: 2px;
margin: 4px 0px;
height: 18px;
width: 18px;
border-radius: 2px;
}
.search-button:focus {
outline: ${selectionColor} solid 2px;
}
.search-button:hover {
background-color: ${backgroundColor};
}
.search-button-active {
background-color: ${selectionColor};
}
.search-button-active:hover {
background-color: ${selectionColor};
}
`}
</style>
</div>
);
};
2023-07-23 11:21:04 -08:00
const SearchBox = forwardRef<HTMLDivElement, SearchBoxProps>((props, ref) => {
const {
caseSensitive,
wholeWord,
regex,
results,
toggleCaseSensitive,
toggleWholeWord,
toggleRegex,
next,
prev,
close,
backgroundColor,
foregroundColor,
borderColor,
selectionColor,
font
} = props;
const searchTermRef = useRef<string>('');
const inputRef = useRef<HTMLInputElement | null>(null);
const handleChange = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
searchTermRef.current = event.currentTarget.value;
if (event.shiftKey && event.key === 'Enter') {
prev(searchTermRef.current);
} else if (event.key === 'Enter') {
next(searchTermRef.current);
}
},
[prev, next]
);
2022-12-30 23:17:19 -09:00
useEffect(() => {
inputRef.current?.focus();
}, [inputRef.current]);
const searchButtonColors: SearchButtonColors = {
backgroundColor: borderColor,
selectionColor,
foregroundColor
2019-11-25 07:16:00 -09:00
};
return (
2023-07-23 11:21:04 -08:00
<div className="flex-row search-container" ref={ref}>
<div className="flex-row search-box">
<input className="search-input" type="text" onKeyDown={handleChange} ref={inputRef} placeholder="Search" />
2022-12-30 23:17:19 -09:00
<SearchButton onClick={toggleCaseSensitive} active={caseSensitive} title="Match Case" {...searchButtonColors}>
<VscCaseSensitive size="14px" />
</SearchButton>
2022-12-30 23:17:19 -09:00
<SearchButton onClick={toggleWholeWord} active={wholeWord} title="Match Whole Word" {...searchButtonColors}>
<VscWholeWord size="14px" />
</SearchButton>
2022-12-30 23:17:19 -09:00
<SearchButton onClick={toggleRegex} active={regex} title="Use Regular Expression" {...searchButtonColors}>
<VscRegex size="14px" />
</SearchButton>
</div>
2022-12-30 23:17:19 -09:00
<span style={{minWidth: '60px', marginLeft: '4px'}}>
{results === undefined
? ''
: results.resultCount === 0
? 'No results'
: `${results.resultIndex + 1} of ${results.resultCount}`}
</span>
<div className="flex-row">
<SearchButton
onClick={() => prev(searchTermRef.current)}
active={false}
title="Previous Match"
{...searchButtonColors}
>
<VscArrowUp size="14px" />
</SearchButton>
<SearchButton
onClick={() => next(searchTermRef.current)}
active={false}
title="Next Match"
{...searchButtonColors}
>
<VscArrowDown size="14px" />
</SearchButton>
<SearchButton onClick={close} active={false} title="Close" {...searchButtonColors}>
<VscClose size="14px" />
</SearchButton>
</div>
2022-12-30 23:17:19 -09:00
<style jsx>
{`
.search-container {
background-color: ${backgroundColor};
border: 1px solid ${borderColor};
border-radius: 2px;
position: absolute;
right: 13px;
top: 4px;
z-index: 10;
padding: 4px;
font-family: ${font};
font-size: 12px;
}
.search-input {
outline: none;
background-color: transparent;
border: none;
color: ${foregroundColor};
align-self: stretch;
width: 100px;
}
.flex-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 4px;
}
2022-12-30 23:17:19 -09:00
.search-box {
border: none;
border-radius: 2px;
outline: ${borderColor} solid 1px;
background-color: ${backgroundColor};
color: ${foregroundColor};
padding: 0px 4px;
}
2022-12-30 23:17:19 -09:00
.search-input::placeholder {
color: ${foregroundColor};
}
2022-12-30 23:17:19 -09:00
.search-box:focus-within {
outline: ${selectionColor} solid 2px;
}
`}
</style>
</div>
);
2023-07-23 11:21:04 -08:00
});
SearchBox.displayName = 'SearchBox';
2022-12-30 23:17:19 -09:00
export default SearchBox;