-
Notifications
You must be signed in to change notification settings - Fork 37
feat(Tour): feat: support keyboard operation #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
--- | ||
title: keyboard | ||
nav: | ||
title: Demo | ||
path: /demo | ||
--- | ||
|
||
<code src="../examples/keyboard.tsx"></code> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import React, { useRef } from 'react'; | ||
import Tour from '../../src/index'; | ||
import './basic.less'; | ||
|
||
const App = () => { | ||
const firstBtnRef = useRef<HTMLButtonElement>(null); | ||
const secondBtnRef = useRef<HTMLButtonElement>(null); | ||
const thirdBtnRef = useRef<HTMLButtonElement>(null); | ||
return ( | ||
<React.StrictMode> | ||
<div style={{ margin: 20 }}> | ||
<div> | ||
<button | ||
className="ant-target" | ||
ref={firstBtnRef} | ||
> | ||
One | ||
</button> | ||
<button className="ant-target" ref={secondBtnRef}> | ||
Two | ||
</button> | ||
<button className="ant-target" ref={thirdBtnRef}> | ||
Three | ||
</button> | ||
</div> | ||
|
||
<div style={{ height: 200 }} /> | ||
|
||
<Tour | ||
defaultCurrent={0} | ||
keyboard={true} | ||
steps={[ | ||
{ | ||
title: 'One', | ||
target: () => firstBtnRef.current, | ||
}, | ||
{ | ||
title: 'Two', | ||
target: () => secondBtnRef.current, | ||
}, | ||
{ | ||
title: 'Three', | ||
target: () => thirdBtnRef.current, | ||
}, | ||
]} | ||
/> | ||
</div> | ||
</React.StrictMode> | ||
); | ||
}; | ||
|
||
export default App; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,6 +58,7 @@ const Tour: React.FC<TourProps> = props => { | |
className, | ||
style, | ||
getPopupContainer, | ||
keyboard = false, | ||
...restProps | ||
} = props; | ||
|
||
|
@@ -157,17 +158,58 @@ const Tour: React.FC<TourProps> = props => { | |
return getPlacements(arrowPointAtCenter); | ||
}, [builtinPlacements, arrowPointAtCenter]); | ||
|
||
// ================= close ======================== | ||
const handleClose = () => { | ||
setMergedOpen(false); | ||
onClose?.(mergedCurrent); | ||
}; | ||
|
||
// ================= Keyboard Navigation ============== | ||
const handleKeyDown = React.useCallback((e: KeyboardEvent) => { | ||
if (!mergedOpen) { | ||
return; | ||
} | ||
if (e.key === 'ArrowLeft') { | ||
if (mergedCurrent <= 0) { | ||
return; | ||
} | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
onInternalChange(mergedCurrent - 1); | ||
return; | ||
} | ||
if (e.key === 'ArrowRight') { | ||
if (mergedCurrent >= steps.length - 1) { | ||
return; | ||
} | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
onInternalChange(mergedCurrent + 1); | ||
return; | ||
} | ||
if (e.key === 'Escape') { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
handleClose(); | ||
return; | ||
} | ||
}, [mergedCurrent, mergedOpen, steps.length]); | ||
|
||
|
||
React.useEffect(() => { | ||
if (keyboard) { | ||
document.addEventListener('keydown', handleKeyDown); | ||
} | ||
return () => { | ||
document.removeEventListener('keydown', handleKeyDown); | ||
}; | ||
}, [handleKeyDown, keyboard]); | ||
Comment on lines
202
to
213
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Attaching the event listener directly to
Comment on lines
202
to
213
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 多个 Tour 实例可能导致键盘事件冲突。 如果页面上同时挂载多个启用 可能的解决方案:
示例修复(检查 Tour 是否打开): const handleKeyDown = React.useCallback((e: KeyboardEvent) => {
- if (!mergedOpen) {
+ if (!mergedOpen || !keyboard) {
return;
} 并在文档中说明此限制。
🤖 Prompt for AI Agents
|
||
|
||
// ========================= Render ========================= | ||
// Skip if not init yet | ||
if (targetElement === undefined || !hasOpened) { | ||
return null; | ||
} | ||
|
||
const handleClose = () => { | ||
setMergedOpen(false); | ||
onClose?.(mergedCurrent); | ||
}; | ||
|
||
const getPopupElement = () => ( | ||
<TourStep | ||
styles={styles} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
示例缺少 Tour 可见性控制。
当前示例中 Tour 组件始终处于打开状态(依赖
defaultCurrent
但没有open
prop 控制)。这可能让用户难以理解如何在实际应用中初始打开和关闭 Tour。建议添加状态控制和触发按钮来演示完整的使用场景:
📝 Committable suggestion
🤖 Prompt for AI Agents