Files
website/src/lib/glossary.tsx
T
Christian ManivongandClaude Sonnet 5 d8892c53ce
CI / TypeScript — type-check (push) Successful in 9s
CI / Publish — build & push image (push) Successful in 7s
CI / Deploy — pull & restart on host (push) Successful in 2s
fix: glossary tooltip layout and viewport overflow
linkify() was returning a bare array, so its fragments landed as
direct children of flex <li> bullets — each text/link piece became
its own flex item and scrambled the reading order on wrap. Now
wrapped in a single <span> so text flows normally.

Tooltips also always centered under the term regardless of position,
overflowing off-screen near the viewport edges. GlossaryMark now
measures the anchor on hover and flips to a left- or right-aligned
placement when centering would push it out of view.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 18:46:54 +02:00

41 lines
1.3 KiB
TypeScript

import { Fragment } from 'react'
import GlossaryMark from '../components/GlossaryMark'
import { GLOSSARY } from '../glossary/terms'
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
const MATCH_TO_ID = new Map<string, string>()
for (const entry of GLOSSARY) {
for (const surface of entry.match) {
MATCH_TO_ID.set(surface, entry.id)
}
}
const ALL_SURFACES = [...MATCH_TO_ID.keys()].sort((a, b) => b.length - a.length)
const GLOSSARY_REGEX = new RegExp(`\\b(${ALL_SURFACES.map(escapeRegExp).join('|')})\\b`, 'g')
/** Wraps every occurrence of a known glossary term in `text` with a hoverable, linked <GlossaryMark>.
* Always returns a single inline element — never a bare array — so callers can safely place the
* result inside a `flex` container (e.g. a bulleted `<li>`) without each fragment becoming its own
* flex item and breaking the reading order. */
export function linkify(text: string): React.ReactNode {
const parts = text.split(GLOSSARY_REGEX)
if (parts.length === 1) return text
return (
<span>
{parts.map((part, i) => {
const id = MATCH_TO_ID.get(part)
if (id === undefined) return <Fragment key={i}>{part}</Fragment>
return (
<GlossaryMark key={i} id={id}>
{part}
</GlossaryMark>
)
})}
</span>
)
}