react-use#useKey TypeScript Examples
The following examples show how to use
react-use#useKey.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: comment-box.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
IssueCommentBox = (props: IProps) => {
const ESC_TARGET = 'issue-comment-box';
const { onSave = () => {}, editAuth } = props;
const loginUser = userStore.useStore((s) => s.loginUser);
const [state, updater] = useUpdate({
visible: false,
disableSave: true,
});
const valueRef = React.useRef('');
const focusRef = React.useRef(true);
const submit = () => {
const saveVal = valueRef.current.trim();
if (saveVal.length) {
onSave(saveVal);
valueRef.current = '';
focusRef.current = false;
updater.visible(false);
}
};
const enterEsc = useEscScope(ESC_TARGET, () => {
updater.visible(false);
});
// fn in useKey will not get newest state, so we need to use ref
useKey('Enter', (e) => {
if (focusRef.current && (isWin ? e.shiftKey : e.metaKey)) {
submit();
}
});
return (
<div
className={`absolute flex items-start z-10 rounded-sm p-4 shadow-card-lg bg-white bottom-0`}
style={{ left: 'calc(12% - 16px)', right: 'calc(12% - 16px)' }}
>
<UserInfo.RenderWithAvatar avatarSize="default" id={loginUser.id} showName={false} className="mr-3" />
{state.visible ? (
<>
<span className="issue-md-arrow" />
<div className="flex-1 overflow-auto">
<MarkdownEditor
value={valueRef.current}
placeholder={i18n.t('dop:Comment ({meta} + Enter to send, Esc to collapse)', {
meta: isWin ? 'Shift' : 'Cmd',
})}
onFocus={() => {
focusRef.current = true;
}}
onBlur={() => {
focusRef.current = false;
}}
autoFocus
className="w-full"
onChange={(val: string) => {
valueRef.current = val;
updater.disableSave(!val.trim().length);
}}
style={{ height: '200px' }}
maxLength={3000}
/>
<div className="mt-2">
<Button className="mr-3" type="primary" disabled={state.disableSave} onClick={() => submit()}>
{i18n.t('dop:Post')}
</Button>
<Button onClick={() => updater.visible(false)}>{i18n.t('dop:Collapse')}</Button>
</div>
</div>
</>
) : (
<div
className="issue-comment-arrow h-8 leading-8 bg-default-04 rounded-sm cursor-pointer px-3 flex-1 hover:text-purple-deep"
onClick={() => {
updater.visible(true);
enterEsc();
}}
>
{i18n.t('dop:Click here to comment')}
</div>
)}
</div>
);
}
Example #2
Source File: FilesystemBrowser.tsx From flood with GNU General Public License v3.0 | 4 votes |
FilesystemBrowser: FC<FilesystemBrowserProps> = memo(
({directory, selectable, onItemSelection, onYieldFocus}: FilesystemBrowserProps) => {
const [cursor, setCursor] = useState<number | null>(null);
const [errorResponse, setErrorResponse] = useState<{data?: NodeJS.ErrnoException} | null>(null);
const [separator, setSeparator] = useState<string>(directory.includes('/') ? '/' : '\\');
const [directories, setDirectories] = useState<string[] | null>(null);
const [files, setFiles] = useState<string[] | null>(null);
const listRef = useRef<HTMLUListElement>(null);
const lastSegmentIndex = directory.lastIndexOf(separator) + 1;
const currentDirectory = lastSegmentIndex > 0 ? directory.substr(0, lastSegmentIndex) : directory;
const lastSegment = directory.substr(lastSegmentIndex);
useEffect(() => {
if (!currentDirectory) {
return;
}
setCursor(null);
setDirectories(null);
setFiles(null);
FloodActions.fetchDirectoryList(currentDirectory)
.then(({files: fetchedFiles, directories: fetchedDirectories, separator: fetchedSeparator}) => {
setDirectories(fetchedDirectories);
setFiles(fetchedFiles);
setSeparator(fetchedSeparator);
setErrorResponse(null);
})
.catch(({response}) => {
setErrorResponse(response);
});
}, [currentDirectory]);
useEffect(() => {
if (listRef.current != null && cursor != null) {
const element = (listRef.current.children[cursor] as HTMLLIElement)?.children[0] as HTMLDivElement;
if (element?.tabIndex === 0) {
element.focus();
} else {
setCursor(
Array.from(listRef.current.children).findIndex((e) => (e.children[0] as HTMLDivElement).tabIndex === 0),
);
}
}
}, [cursor]);
useKey('ArrowUp', (e) => {
e.preventDefault();
setCursor((prevCursor) => {
if (prevCursor == null || prevCursor - 1 < 0) {
onYieldFocus?.();
return null;
}
return prevCursor - 1;
});
});
useKey('ArrowDown', (e) => {
e.preventDefault();
setCursor((prevCursor) => {
if (prevCursor != null) {
return prevCursor + 1;
}
return 0;
});
});
let errorMessage: string | null = null;
let listItems: ReactNodeArray = [];
if ((directories == null && selectable === 'directories') || (files == null && selectable === 'files')) {
errorMessage = 'filesystem.fetching';
}
if (errorResponse && errorResponse.data && errorResponse.data.code) {
errorMessage = MESSAGES[errorResponse.data.code as keyof typeof MESSAGES] || 'filesystem.error.unknown';
}
if (!directory) {
errorMessage = 'filesystem.error.no.input';
} else {
const parentDirectory = `${currentDirectory.split(separator).slice(0, -2).join(separator)}${separator}`;
const parentDirectoryElement = (
<li
css={[
listItemStyle,
listItemSelectableStyle,
{
'@media (max-width: 720px)': headerStyle,
},
]}
key={parentDirectory}
>
<button type="button" onClick={() => onItemSelection?.(parentDirectory, true)}>
<Arrow css={{transform: 'scale(0.75) rotate(180deg)'}} />
..
</button>
</li>
);
const isDirectorySelectable = selectable !== 'files';
const directoryMatched = lastSegment ? termMatch(directories, (subDirectory) => subDirectory, lastSegment) : [];
const inputDirectoryElement =
!directoryMatched.includes(lastSegment) && selectable === 'directories' && lastSegment && !errorMessage
? (() => {
const inputDestination = `${currentDirectory}${lastSegment}${separator}`;
return [
<li
css={[
listItemStyle,
listItemSelectableStyle,
{fontWeight: 'bold', '@media (max-width: 720px)': {display: 'none'}},
]}
key={inputDestination}
>
<button type="button" onClick={() => onItemSelection?.(inputDestination, false)}>
<FolderClosedOutlined />
<span css={{whiteSpace: 'pre-wrap'}}>{lastSegment}</span>
<em css={{fontWeight: 'lighter'}}>
{' - '}
<Trans id="filesystem.error.enoent" />
</em>
</button>
</li>,
];
})()
: [];
const directoryList: ReactNodeArray =
(directories?.length &&
sort(directories.slice())
.desc((subDirectory) => directoryMatched.includes(subDirectory))
.map((subDirectory) => {
const destination = `${currentDirectory}${subDirectory}${separator}`;
return (
<li
css={[
listItemStyle,
isDirectorySelectable ? listItemSelectableStyle : undefined,
directoryMatched.includes(subDirectory) ? {fontWeight: 'bold'} : undefined,
]}
key={destination}
>
<button
type="button"
disabled={!isDirectorySelectable}
tabIndex={isDirectorySelectable ? 0 : -1}
onClick={isDirectorySelectable ? () => onItemSelection?.(destination, true) : undefined}
>
<FolderClosedSolid />
{subDirectory}
</button>
</li>
);
})) ||
[];
const isFileSelectable = selectable !== 'directories';
const fileMatched = lastSegment ? termMatch(files, (file) => file, lastSegment) : [];
const fileList: ReactNodeArray =
(files?.length &&
sort(files.slice())
.desc((file) => fileMatched.includes(file))
.map((file) => {
const destination = `${currentDirectory}${file}`;
return (
<li
css={[
listItemStyle,
isFileSelectable ? listItemSelectableStyle : undefined,
fileMatched.includes(file) ? {fontWeight: 'bold'} : undefined,
]}
key={destination}
>
<button
type="button"
disabled={!isFileSelectable}
tabIndex={isFileSelectable ? 0 : -1}
onClick={isFileSelectable ? () => onItemSelection?.(destination, false) : undefined}
>
<File />
{file}
</button>
</li>
);
})) ||
[];
if (directoryList.length === 0 && fileList.length === 0 && !errorMessage) {
errorMessage = 'filesystem.empty.directory';
}
listItems = [parentDirectoryElement, ...inputDirectoryElement, ...directoryList, ...fileList];
}
return (
<div
css={{
color: foregroundColor,
listStyle: 'none',
padding: '3px 0px',
'.icon': {
fill: 'currentColor',
height: '14px',
width: '14px',
marginRight: `${25 * (1 / 5)}px`,
marginTop: '-3px',
verticalAlign: 'middle',
},
}}
>
{currentDirectory && (
<li
css={[
listItemStyle,
headerStyle,
itemPadding,
{
whiteSpace: 'pre-wrap',
wordBreak: 'break-all',
'.icon': {
transform: 'scale(0.9)',
marginTop: '-2px !important',
},
'@media (max-width: 720px)': {
display: 'none',
},
},
]}
>
<FolderOpenSolid />
{currentDirectory}
</li>
)}
<ul ref={listRef}>{listItems}</ul>
{errorMessage && (
<div css={[listItemStyle, itemPadding, {opacity: 1}]}>
<em>
<Trans id={errorMessage} />
</em>
</div>
)}
</div>
);
},
)
Example #3
Source File: Select.tsx From flood with GNU General Public License v3.0 | 4 votes |
Select: FC<SelectProps> = ({
additionalClassNames,
children,
defaultID,
disabled,
label,
labelOffset,
persistentPlaceholder,
priority,
shrink,
grow,
matchTriggerWidth,
width,
id,
menuAlign,
onOpen,
onClose,
onSelect,
}: SelectProps) => {
const menuRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const triggerRef = useRef<HTMLButtonElement>(null);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [selectedID, setSelectedID] = useState<string | number>(
defaultID ??
(
(children as ReactNodeArray)?.find(
(child) => (child as ReactElement<SelectItemProps>)?.props?.id != null,
) as ReactElement<SelectItemProps>
)?.props.id ??
'',
);
const classes = classnames('select form__element', additionalClassNames, {
'form__element--disabled': disabled,
'form__element--label-offset': labelOffset,
'select--is-open': isOpen,
});
const selectedItem = Children.toArray(children).find((child, index) => {
const item = child as ReactElement<SelectItemProps>;
return (
(persistentPlaceholder && item.props.isPlaceholder) ||
(!selectedID && index === 0) ||
item.props.id === selectedID
);
});
useKey('Escape', (event) => {
event.preventDefault();
setIsOpen(false);
});
useEvent(
'scroll',
(event: Event) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
},
window,
{capture: true},
);
useEffect(() => {
if (isOpen) {
onOpen?.();
} else {
onClose?.();
}
}, [isOpen, onClose, onOpen]);
useEffect(() => {
onSelect?.(selectedID);
if (inputRef.current != null) {
dispatchChangeEvent(inputRef.current);
}
}, [onSelect, selectedID]);
return (
<FormRowItem shrink={shrink} grow={grow} width={width}>
{label && (
<label className="form__element__label" htmlFor={`${id}`}>
{label}
</label>
)}
<div className={classes}>
<input
className="input input--hidden"
name={`${id}`}
onChange={() => undefined}
tabIndex={-1}
ref={inputRef}
type="text"
value={selectedID}
/>
<Button
additionalClassNames="select__button"
buttonRef={triggerRef}
addonPlacement="after"
onClick={() => {
if (!disabled) {
setIsOpen(!isOpen);
}
}}
priority={priority}
wrap={false}
>
<FormElementAddon className="select__indicator">
<Chevron />
</FormElementAddon>
{selectedItem && cloneElement(selectedItem as ReactElement, {isTrigger: true})}
</Button>
<Portal>
<ContextMenu
onOverlayClick={() => {
setIsOpen(!isOpen);
}}
isIn={isOpen}
matchTriggerWidth={matchTriggerWidth}
menuAlign={menuAlign}
ref={menuRef}
triggerRef={triggerRef}
>
{Children.toArray(children).reduce((accumulator: Array<ReactElement>, child) => {
const item = child as ReactElement<SelectItemProps>;
if (item.props.isPlaceholder) {
return accumulator;
}
accumulator.push(
cloneElement(child as ReactElement, {
onClick: (selection: string | number) => {
setIsOpen(false);
setSelectedID(selection);
},
isSelected: item.props.id === selectedID,
}),
);
return accumulator;
}, [])}
</ContextMenu>
</Portal>
</div>
</FormRowItem>
);
}
Example #4
Source File: AddInput.tsx From nextjs-hasura-fullstack with MIT License | 4 votes |
AddInput: React.FC<AddInputProps> = (props) => {
const {
onSubmit,
isCreating,
setIsCreating,
loading,
className,
label,
placeholder,
onClickAway,
} = props
const [value, setValue] = React.useState('')
const reset = React.useCallback(() => {
setIsCreating(false)
setValue('')
}, [])
const handleClickAway = React.useCallback(() => {
onClickAway(value)
reset()
}, [onClickAway, reset, value])
const ref = React.useRef<HTMLInputElement>(null)
useClickAway(ref, () => handleClickAway())
useKey('Escape', () => handleClickAway())
const handleSubmit = React.useCallback(() => {
onSubmit(value)
reset()
}, [onSubmit, reset, value])
const cls = clsx(className, `flex w-full h-10`)
return (
<div className={cls}>
{!isCreating && (
<Button
isLoading={loading}
isBlock
className={``}
onClick={() => {
setIsCreating(true)
}}
variant="light"
iconLeft={
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z"
clipRule="evenodd"
/>
</svg>
}
>
{label}
</Button>
)}
{isCreating && (
<div className={`flex items-center justify-between`} ref={ref}>
<Input
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true}
placeholder={placeholder ?? `New ${label}`}
value={value}
onChange={(e) => {
setValue(e.target.value)
}}
className={``}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSubmit()
}
}}
/>
<ButtonIcon
className={`flex-grow ml-2`}
size="base"
onClick={(e) => {
e.preventDefault()
handleSubmit()
}}
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
}
/>
</div>
)}
</div>
)
}