Skip to content
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

fix(sidebar-text-highlight): Improve component to highlight text and use in navigation #27894

Merged
merged 4 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions frontend/src/layout/navigation-3000/Navigation.scss
Original file line number Diff line number Diff line change
Expand Up @@ -619,11 +619,6 @@
.SidebarListItem__icon {
flex-shrink: 0;
}

.SidebarListItem__name {
overflow: hidden;
text-overflow: ellipsis;
}
}

.SidebarListItem__rename {
Expand Down
29 changes: 29 additions & 0 deletions frontend/src/layout/navigation-3000/components/SearchHighlight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
interface Props {
string: string
substring: string
className?: string
}

/**
* Highlight a substring within a string (case-insensitive)
* @param string - the aggregate string to search within (e.g. "Hello, world!")
* @param substring - the substring to search for (e.g. "world")
* @param className - additional classes to apply to the component
*/
export default function SearchHighlight({ string, substring, className }: Props): JSX.Element {
const parts = string.split(new RegExp(`(${substring})`, 'gi'))
return (
<div className={`truncate ${className}`}>
{parts.map((part, index) => (
<span
key={index}
className={`text-xs ${
part.toLowerCase() === substring.toLowerCase() ? 'bg-primary-highlight bg-opacity-60' : ''
}`}
>
{part}
</span>
))}
</div>
)
}
40 changes: 4 additions & 36 deletions frontend/src/layout/navigation-3000/components/SidebarList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import { AutoSizer } from 'react-virtualized/dist/es/AutoSizer'
import { InfiniteLoader } from 'react-virtualized/dist/es/InfiniteLoader'
import { List, ListProps } from 'react-virtualized/dist/es/List'

import SearchHighlight from '~/layout/navigation-3000/components/SearchHighlight'

import { ITEM_KEY_PART_SEPARATOR, navigation3000Logic } from '../navigationLogic'
import {
BasicListItem,
Expand Down Expand Up @@ -305,15 +307,15 @@ function SidebarListItem({ item, validateName, active, style }: SidebarListItemP
style={{ '--depth': item.depth } as React.CSSProperties}
>
{item.icon && <div className="SidebarListItem__icon">{item.icon}</div>}
<h5 className="SidebarListItem__name">{item.name}</h5>
phixMe marked this conversation as resolved.
Show resolved Hide resolved
<SearchHighlight string={item.name} substring={navigation3000Logic.values.searchTerm} />
</div>
)
} else if (!save || (!isItemTentative(item) && newName === null)) {
if (isItemTentative(item)) {
throw new Error('Tentative items should not be rendered in read mode')
}
let formattedName = item.searchMatch?.nameHighlightRanges?.length ? (
<TextWithHighlights ranges={item.searchMatch.nameHighlightRanges}>{item.name}</TextWithHighlights>
<SearchHighlight string={item.name} substring={navigation3000Logic.values.searchTerm} />
) : (
item.name
)
Expand Down Expand Up @@ -495,40 +497,6 @@ function SidebarListItem({ item, validateName, active, style }: SidebarListItemP
)
}

/** Text with specified ranges highlighted by increased font weight. Great for higlighting search term matches. */
function TextWithHighlights({
children,
ranges,
}: {
children: string
ranges: readonly [number, number][]
}): JSX.Element {
const segments: JSX.Element[] = []
let previousBoldEnd = 0
let segmentIndex = 0
// Divide the item name into bold and regular segments
for (let i = 0; i < ranges.length; i++) {
const [currentBoldStart, currentBoldEnd] = ranges[i]
if (currentBoldStart > previousBoldEnd) {
segments.push(
<React.Fragment key={segmentIndex}>{children.slice(previousBoldEnd, currentBoldStart)}</React.Fragment>
)
segmentIndex++
}
segments.push(<b key={segmentIndex}>{children.slice(currentBoldStart, currentBoldEnd)}</b>)
segmentIndex++
previousBoldEnd = currentBoldEnd
}
// If there is a non-highlighted segment left at the end, add it now
if (previousBoldEnd < children.length) {
segments.push(
<React.Fragment key={segmentIndex}>{children.slice(previousBoldEnd, children.length)}</React.Fragment>
)
}

return <>{segments}</>
}

/** Smart rendering of list item extra context. */
function ExtraContext({ data }: { data: ExtraListItemContext }): JSX.Element {
return isDayjs(data) ? <TZLabel time={data} /> : <>{data}</>
Expand Down
Loading