This repository has been archived by the owner on Nov 9, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Gabriel Pereira Woitechen
committed
Jul 1, 2020
1 parent
ab0555c
commit 7f9da73
Showing
14 changed files
with
1,034 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
@import "tailwindcss/base"; | ||
@import "tailwindcss/components"; | ||
@import "tailwindcss/utilities"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
require("!style-loader!css-loader!postcss-loader!./global.css"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/** | ||
* @param {{ env: string; }} ctx | ||
*/ | ||
module.exports = (ctx) => ({ | ||
plugins: { | ||
tailwindcss: {}, | ||
autoprefixer: ctx.env === "production" ? {} : false, | ||
cssnano: ctx.env === "production" ? {} : false, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,48 @@ | ||
import React, { ReactElement } from "react"; | ||
import React, { ReactElement, useMemo } from "react"; | ||
import { nanoid } from "nanoid"; | ||
|
||
function Input(): ReactElement { | ||
return <input />; | ||
interface Props { | ||
value?: string | number; | ||
onChange?(event: React.ChangeEvent<HTMLInputElement>): void; | ||
|
||
label?: string; | ||
placeholder?: string; | ||
|
||
onFocus?(event: React.FocusEvent<HTMLInputElement>): void; | ||
onMouseDown?(event: React.MouseEvent<HTMLInputElement, MouseEvent>): void; | ||
onBlur?(event: React.FocusEvent<HTMLInputElement>): void; | ||
} | ||
|
||
function Input( | ||
{ value, onChange, label, placeholder, onFocus, onMouseDown, onBlur }: Props, | ||
ref: React.MutableRefObject<HTMLInputElement> | ||
): ReactElement { | ||
const componentId = useMemo(() => nanoid(), []); | ||
|
||
return ( | ||
<div className="flex flex-col"> | ||
{label && ( | ||
<label | ||
className="text-gray-600 font-bold mb-1 pr-4" | ||
htmlFor={componentId} | ||
> | ||
{label} | ||
</label> | ||
)} | ||
<input | ||
ref={ref} | ||
value={value} | ||
onChange={onChange} | ||
className="bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-3 text-gray-700 focus:outline-none focus:bg-white focus:border-blue-500" | ||
id={componentId} | ||
type="text" | ||
placeholder={placeholder} | ||
onFocus={onFocus} | ||
onMouseDown={onMouseDown} | ||
onBlur={onBlur} | ||
/> | ||
</div> | ||
); | ||
} | ||
|
||
export default Input; | ||
export default React.forwardRef(Input); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.Suggester__options { | ||
max-height: 176px; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import React, { | ||
ReactElement, | ||
useState, | ||
useCallback, | ||
useRef, | ||
useMemo, | ||
} from "react"; | ||
import Input from "../Input"; | ||
import { Option } from "../../types"; | ||
import styles from "./Suggester.module.css"; | ||
import clsx from "clsx"; | ||
|
||
interface Props { | ||
options: Option[]; | ||
|
||
label?: string; | ||
placeholder?: string; | ||
async?: true; | ||
loading?: boolean; | ||
min?: number; | ||
onSearch?(value: string): void; | ||
onSelect?(option: Option): void; | ||
} | ||
|
||
function Suggester({ | ||
options, | ||
label, | ||
placeholder, | ||
async, | ||
loading, | ||
min = 3, | ||
onSearch, | ||
onSelect, | ||
}: Props): ReactElement { | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
const [value, setValue] = useState(""); | ||
const [open, setOpen] = useState(false); | ||
const [shouldBlur, setShouldBlur] = useState(true); | ||
|
||
const onChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => { | ||
const inputValue = event.target.value; | ||
setValue(inputValue); | ||
setOpen(true); | ||
|
||
if (async && onSearch && inputValue.length >= 3) { | ||
onSearch(inputValue); | ||
} | ||
}, []); | ||
|
||
const onFocus = useCallback(() => { | ||
setOpen(true); | ||
}, []); | ||
const onBlur = useCallback( | ||
(event: React.FocusEvent<HTMLInputElement>) => { | ||
if (!shouldBlur) { | ||
event.preventDefault(); | ||
event.target.focus(); | ||
|
||
setShouldBlur(true); | ||
return; | ||
} | ||
|
||
setOpen(false); | ||
}, | ||
[shouldBlur] | ||
); | ||
|
||
const onOptionMouseDown = useCallback(() => { | ||
setShouldBlur(false); | ||
}, []); | ||
const onOptionMouseUp = useCallback( | ||
(option: Option) => () => { | ||
setOpen(false); | ||
setShouldBlur(true); | ||
setValue(option.label); | ||
|
||
if (onSelect) { | ||
onSelect(option); | ||
} | ||
}, | ||
[] | ||
); | ||
|
||
const filteredOptions = useMemo(() => { | ||
const valueToMatch = value.toLowerCase(); | ||
return options.filter((option) => { | ||
const label = option.label.toLowerCase(); | ||
return label.indexOf(valueToMatch) === 0; | ||
}); | ||
}, [options, value]); | ||
|
||
const getOptions = useCallback((): ReactElement | ReactElement[] => { | ||
if (async && value.length < min) { | ||
return ( | ||
<li | ||
key={`Suggester__option-loading`} | ||
className="select-none text-gray-600 py-1 px-3" | ||
onMouseDown={onOptionMouseDown} | ||
> | ||
Enter at least {min} characters | ||
</li> | ||
); | ||
} | ||
|
||
if (async && loading) { | ||
return ( | ||
<li | ||
key={`Suggester__option-loading`} | ||
className="select-none text-gray-600 py-1 px-3" | ||
onMouseDown={onOptionMouseDown} | ||
> | ||
Loading... | ||
</li> | ||
); | ||
} | ||
|
||
if (filteredOptions.length) { | ||
return filteredOptions.map((option, index) => ( | ||
<li | ||
key={`Suggester__option-${index}`} | ||
className="cursor-pointer select-none hover:bg-gray-300 active:bg-gray-400 text-gray-600 py-1 px-3" | ||
onMouseDown={onOptionMouseDown} | ||
onMouseUp={onOptionMouseUp(option)} | ||
> | ||
{option.label} | ||
</li> | ||
)); | ||
} | ||
|
||
return ( | ||
<li | ||
key={`Suggester__option-empty`} | ||
className="select-none text-gray-600 py-1 px-3" | ||
onMouseDown={onOptionMouseDown} | ||
> | ||
No suggestions | ||
</li> | ||
); | ||
}, [loading, value, filteredOptions]); | ||
|
||
return ( | ||
<div> | ||
<Input | ||
ref={inputRef} | ||
value={value} | ||
onChange={onChange} | ||
label={label} | ||
placeholder={placeholder} | ||
onFocus={onFocus} | ||
onMouseDown={onFocus} | ||
onBlur={onBlur} | ||
/> | ||
{open && ( | ||
<ul | ||
className={clsx( | ||
"shadow-sm overflow-auto mt-1 bg-gray-200 w-full rounded py-2", | ||
styles["Suggester__options"] | ||
)} | ||
> | ||
{getOptions()} | ||
</ul> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
export default Suggester; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
declare module "*.module.css"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.