Skip to content

Commit

Permalink
✨ feat: 添加键盘控制上下移动
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Feb 14, 2021
1 parent f4c14bd commit a1d202d
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 112 deletions.
27 changes: 0 additions & 27 deletions src/contentScripts/searchBar/app/AnimatedHeight/index.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useSize } from 'ahooks';
import { useSpring, animated } from 'react-spring';

import { forwardRef } from 'react';

interface AnimatedHeightProps {
maxHeight: number;
}
const AnimatedHeight = forwardRef<HTMLDivElement, AnimatedHeightProps>(
({ children, maxHeight }, ref) => {
// @ts-ignore
const { height } = useSize(ref);

const animaHeightStyle = useSpring({
height: height || 0,
});

return (
<animated.div style={animaHeightStyle}>
<div
ref={ref}
style={{ maxHeight, overflowY: 'scroll', scrollBehavior: 'smooth' }}
>
{children}
</div>
</animated.div>
);
},
);

export default AnimatedHeight;
95 changes: 50 additions & 45 deletions src/contentScripts/searchBar/app/SearchResult/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import cls from 'classnames';
import { SearchService } from '../useSearchService';
import { useKeyboardResult } from './useKeyboardResult';
import RepoIcon from './RepoIcon';
import AnimatedHeight from './AnimatedHeight';

import styles from './style.less';

Expand All @@ -15,57 +16,61 @@ const SearchResult: FC = () => {
resultIndex,
handleResultIndex,
isFocusOnResult,
resultRef,
} = useKeyboardResult();

return (
<Skeleton
loading={loading}
title={false}
paragraph={{
rows: 4,
}}
active
className={styles.skeleton}
>
{result?.map((item, index) => {
const { title, info, id, url, target, type } = item;
// @ts-ignore
<AnimatedHeight maxHeight={400} ref={resultRef}>
<Skeleton
loading={loading}
title={false}
paragraph={{
rows: 4,
}}
active
className={styles.skeleton}
>
{result?.map((item, index) => {
const { title, info, id, url, target, type } = item;

return (
<div
key={id}
className={cls({
[styles.row]: true,
[styles.selected]: isFocusOnResult && resultIndex === index,
})}
onClick={() => {
window.open(url);
}}
onMouseEnter={() => {
handleResultIndex(index);
}}
>
<div className={styles.repo}>
{type === 'repo' ? (
<RepoIcon
type={(target as yuque.RepoTarget).type.toLowerCase()}
return (
<div
key={id}
className={cls({
[styles.row]: true,
[styles.selected]: isFocusOnResult && resultIndex === index,
})}
onClick={() => {
window.open(url);
}}
onMouseEnter={() => {
handleResultIndex(index);
}}
>
<div className={styles.repo}>
{type === 'repo' ? (
<RepoIcon
type={(target as yuque.RepoTarget).type.toLowerCase()}
/>
) : (
<RepoIcon type={type.toLowerCase()} />
)}
</div>
<div>
<div
className={styles.title}
dangerouslySetInnerHTML={{
__html: title,
}}
/>
) : (
<RepoIcon type={type.toLowerCase()} />
)}
<div className={styles.desc}>{info}</div>
</div>
</div>
<div>
<div
className={styles.title}
dangerouslySetInnerHTML={{
__html: title,
}}
/>
<div className={styles.desc}>{info}</div>
</div>
</div>
);
})}
</Skeleton>
);
})}
</Skeleton>
</AnimatedHeight>
);
};

Expand Down
107 changes: 71 additions & 36 deletions src/contentScripts/searchBar/app/SearchResult/useKeyboardResult.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useContext, useEffect, useState } from 'react';
import { useContext, useEffect, useRef, useState } from 'react';

import { SearchService } from '../useSearchService';
import { KeyboardService, handleIndex } from '../useKeyboardService';
Expand All @@ -17,65 +17,100 @@ export const useKeyboardResult = () => {
} = useContext(KeyboardService);

const [resultIndex, setResultIndex] = useState(0);
const resultRef = useRef<HTMLDivElement>(null);
console.log('real', resultIndex);

/**
* 处理结果区高度
*/
const scrollResultContainer = (index: number, back?: boolean) => {
const { current: ctn } = resultRef;
if (!ctn) return;

const step = 77;

if (index === result.length) {
ctn.scrollTop = 0;
}

if (!back) {
if (index > 3) {
ctn.scrollTop += step;
}
} else if (ctn.scrollTop > 0) {
ctn.scrollTop -= step;
}
};

/**
* 按 上下键切换选中的 result
* @param index
* @param back
*/
const switchResultIndex = (back?: boolean) => {
const newIndex = handleIndex(resultIndex, result.length, back);
setResultIndex(newIndex);
};

const keepResultIndex = () => {
if (resultIndex >= result.length) {
setResultIndex(result.length - 1);
}
};
const handleResultIndex = (index: number) => {
focusOnResult();
setResultIndex(index);
};

// 将焦点切换到 Options
const onKeyDown = useCallback(
(event: KeyboardEvent) => {
if (focusKey !== 'result') return;
switch (event.key) {
case 'Tab':
event.preventDefault();
switchOptionIndex(event.shiftKey);
break;
case 'ArrowDown':
event.preventDefault();
switchResultIndex();
break;
case 'ArrowUp':
event.preventDefault();
if (resultIndex === 0) {
focusOnOptions();
} else {
switchResultIndex(true);
}
break;
case 'ArrowRight':
switchOptionIndex();
break;
case 'ArrowLeft':
switchOptionIndex(true);
break;
case 'Escape':
focusOnInput();
break;
default:
}
},
[focusKey, resultIndex, switchOptionIndex],
);
const onKeyDown = (event: KeyboardEvent) => {
if (focusKey !== 'result') return;
console.log(resultIndex);

switch (event.key) {
case 'Tab':
event.preventDefault();
switchOptionIndex(event.shiftKey);
break;
case 'ArrowDown':
event.preventDefault();
scrollResultContainer(resultIndex + 1);
switchResultIndex();
break;
case 'ArrowUp':
event.preventDefault();

if (resultIndex === 0) {
focusOnOptions();
} else {
scrollResultContainer(resultIndex - 1, true);
switchResultIndex(true);
}
break;
case 'ArrowRight':
switchOptionIndex();
keepResultIndex();
break;
case 'ArrowLeft':
switchOptionIndex(true);
keepResultIndex();
break;
case 'Escape':
focusOnInput();
break;
default:
}
};

useEffect(() => {
document.addEventListener('keydown', onKeyDown);
return () => {
document.removeEventListener('keydown', onKeyDown);
};
}, [onKeyDown]);
}, [focusKey, result, resultIndex, keepResultIndex, switchOptionIndex]);

return {
resultRef,
resultIndex,
handleResultIndex,
isFocusOnResult: focusKey === 'result',
Expand Down
5 changes: 1 addition & 4 deletions src/contentScripts/searchBar/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { KeyboardService, useKeyboardService } from './useKeyboardService';

import SearchInput from './SearchInput';
import SearchResult from './SearchResult';
import AnimatedHeight from './AnimatedHeight';

import styles from './style.less';
import { isDev } from '@/utils';
Expand All @@ -31,9 +30,7 @@ const SearchBar: FC = () => {
<SearchInput />
</div>
<div className={styles.result}>
<AnimatedHeight maxHeight={400}>
<SearchResult />
</AnimatedHeight>
<SearchResult />
</div>
</>
) : (
Expand Down

0 comments on commit a1d202d

Please sign in to comment.