133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
import { useMemo, useState } from "react";
|
||
import type { RegexFlavourId } from "../regex/model/flavour";
|
||
import {
|
||
ECMASCRIPT_REFERENCE,
|
||
PCRE2_REFERENCE,
|
||
} from "../regex/reference/token-reference";
|
||
|
||
export function QuickReference({
|
||
onUsePattern,
|
||
onClose,
|
||
flavour = "ecmascript",
|
||
}: {
|
||
readonly onUsePattern: (syntax: string) => void;
|
||
readonly onClose?: () => void;
|
||
readonly flavour?: RegexFlavourId;
|
||
}) {
|
||
const [query, setQuery] = useState("");
|
||
const reference =
|
||
flavour === "pcre2" ? PCRE2_REFERENCE : ECMASCRIPT_REFERENCE;
|
||
const entries = useMemo(() => {
|
||
const normalizeSearchText = (value: string) =>
|
||
value
|
||
.toLocaleLowerCase()
|
||
.replaceAll(/[^\p{L}\p{N}]+/gu, " ")
|
||
.trim();
|
||
const needle = normalizeSearchText(query);
|
||
return needle
|
||
? reference.filter((entry) =>
|
||
normalizeSearchText(
|
||
[
|
||
entry.construct,
|
||
entry.syntax,
|
||
entry.explanation,
|
||
entry.example,
|
||
entry.flags.join(" "),
|
||
entry.source,
|
||
].join(" "),
|
||
).includes(needle),
|
||
)
|
||
: reference;
|
||
}, [query, reference]);
|
||
return (
|
||
<section
|
||
className="panel reference-panel"
|
||
aria-labelledby="reference-heading"
|
||
>
|
||
<header className="panel-heading">
|
||
<div>
|
||
<p className="eyebrow">
|
||
Original {flavour === "pcre2" ? "PCRE2" : "ECMAScript"} reference
|
||
</p>
|
||
<h2 id="reference-heading">Quick reference</h2>
|
||
</div>
|
||
<div className="panel-heading-actions">
|
||
<span>
|
||
{entries.length} of {reference.length} constructs
|
||
</span>
|
||
{onClose ? (
|
||
<button
|
||
type="button"
|
||
className="icon-button"
|
||
aria-label="Close quick reference"
|
||
data-dialog-initial-focus
|
||
onClick={onClose}
|
||
>
|
||
×
|
||
</button>
|
||
) : null}
|
||
</div>
|
||
</header>
|
||
<label className="stacked-control">
|
||
<span>Filter constructs</span>
|
||
<input
|
||
type="search"
|
||
value={query}
|
||
onChange={(event) => setQuery(event.target.value)}
|
||
/>
|
||
</label>
|
||
{entries.length > 0 ? (
|
||
<ul className="reference-list">
|
||
{entries.map((entry) => (
|
||
<li key={entry.id}>
|
||
<div>
|
||
<strong>{entry.construct}</strong>
|
||
<code>{entry.syntax}</code>
|
||
</div>
|
||
<p>{entry.explanation}</p>
|
||
<dl className="reference-meta">
|
||
<div>
|
||
<dt>Flavour</dt>
|
||
<dd>{entry.flavours.join(", ")}</dd>
|
||
</div>
|
||
<div>
|
||
<dt>Relevant flags</dt>
|
||
<dd>
|
||
{entry.flags.length > 0 ? entry.flags.join(", ") : "None"}
|
||
</dd>
|
||
</div>
|
||
<div>
|
||
<dt>Example</dt>
|
||
<dd>
|
||
<code>{entry.example}</code>
|
||
</dd>
|
||
</div>
|
||
<div>
|
||
<dt>Source</dt>
|
||
<dd>
|
||
<a href={entry.sourceUrl} target="_blank" rel="noreferrer">
|
||
{entry.source}
|
||
</a>
|
||
</dd>
|
||
</div>
|
||
</dl>
|
||
{entry.caveat ? <small>{entry.caveat}</small> : null}
|
||
<button
|
||
type="button"
|
||
className="secondary-button"
|
||
onClick={() => onUsePattern(entry.example)}
|
||
>
|
||
Use example as pattern
|
||
</button>
|
||
</li>
|
||
))}
|
||
</ul>
|
||
) : (
|
||
<p className="empty-state" role="status">
|
||
No reference constructs match “{query.trim()}”.
|
||
</p>
|
||
)}
|
||
</section>
|
||
);
|
||
}
|