Skip to content

Commit

Permalink
fix(sidebar-text-highlight): Improve component to highlight text and …
Browse files Browse the repository at this point in the history
…use in navigation (#27894)
  • Loading branch information
phixMe authored and adamleithp committed Jan 29, 2025
1 parent dbda5b2 commit d5e5350
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 41 deletions.
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>
<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

0 comments on commit d5e5350

Please sign in to comment.