-
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.
Merge pull request #24 from Bandmators/feature/search
feat(common): search (#23)
- Loading branch information
Showing
12 changed files
with
508 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
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,19 @@ | ||
import { PortalProvider } from '@/components/Portal/PortalProvider'; | ||
import { AlignType } from '@/types/align'; | ||
|
||
interface SearchProps extends React.PropsWithChildren { | ||
align?: AlignType; | ||
space?: number; | ||
} | ||
|
||
/** | ||
* Displays a list of menus. | ||
* @returns | ||
*/ | ||
export const Search = ({ align = 'center', space = 0, children }: SearchProps) => { | ||
return ( | ||
<PortalProvider align={align} space={space}> | ||
{children} | ||
</PortalProvider> | ||
); | ||
}; |
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,20 @@ | ||
import * as React from 'react'; | ||
|
||
import { PortalContent } from '@/components/Portal/PortalContent'; | ||
|
||
interface ModalProps extends React.ComponentPropsWithoutRef<'div'> { | ||
width?: React.CSSProperties['width']; | ||
} | ||
|
||
/** | ||
* SearchContent | ||
* @returns | ||
*/ | ||
export const SearchContent = React.forwardRef<HTMLDivElement, ModalProps>(({ width, children, ...props }, ref) => { | ||
return ( | ||
<PortalContent width={width} ref={ref} role="group" disabledAutoFocus {...props}> | ||
{children} | ||
</PortalContent> | ||
); | ||
}); | ||
SearchContent.displayName = 'SearchContent'; |
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,27 @@ | ||
import styled from '@emotion/styled'; | ||
import * as React from 'react'; | ||
|
||
import { Input } from '../Input'; | ||
|
||
interface InputProps extends React.ComponentPropsWithoutRef<'input'> {} | ||
|
||
export const SearchInput = ({ ...props }: InputProps) => { | ||
const inputRef = React.useRef<HTMLInputElement>(null); | ||
|
||
React.useEffect(() => { | ||
if (inputRef.current) inputRef.current.focus(); | ||
}, []); | ||
|
||
return <StyledInput ref={inputRef} {...props} />; | ||
}; | ||
|
||
const StyledInput = styled(Input)` | ||
margin: 0.375rem 0.375rem; | ||
padding: 0.375rem 0.375rem; | ||
width: auto; | ||
border-radius: 0.25rem; | ||
font-size: 0.875rem; | ||
line-height: 1.25rem; | ||
outline: 2px solid transparent; | ||
outline-offset: 2px; | ||
`; |
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,70 @@ | ||
import * as React from 'react'; | ||
|
||
import { Input } from '@/components/Input'; | ||
import { PortalContext } from '@/components/Portal/PortalContext'; | ||
import useContext from '@/hooks/useContext'; | ||
import { composeEventHandlers } from '@/libs/event'; | ||
import { composeRefs } from '@/libs/ref'; | ||
|
||
type ComponentPropsWithoutRef<E extends React.ElementType> = React.ComponentPropsWithoutRef<E>; | ||
|
||
/** | ||
* SearchInputToggle | ||
* @returns | ||
*/ | ||
export const SearchInputToggle = React.forwardRef<HTMLInputElement, ComponentPropsWithoutRef<'input'>>( | ||
({ onClick, onChange, onKeyDown, ...props }, ref) => { | ||
const { showModal, setShowModal, setToggleElment } = useContext(PortalContext); | ||
const compRef = React.useRef<HTMLInputElement | null>(null); | ||
|
||
const openModal = () => { | ||
if (showModal) return; | ||
|
||
if (compRef.current) { | ||
const rect = compRef.current; | ||
setToggleElment(rect); | ||
} | ||
|
||
setShowModal(true); | ||
}; | ||
|
||
const closeModal = React.useCallback(() => { | ||
setShowModal(false); | ||
}, []); | ||
|
||
const ACTIONS: Record<string, (e: React.KeyboardEvent<HTMLInputElement>) => void> = { | ||
ArrowDown: () => focus('next'), | ||
ArrowUp: () => focus('prev'), | ||
Tab: () => closeModal(), | ||
Enter: () => focus('next'), | ||
Escape: () => closeModal(), | ||
}; | ||
|
||
const handleOnKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { | ||
console.log(e); | ||
const handler = ACTIONS[e.key]; | ||
|
||
if (handler) { | ||
// e.preventDefault(); | ||
handler(e); | ||
} | ||
}; | ||
|
||
const focus = (key: 'next' | 'prev'): void => { | ||
const el = document.querySelector('#bmates-portal [data-focus-enabled="true"]') as HTMLElement; | ||
if (key === 'next' && el) el.focus(); | ||
}; | ||
|
||
return ( | ||
<Input | ||
ref={composeRefs(compRef, ref)} | ||
aria-haspopup="true" | ||
onClick={composeEventHandlers(openModal, onClick)} | ||
onChange={composeEventHandlers(openModal, onChange)} | ||
onKeyDown={composeEventHandlers(onKeyDown, handleOnKeyDown)} | ||
{...props} | ||
/> | ||
); | ||
}, | ||
); | ||
SearchInputToggle.displayName = 'SearchInputToggle'; |
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,55 @@ | ||
import { css } from '@emotion/react'; | ||
import styled from '@emotion/styled'; | ||
import * as React from 'react'; | ||
|
||
import { PortalContext } from '@/components/Portal/PortalContext'; | ||
import useContext from '@/hooks/useContext'; | ||
import { composeEventHandlers } from '@/libs/event'; | ||
|
||
interface SearchItemProps extends React.ComponentPropsWithoutRef<'li'> { | ||
disabled?: boolean; | ||
} | ||
export const SearchItem = React.forwardRef<HTMLLIElement, SearchItemProps>(({ disabled = false, ...props }, ref) => { | ||
const { setShowModal } = useContext(PortalContext); | ||
|
||
const onClickHandler = () => { | ||
if (!disabled) setShowModal(false); | ||
}; | ||
|
||
return ( | ||
<SearchItemStyled | ||
ref={ref} | ||
tabIndex={0} | ||
role={'menuitem'} | ||
disabled={disabled} | ||
onClick={composeEventHandlers(props.onClick, onClickHandler)} | ||
data-focus-enabled="true" | ||
{...props} | ||
></SearchItemStyled> | ||
); | ||
}); | ||
SearchItem.displayName = 'SearchItem'; | ||
|
||
const SearchItemStyled = styled.li<{ disabled: boolean }>` | ||
display: flex; | ||
position: relative; | ||
align-items: center; | ||
padding: 0.375rem 0.5rem; | ||
border-radius: 0.25rem; | ||
font-size: 0.875rem; | ||
line-height: 1.25rem; | ||
outline: 2px solid transparent; | ||
outline-offset: 2px; | ||
cursor: default; | ||
${({ disabled }) => | ||
disabled | ||
? css` | ||
opacity: 0.5; | ||
` | ||
: css` | ||
&:hover, | ||
&:focus { | ||
background-color: var(--gray-100); | ||
} | ||
`} | ||
`; |
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,43 @@ | ||
import * as React from 'react'; | ||
|
||
import { PortalContext } from '@/components/Portal/PortalContext'; | ||
import Slot from '@/components/Slot'; | ||
import useContext from '@/hooks/useContext'; | ||
import { composeEventHandlers } from '@/libs/event'; | ||
import { composeRefs } from '@/libs/ref'; | ||
|
||
import { Button } from '../..'; | ||
|
||
type ComponentPropsWithoutRef<E extends React.ElementType> = React.ComponentPropsWithoutRef<E> & { | ||
asChild?: boolean; | ||
}; | ||
|
||
/** | ||
* SearchToggle | ||
* @returns | ||
*/ | ||
export const SearchToggle = React.forwardRef<HTMLButtonElement, ComponentPropsWithoutRef<'button'>>( | ||
({ asChild, onClick, ...props }, ref) => { | ||
const { setShowModal, setToggleElment } = useContext(PortalContext); | ||
const compRef = React.useRef<HTMLButtonElement | null>(null); | ||
|
||
const Comp = asChild ? Slot : Button; | ||
|
||
return ( | ||
<Comp | ||
ref={composeRefs(compRef, ref)} | ||
aria-haspopup="true" | ||
onClick={composeEventHandlers(onClick, () => { | ||
if (compRef.current) { | ||
const rect = compRef.current; | ||
setToggleElment(rect); | ||
} | ||
|
||
setShowModal(true); | ||
})} | ||
{...props} | ||
/> | ||
); | ||
}, | ||
); | ||
SearchToggle.displayName = 'SearchToggle'; |
Oops, something went wrong.