-
Notifications
You must be signed in to change notification settings - Fork 3.3k
fix(invite): fixed invite modal, fix search modal keyboard nav #879
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
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
219 changes: 219 additions & 0 deletions
219
.../sim/app/workspace/[workspaceId]/w/components/search-modal/hooks/use-search-navigation.ts
This file contains hidden or 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,219 @@ | ||||||||||||||||
| import { useCallback, useEffect, useRef, useState } from 'react' | ||||||||||||||||
|
|
||||||||||||||||
| export interface NavigationSection { | ||||||||||||||||
| id: string | ||||||||||||||||
| name: string | ||||||||||||||||
| type: 'grid' | 'list' | ||||||||||||||||
| items: any[] | ||||||||||||||||
| gridCols?: number // How many columns per row for grid sections | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| export interface NavigationPosition { | ||||||||||||||||
| sectionIndex: number | ||||||||||||||||
| itemIndex: number | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| export function useSearchNavigation(sections: NavigationSection[], isOpen: boolean) { | ||||||||||||||||
| const [position, setPosition] = useState<NavigationPosition>({ sectionIndex: 0, itemIndex: 0 }) | ||||||||||||||||
| const scrollRefs = useRef<Map<string, HTMLElement>>(new Map()) | ||||||||||||||||
| const lastPositionInSection = useRef<Map<string, number>>(new Map()) | ||||||||||||||||
|
|
||||||||||||||||
| // Reset position when sections change or modal opens | ||||||||||||||||
| useEffect(() => { | ||||||||||||||||
| if (sections.length > 0) { | ||||||||||||||||
| setPosition({ sectionIndex: 0, itemIndex: 0 }) | ||||||||||||||||
| } | ||||||||||||||||
| }, [sections, isOpen]) | ||||||||||||||||
|
|
||||||||||||||||
| const getCurrentItem = useCallback(() => { | ||||||||||||||||
| if (sections.length === 0 || position.sectionIndex >= sections.length) return null | ||||||||||||||||
|
|
||||||||||||||||
| const section = sections[position.sectionIndex] | ||||||||||||||||
| if (position.itemIndex >= section.items.length) return null | ||||||||||||||||
|
|
||||||||||||||||
| return { | ||||||||||||||||
| section, | ||||||||||||||||
| item: section.items[position.itemIndex], | ||||||||||||||||
| position, | ||||||||||||||||
| } | ||||||||||||||||
| }, [sections, position]) | ||||||||||||||||
|
|
||||||||||||||||
| const navigate = useCallback( | ||||||||||||||||
| (direction: 'up' | 'down' | 'left' | 'right') => { | ||||||||||||||||
| if (sections.length === 0) return | ||||||||||||||||
|
|
||||||||||||||||
| const currentSection = sections[position.sectionIndex] | ||||||||||||||||
| if (!currentSection) return | ||||||||||||||||
|
|
||||||||||||||||
| const isGridSection = currentSection.type === 'grid' | ||||||||||||||||
| const gridCols = currentSection.gridCols || 1 | ||||||||||||||||
|
|
||||||||||||||||
| setPosition((prevPosition) => { | ||||||||||||||||
| let newSectionIndex = prevPosition.sectionIndex | ||||||||||||||||
| let newItemIndex = prevPosition.itemIndex | ||||||||||||||||
|
|
||||||||||||||||
| if (direction === 'up') { | ||||||||||||||||
| if (isGridSection) { | ||||||||||||||||
| // In grid: up moves to previous row in same section, or previous section | ||||||||||||||||
| if (newItemIndex >= gridCols) { | ||||||||||||||||
| newItemIndex -= gridCols | ||||||||||||||||
| } else if (newSectionIndex > 0) { | ||||||||||||||||
| // Save current position before moving to previous section | ||||||||||||||||
| lastPositionInSection.current.set(currentSection.id, newItemIndex) | ||||||||||||||||
|
|
||||||||||||||||
| // Move to previous section | ||||||||||||||||
| newSectionIndex -= 1 | ||||||||||||||||
| const prevSection = sections[newSectionIndex] | ||||||||||||||||
|
|
||||||||||||||||
| // Restore last position in that section, or go to end | ||||||||||||||||
| const lastPos = lastPositionInSection.current.get(prevSection.id) | ||||||||||||||||
| if (lastPos !== undefined && lastPos < prevSection.items.length) { | ||||||||||||||||
| newItemIndex = lastPos | ||||||||||||||||
| } else { | ||||||||||||||||
| newItemIndex = Math.max(0, prevSection.items.length - 1) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } else { | ||||||||||||||||
| // In list: up moves to previous item, or previous section | ||||||||||||||||
| if (newItemIndex > 0) { | ||||||||||||||||
| newItemIndex -= 1 | ||||||||||||||||
| } else if (newSectionIndex > 0) { | ||||||||||||||||
| // Save current position before moving to previous section | ||||||||||||||||
| lastPositionInSection.current.set(currentSection.id, newItemIndex) | ||||||||||||||||
|
|
||||||||||||||||
| newSectionIndex -= 1 | ||||||||||||||||
| const prevSection = sections[newSectionIndex] | ||||||||||||||||
|
|
||||||||||||||||
| // Restore last position in that section, or go to end | ||||||||||||||||
| const lastPos = lastPositionInSection.current.get(prevSection.id) | ||||||||||||||||
| if (lastPos !== undefined && lastPos < prevSection.items.length) { | ||||||||||||||||
| newItemIndex = lastPos | ||||||||||||||||
| } else { | ||||||||||||||||
| newItemIndex = Math.max(0, prevSection.items.length - 1) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } else if (direction === 'down') { | ||||||||||||||||
| if (isGridSection) { | ||||||||||||||||
| // In grid: down moves to next row in same section, or next section | ||||||||||||||||
| const maxIndexInCurrentRow = Math.min( | ||||||||||||||||
| newItemIndex + gridCols, | ||||||||||||||||
| currentSection.items.length - 1 | ||||||||||||||||
| ) | ||||||||||||||||
|
Comment on lines
+99
to
+102
Contributor
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. style: This variable is declared but never used, consider removing it.
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| if (newItemIndex + gridCols < currentSection.items.length) { | ||||||||||||||||
| newItemIndex += gridCols | ||||||||||||||||
| } else if (newSectionIndex < sections.length - 1) { | ||||||||||||||||
| // Save current position before moving to next section | ||||||||||||||||
| lastPositionInSection.current.set(currentSection.id, newItemIndex) | ||||||||||||||||
|
|
||||||||||||||||
| // Move to next section | ||||||||||||||||
| newSectionIndex += 1 | ||||||||||||||||
| const nextSection = sections[newSectionIndex] | ||||||||||||||||
|
|
||||||||||||||||
| // Restore last position in next section, or start at beginning | ||||||||||||||||
| const lastPos = lastPositionInSection.current.get(nextSection.id) | ||||||||||||||||
| if (lastPos !== undefined && lastPos < nextSection.items.length) { | ||||||||||||||||
| newItemIndex = lastPos | ||||||||||||||||
| } else { | ||||||||||||||||
| newItemIndex = 0 | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } else { | ||||||||||||||||
| // In list: down moves to next item, or next section | ||||||||||||||||
| if (newItemIndex < currentSection.items.length - 1) { | ||||||||||||||||
| newItemIndex += 1 | ||||||||||||||||
| } else if (newSectionIndex < sections.length - 1) { | ||||||||||||||||
| // Save current position before moving to next section | ||||||||||||||||
| lastPositionInSection.current.set(currentSection.id, newItemIndex) | ||||||||||||||||
|
|
||||||||||||||||
| newSectionIndex += 1 | ||||||||||||||||
| const nextSection = sections[newSectionIndex] | ||||||||||||||||
|
|
||||||||||||||||
| // Restore last position in next section, or start at beginning | ||||||||||||||||
| const lastPos = lastPositionInSection.current.get(nextSection.id) | ||||||||||||||||
| if (lastPos !== undefined && lastPos < nextSection.items.length) { | ||||||||||||||||
| newItemIndex = lastPos | ||||||||||||||||
| } else { | ||||||||||||||||
| newItemIndex = 0 | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } else if (direction === 'left' && isGridSection) { | ||||||||||||||||
| // In grid: left moves to previous item in same row | ||||||||||||||||
| if (newItemIndex > 0) { | ||||||||||||||||
| const currentRow = Math.floor(newItemIndex / gridCols) | ||||||||||||||||
| const newIndex = newItemIndex - 1 | ||||||||||||||||
| const newRow = Math.floor(newIndex / gridCols) | ||||||||||||||||
|
|
||||||||||||||||
| // Only move if we stay in the same row | ||||||||||||||||
| if (currentRow === newRow) { | ||||||||||||||||
| newItemIndex = newIndex | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } else if (direction === 'right' && isGridSection) { | ||||||||||||||||
| // In grid: right moves to next item in same row | ||||||||||||||||
| if (newItemIndex < currentSection.items.length - 1) { | ||||||||||||||||
| const currentRow = Math.floor(newItemIndex / gridCols) | ||||||||||||||||
| const newIndex = newItemIndex + 1 | ||||||||||||||||
| const newRow = Math.floor(newIndex / gridCols) | ||||||||||||||||
|
|
||||||||||||||||
| // Only move if we stay in the same row | ||||||||||||||||
| if (currentRow === newRow) { | ||||||||||||||||
| newItemIndex = newIndex | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| return { sectionIndex: newSectionIndex, itemIndex: newItemIndex } | ||||||||||||||||
| }) | ||||||||||||||||
| }, | ||||||||||||||||
| [sections, position] | ||||||||||||||||
| ) | ||||||||||||||||
|
|
||||||||||||||||
| // Scroll selected item into view | ||||||||||||||||
| useEffect(() => { | ||||||||||||||||
| const current = getCurrentItem() | ||||||||||||||||
| if (!current) return | ||||||||||||||||
|
|
||||||||||||||||
| const { section, position: currentPos } = current | ||||||||||||||||
| const scrollContainer = scrollRefs.current.get(section.id) | ||||||||||||||||
|
|
||||||||||||||||
| if (scrollContainer) { | ||||||||||||||||
| const itemElement = scrollContainer.querySelector( | ||||||||||||||||
| `[data-nav-item="${section.id}-${currentPos.itemIndex}"]` | ||||||||||||||||
| ) as HTMLElement | ||||||||||||||||
|
|
||||||||||||||||
| if (itemElement) { | ||||||||||||||||
| // For horizontal scrolling sections (blocks/tools) | ||||||||||||||||
| if (section.type === 'grid') { | ||||||||||||||||
| const containerRect = scrollContainer.getBoundingClientRect() | ||||||||||||||||
| const itemRect = itemElement.getBoundingClientRect() | ||||||||||||||||
|
|
||||||||||||||||
| // Check if item is outside the visible area horizontally | ||||||||||||||||
| if (itemRect.left < containerRect.left) { | ||||||||||||||||
| scrollContainer.scrollTo({ | ||||||||||||||||
| left: scrollContainer.scrollLeft - (containerRect.left - itemRect.left + 20), | ||||||||||||||||
| behavior: 'smooth', | ||||||||||||||||
| }) | ||||||||||||||||
| } else if (itemRect.right > containerRect.right) { | ||||||||||||||||
| scrollContainer.scrollTo({ | ||||||||||||||||
| left: scrollContainer.scrollLeft + (itemRect.right - containerRect.right + 20), | ||||||||||||||||
| behavior: 'smooth', | ||||||||||||||||
| }) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| // Always ensure vertical visibility | ||||||||||||||||
| itemElement.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
| }, [getCurrentItem, position]) | ||||||||||||||||
|
|
||||||||||||||||
| return { | ||||||||||||||||
| navigate, | ||||||||||||||||
| getCurrentItem, | ||||||||||||||||
| scrollRefs, | ||||||||||||||||
| position, | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
style: Using
anytype reduces type safety. Consider defining a proper interface for navigation items.Context Used: Context - Avoid using type assertions to 'any' in TypeScript. Instead, ensure proper type definitions are used to maintain type safety. (link)