@mailwoman/react 0.0.1 → 7.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -4
- package/common/ClientOnly.tsx +30 -0
- package/common/CopyButton.tsx +44 -0
- package/common/KindBadge.tsx +44 -0
- package/common/LoadingIndicator.tsx +104 -0
- package/common/PresetChips.tsx +43 -0
- package/common/cx.ts +34 -0
- package/common/useClipboard.ts +73 -0
- package/common/useDebouncedValue.ts +23 -0
- package/index.ts +86 -4
- package/out/common/ClientOnly.d.ts +22 -0
- package/out/common/ClientOnly.d.ts.map +1 -0
- package/out/common/ClientOnly.js +20 -0
- package/out/common/ClientOnly.js.map +1 -0
- package/out/common/CopyButton.d.ts +21 -0
- package/out/common/CopyButton.d.ts.map +1 -0
- package/out/common/CopyButton.js +7 -0
- package/out/common/CopyButton.js.map +1 -0
- package/out/common/KindBadge.d.ts +24 -0
- package/out/common/KindBadge.d.ts.map +1 -0
- package/out/common/KindBadge.js +6 -0
- package/out/common/KindBadge.js.map +1 -0
- package/out/common/LoadingIndicator.d.ts +27 -0
- package/out/common/LoadingIndicator.d.ts.map +1 -0
- package/out/common/LoadingIndicator.js +41 -0
- package/out/common/LoadingIndicator.js.map +1 -0
- package/out/common/PresetChips.d.ts +22 -0
- package/out/common/PresetChips.d.ts.map +1 -0
- package/out/common/PresetChips.js +5 -0
- package/out/common/PresetChips.js.map +1 -0
- package/out/common/cx.d.ts +13 -0
- package/out/common/cx.d.ts.map +1 -0
- package/out/common/cx.js +28 -0
- package/out/common/cx.js.map +1 -0
- package/out/common/useClipboard.d.ts +16 -0
- package/out/common/useClipboard.d.ts.map +1 -0
- package/out/common/useClipboard.js +56 -0
- package/out/common/useClipboard.js.map +1 -0
- package/out/common/useDebouncedValue.d.ts +11 -0
- package/out/common/useDebouncedValue.d.ts.map +1 -0
- package/out/common/useDebouncedValue.js +19 -0
- package/out/common/useDebouncedValue.js.map +1 -0
- package/out/index.d.ts +57 -4
- package/out/index.d.ts.map +1 -1
- package/out/index.js +37 -4
- package/out/index.js.map +1 -1
- package/out/pipeline/CandidatePicker.d.ts +17 -0
- package/out/pipeline/CandidatePicker.d.ts.map +1 -0
- package/out/pipeline/CandidatePicker.js +6 -0
- package/out/pipeline/CandidatePicker.js.map +1 -0
- package/out/pipeline/ComponentTable.d.ts +14 -0
- package/out/pipeline/ComponentTable.d.ts.map +1 -0
- package/out/pipeline/ComponentTable.js +6 -0
- package/out/pipeline/ComponentTable.js.map +1 -0
- package/out/pipeline/ConfidenceCell.d.ts +13 -0
- package/out/pipeline/ConfidenceCell.d.ts.map +1 -0
- package/out/pipeline/ConfidenceCell.js +22 -0
- package/out/pipeline/ConfidenceCell.js.map +1 -0
- package/out/pipeline/PipelineExplorer.d.ts +25 -0
- package/out/pipeline/PipelineExplorer.d.ts.map +1 -0
- package/out/pipeline/PipelineExplorer.js +27 -0
- package/out/pipeline/PipelineExplorer.js.map +1 -0
- package/out/pipeline/QueryForm.d.ts +19 -0
- package/out/pipeline/QueryForm.d.ts.map +1 -0
- package/out/pipeline/QueryForm.js +9 -0
- package/out/pipeline/QueryForm.js.map +1 -0
- package/out/pipeline/ResolvedPlace.d.ts +16 -0
- package/out/pipeline/ResolvedPlace.d.ts.map +1 -0
- package/out/pipeline/ResolvedPlace.js +14 -0
- package/out/pipeline/ResolvedPlace.js.map +1 -0
- package/out/pipeline/copy.d.ts +11 -0
- package/out/pipeline/copy.d.ts.map +1 -0
- package/out/pipeline/copy.js +31 -0
- package/out/pipeline/copy.js.map +1 -0
- package/out/pipeline/presets.d.ts +12 -0
- package/out/pipeline/presets.d.ts.map +1 -0
- package/out/pipeline/presets.js +21 -0
- package/out/pipeline/presets.js.map +1 -0
- package/out/pipeline/types.d.ts +110 -0
- package/out/pipeline/types.d.ts.map +1 -0
- package/out/pipeline/types.js +12 -0
- package/out/pipeline/types.js.map +1 -0
- package/out/pipeline/useParsePipeline.d.ts +34 -0
- package/out/pipeline/useParsePipeline.d.ts.map +1 -0
- package/out/pipeline/useParsePipeline.js +55 -0
- package/out/pipeline/useParsePipeline.js.map +1 -0
- package/out/poi/AbstainPanel.d.ts +14 -0
- package/out/poi/AbstainPanel.d.ts.map +1 -0
- package/out/poi/AbstainPanel.js +5 -0
- package/out/poi/AbstainPanel.js.map +1 -0
- package/out/poi/LiveResultsBlock.d.ts +21 -0
- package/out/poi/LiveResultsBlock.d.ts.map +1 -0
- package/out/poi/LiveResultsBlock.js +7 -0
- package/out/poi/LiveResultsBlock.js.map +1 -0
- package/out/poi/OverpassBlock.d.ts +15 -0
- package/out/poi/OverpassBlock.d.ts.map +1 -0
- package/out/poi/OverpassBlock.js +11 -0
- package/out/poi/OverpassBlock.js.map +1 -0
- package/out/poi/POIExplorer.d.ts +31 -0
- package/out/poi/POIExplorer.d.ts.map +1 -0
- package/out/poi/POIExplorer.js +43 -0
- package/out/poi/POIExplorer.js.map +1 -0
- package/out/poi/QueryInput.d.ts +17 -0
- package/out/poi/QueryInput.d.ts.map +1 -0
- package/out/poi/QueryInput.js +5 -0
- package/out/poi/QueryInput.js.map +1 -0
- package/out/poi/SubjectPanel.d.ts +16 -0
- package/out/poi/SubjectPanel.d.ts.map +1 -0
- package/out/poi/SubjectPanel.js +12 -0
- package/out/poi/SubjectPanel.js.map +1 -0
- package/out/poi/runtime.d.ts +43 -0
- package/out/poi/runtime.d.ts.map +1 -0
- package/out/poi/runtime.js +67 -0
- package/out/poi/runtime.js.map +1 -0
- package/out/poi/types.d.ts +113 -0
- package/out/poi/types.d.ts.map +1 -0
- package/out/poi/types.js +12 -0
- package/out/poi/types.js.map +1 -0
- package/out/poi/usePOISearch.d.ts +42 -0
- package/out/poi/usePOISearch.d.ts.map +1 -0
- package/out/poi/usePOISearch.js +158 -0
- package/out/poi/usePOISearch.js.map +1 -0
- package/package.json +52 -8
- package/pipeline/CandidatePicker.tsx +45 -0
- package/pipeline/ComponentTable.tsx +41 -0
- package/pipeline/ConfidenceCell.tsx +33 -0
- package/pipeline/PipelineExplorer.tsx +148 -0
- package/pipeline/QueryForm.tsx +52 -0
- package/pipeline/ResolvedPlace.tsx +50 -0
- package/pipeline/copy.ts +37 -0
- package/pipeline/presets.ts +24 -0
- package/pipeline/types.ts +118 -0
- package/pipeline/useParsePipeline.ts +84 -0
- package/poi/AbstainPanel.tsx +22 -0
- package/poi/LiveResultsBlock.tsx +70 -0
- package/poi/OverpassBlock.tsx +37 -0
- package/poi/POIExplorer.tsx +133 -0
- package/poi/QueryInput.tsx +26 -0
- package/poi/SubjectPanel.tsx +79 -0
- package/poi/runtime.ts +78 -0
- package/poi/types.ts +112 -0
- package/poi/usePOISearch.ts +234 -0
- package/styles.css +648 -0
package/README.md
CHANGED
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
# @mailwoman/react
|
|
2
2
|
|
|
3
3
|
React components + headless hooks for [mailwoman](https://mailwoman.sister.software) — the
|
|
4
|
-
parse/geocode/POI explorers from the docs site, packaged for use in any React app.
|
|
4
|
+
parse/geocode/POI explorers from the docs site, decomposed and packaged for use in any React app.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
## What's in the box
|
|
7
|
+
|
|
8
|
+
Two composed explorers, plus the small presentational units and headless hooks they decompose into:
|
|
9
|
+
|
|
10
|
+
- **`POIExplorer`** — a self-contained POI-intent tester (classify → subject → OverpassQL). The intent
|
|
11
|
+
path runs entirely offline over `@mailwoman/kind-classifier`, `@mailwoman/poi-taxonomy`, and
|
|
12
|
+
`@mailwoman/query-shape` — no weights, no network. A live poi.db search is opt-in via an injected
|
|
13
|
+
`runLiveSearch` probe, so the httpvfs/worker machinery never enters this package's graph.
|
|
14
|
+
- **`PipelineExplorer`** — a parse + resolve tester driven by an **injected `PipelineRuntime`**. The
|
|
15
|
+
host supplies `runParse` (compute shape → classify → resolve) and the heavy visualizers as `panels`,
|
|
16
|
+
keeping onnxruntime-web, sql.js-httpvfs, and node builtins out of this package entirely.
|
|
17
|
+
|
|
18
|
+
The headless hooks — `usePOISearch`, `useParsePipeline` — own the state machines; the presentational
|
|
19
|
+
units (`QueryInput`, `SubjectPanel`, `OverpassBlock`, `LiveResultsBlock`, `ComponentTable`,
|
|
20
|
+
`ResolvedPlace`, `CandidatePicker`, `KindBadge`, `LoadingIndicator`, …) are pure and prop-driven.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { POIExplorer, PipelineExplorer } from "@mailwoman/react"
|
|
26
|
+
import "@mailwoman/react/styles.css"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Styling ships as a standalone stylesheet (`@mailwoman/react/styles.css`) — plain, `mw-`-prefixed, and
|
|
30
|
+
Infima-token-aware, so it looks right both inside Docusaurus and standalone. No component imports CSS,
|
|
31
|
+
so the bare package import stays node-safe.
|
|
32
|
+
|
|
33
|
+
## Development
|
|
34
|
+
|
|
35
|
+
- `yarn workspace @mailwoman/react storybook` — Storybook (Vite) for every unit + the composed
|
|
36
|
+
explorers, with mocked runtimes (no network/db).
|
|
37
|
+
- `yarn workspace @mailwoman/react test:browser` — Vitest browser-mode component + hook tests
|
|
38
|
+
(Playwright / headless Chromium).
|
|
9
39
|
|
|
10
40
|
## License
|
|
11
41
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `ClientOnly` — a portable SSR boundary. The docs explorers wrapped Docusaurus's `<BrowserOnly>`;
|
|
7
|
+
* this is the framework-agnostic equivalent so the components stay usable in any React app (Next,
|
|
8
|
+
* Remix, Docusaurus). It renders `fallback` on the server and the first client paint, then swaps to
|
|
9
|
+
* `children()` once mounted — keeping timers, clipboard, and dynamic imports off the server render.
|
|
10
|
+
*
|
|
11
|
+
* The mount flag is derived from a `useEffect` that fires only in the browser; this is a legitimate
|
|
12
|
+
* external-sync effect (bridging the server/client rendering boundary), not derived state.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { type ReactNode, useEffect, useState } from "react"
|
|
16
|
+
|
|
17
|
+
export interface ClientOnlyProps {
|
|
18
|
+
/** Rendered once mounted in the browser. A thunk so its (browser-only) imports never run on the server. */
|
|
19
|
+
children: () => ReactNode
|
|
20
|
+
/** Rendered on the server and until the first client mount. */
|
|
21
|
+
fallback?: ReactNode
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ClientOnly({ children, fallback = null }: ClientOnlyProps): ReactNode {
|
|
25
|
+
const [mounted, setMounted] = useState(false)
|
|
26
|
+
|
|
27
|
+
useEffect(() => setMounted(true), [])
|
|
28
|
+
|
|
29
|
+
return mounted ? children() : fallback
|
|
30
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `CopyButton` — a small presentational button that copies a string and shows a transient checkmark.
|
|
7
|
+
* Wraps {@link useClipboard}. Used by both explorers (OverpassQL copy, parse-JSON copy).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from "react"
|
|
11
|
+
|
|
12
|
+
import { useClipboard } from "./useClipboard.ts"
|
|
13
|
+
|
|
14
|
+
export interface CopyButtonProps {
|
|
15
|
+
/** The text to copy. If a thunk, it's evaluated at click time (so callers can copy the latest value). */
|
|
16
|
+
value: string | (() => string)
|
|
17
|
+
/** Label in the idle state. @default "Copy" */
|
|
18
|
+
label?: ReactNode
|
|
19
|
+
/** Label shown briefly after a successful copy. @default "✓ Copied" */
|
|
20
|
+
copiedLabel?: ReactNode
|
|
21
|
+
className?: string
|
|
22
|
+
disabled?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function CopyButton({
|
|
26
|
+
value,
|
|
27
|
+
label = "Copy",
|
|
28
|
+
copiedLabel = "✓ Copied",
|
|
29
|
+
className = "mw-btn",
|
|
30
|
+
disabled,
|
|
31
|
+
}: CopyButtonProps): ReactNode {
|
|
32
|
+
const { copied, copy } = useClipboard()
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
className={className}
|
|
38
|
+
disabled={disabled}
|
|
39
|
+
onClick={() => copy(typeof value === "function" ? value() : value)}
|
|
40
|
+
>
|
|
41
|
+
{copied ? copiedLabel : label}
|
|
42
|
+
</button>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `KindBadge` — compact display of the Stage 2.5 kind-classifier verdict: the top kind + confidence
|
|
7
|
+
* as a pill, expanding to the alternatives on click. Shared by both explorers. The result type is
|
|
8
|
+
* kept structural (`kind: string`) so both the POI query classifier's `QueryKindResult` and the
|
|
9
|
+
* pipeline's projection satisfy it without a hard type dependency.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ReactNode } from "react"
|
|
13
|
+
|
|
14
|
+
export interface KindBadgeResult {
|
|
15
|
+
kind: string
|
|
16
|
+
confidence: number
|
|
17
|
+
alternatives: ReadonlyArray<{ kind: string; confidence: number }>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface KindBadgeProps {
|
|
21
|
+
kindResult: KindBadgeResult
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const formatPct = (n: number): string => `${Math.round(n * 100)}%`
|
|
25
|
+
|
|
26
|
+
export function KindBadge({ kindResult }: KindBadgeProps): ReactNode {
|
|
27
|
+
return (
|
|
28
|
+
<details className="mw-kind">
|
|
29
|
+
<summary className="mw-kind__summary">
|
|
30
|
+
<strong>Kind:</strong> <code>{kindResult.kind}</code>{" "}
|
|
31
|
+
<span className="mw-kind__confidence">({formatPct(kindResult.confidence)})</span>
|
|
32
|
+
</summary>
|
|
33
|
+
{kindResult.alternatives.length > 0 ? (
|
|
34
|
+
<ul className="mw-kind__alternatives">
|
|
35
|
+
{kindResult.alternatives.map((alt) => (
|
|
36
|
+
<li key={alt.kind}>
|
|
37
|
+
<code>{alt.kind}</code> <span className="mw-kind__confidence">({formatPct(alt.confidence)})</span>
|
|
38
|
+
</li>
|
|
39
|
+
))}
|
|
40
|
+
</ul>
|
|
41
|
+
) : null}
|
|
42
|
+
</details>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* LoadingIndicator — pure-CSS loading states for three UX patterns: `pulse` (skeleton bars),
|
|
7
|
+
* `spinner` (rotation ring), and `staged` (a step list with checkmarks). SSR-safe: every animation
|
|
8
|
+
* is CSS-driven, so the component renders identically on server and client. Ported from the docs
|
|
9
|
+
* component onto plain (`mw-`-prefixed) class names.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { memo, type ReactNode } from "react"
|
|
13
|
+
|
|
14
|
+
import { cx } from "./cx.ts"
|
|
15
|
+
|
|
16
|
+
export type LoadingMode = "pulse" | "spinner" | "staged"
|
|
17
|
+
|
|
18
|
+
export interface LoadingIndicatorProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
19
|
+
/** @default "spinner" */
|
|
20
|
+
mode?: LoadingMode
|
|
21
|
+
/** Optional label; in staged mode it renders above the step list. */
|
|
22
|
+
label?: string
|
|
23
|
+
/** Number of skeleton bars (pulse mode). @default 3 */
|
|
24
|
+
barCount?: number
|
|
25
|
+
/** Spinner ring size. @default "medium" */
|
|
26
|
+
size?: "small" | "medium" | "large"
|
|
27
|
+
/** Step labels (staged mode) — one row each. */
|
|
28
|
+
steps?: string[]
|
|
29
|
+
/** 0-based index of the active step; earlier steps are complete, later ones pending. @default -1 */
|
|
30
|
+
activeStep?: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const SIZE_CLASS: Record<NonNullable<LoadingIndicatorProps["size"]>, string> = {
|
|
34
|
+
small: "mw-spinner--small",
|
|
35
|
+
medium: "",
|
|
36
|
+
large: "mw-spinner--large",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const LoadingIndicator = memo<LoadingIndicatorProps>(function LoadingIndicator({
|
|
40
|
+
mode = "spinner",
|
|
41
|
+
label,
|
|
42
|
+
barCount = 3,
|
|
43
|
+
size = "medium",
|
|
44
|
+
steps,
|
|
45
|
+
activeStep = -1,
|
|
46
|
+
className,
|
|
47
|
+
...rest
|
|
48
|
+
}) {
|
|
49
|
+
return (
|
|
50
|
+
<div className={cx("mw-loading", className)} role="status" aria-label={label ?? "Loading"} {...rest}>
|
|
51
|
+
{mode === "pulse" ? <PulseBars count={barCount} /> : null}
|
|
52
|
+
{mode === "spinner" ? <SpinnerRing size={size} /> : null}
|
|
53
|
+
{mode === "staged" ? <StagedProgress steps={steps} activeStep={activeStep} /> : null}
|
|
54
|
+
{label ? <span className="mw-loading__label">{label}</span> : null}
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const PulseBars = memo<{ count: number }>(function PulseBars({ count }) {
|
|
60
|
+
return (
|
|
61
|
+
<div className="mw-pulse">
|
|
62
|
+
{Array.from({ length: count }, (_, i) => (
|
|
63
|
+
<div key={i} className="mw-pulse__bar" />
|
|
64
|
+
))}
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const SpinnerRing = memo<{ size: NonNullable<LoadingIndicatorProps["size"]> }>(function SpinnerRing({ size }) {
|
|
70
|
+
return <div className={cx("mw-spinner", SIZE_CLASS[size])} />
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const StagedProgress = memo<{ steps?: string[]; activeStep: number }>(function StagedProgress({ steps, activeStep }) {
|
|
74
|
+
if (!steps || steps.length === 0) return null
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<ul className="mw-staged">
|
|
78
|
+
{steps.map((step, i): ReactNode => {
|
|
79
|
+
const isComplete = i < activeStep
|
|
80
|
+
const isActive = i === activeStep
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<li
|
|
84
|
+
key={i}
|
|
85
|
+
className={cx("mw-staged__step", {
|
|
86
|
+
"mw-staged__step--complete": isComplete,
|
|
87
|
+
"mw-staged__step--active": isActive,
|
|
88
|
+
"mw-staged__step--pending": !isComplete && !isActive,
|
|
89
|
+
})}
|
|
90
|
+
>
|
|
91
|
+
<span className="mw-staged__icon">
|
|
92
|
+
{isComplete ? (
|
|
93
|
+
<span className="mw-staged__check" />
|
|
94
|
+
) : isActive ? (
|
|
95
|
+
<span className="mw-staged__dot" />
|
|
96
|
+
) : null}
|
|
97
|
+
</span>
|
|
98
|
+
<span>{step}</span>
|
|
99
|
+
</li>
|
|
100
|
+
)
|
|
101
|
+
})}
|
|
102
|
+
</ul>
|
|
103
|
+
)
|
|
104
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `PresetChips` — the "Try:" row of example buttons shared by both explorers. Presentational: it
|
|
7
|
+
* renders a labelled list and calls `onPick` with the chosen preset's value.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from "react"
|
|
11
|
+
|
|
12
|
+
export interface Preset {
|
|
13
|
+
label: string
|
|
14
|
+
value: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PresetChipsProps {
|
|
18
|
+
presets: ReadonlyArray<Preset>
|
|
19
|
+
onPick: (value: string) => void
|
|
20
|
+
disabled?: boolean
|
|
21
|
+
/** Leading label. @default "Try:" */
|
|
22
|
+
caption?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function PresetChips({ presets, onPick, disabled, caption = "Try:" }: PresetChipsProps): ReactNode {
|
|
26
|
+
return (
|
|
27
|
+
<div className="mw-presets">
|
|
28
|
+
<span className="mw-presets__label">{caption}</span>
|
|
29
|
+
{presets.map((preset) => (
|
|
30
|
+
<button
|
|
31
|
+
key={preset.label}
|
|
32
|
+
type="button"
|
|
33
|
+
className="mw-chip"
|
|
34
|
+
disabled={disabled}
|
|
35
|
+
onClick={() => onPick(preset.value)}
|
|
36
|
+
title={preset.value}
|
|
37
|
+
>
|
|
38
|
+
{preset.label}
|
|
39
|
+
</button>
|
|
40
|
+
))}
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
package/common/cx.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Tiny classname joiner — the only utility the presentational units need. Kept in-package (rather
|
|
7
|
+
* than pulling `classnames`) so `@mailwoman/react` ships zero runtime deps beyond its `@mailwoman/*`
|
|
8
|
+
* siblings and the React peer.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type ClassValue = string | number | false | null | undefined | Record<string, boolean>
|
|
12
|
+
|
|
13
|
+
/** Join truthy class tokens; object entries contribute their key when the value is truthy. */
|
|
14
|
+
export function cx(...values: ClassValue[]): string {
|
|
15
|
+
const out: string[] = []
|
|
16
|
+
|
|
17
|
+
for (const value of values) {
|
|
18
|
+
if (!value) continue
|
|
19
|
+
|
|
20
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
21
|
+
out.push(String(value))
|
|
22
|
+
|
|
23
|
+
continue
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
for (const [key, on] of Object.entries(value)) {
|
|
27
|
+
if (on) {
|
|
28
|
+
out.push(key)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return out.join(" ")
|
|
34
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `useClipboard` — headless copy-to-clipboard with the async Clipboard API and a legacy
|
|
7
|
+
* `execCommand` fallback (for insecure contexts / older browsers), plus a transient "copied" flag
|
|
8
|
+
* that auto-resets. Extracted from the two explorers' duplicated `onCopy` handlers.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useCallback, useEffect, useRef, useState } from "react"
|
|
12
|
+
|
|
13
|
+
/** Best-effort clipboard write: async Clipboard API first, hidden-textarea `execCommand` fallback. */
|
|
14
|
+
async function writeToClipboard(value: string): Promise<void> {
|
|
15
|
+
try {
|
|
16
|
+
await navigator.clipboard.writeText(value)
|
|
17
|
+
|
|
18
|
+
return
|
|
19
|
+
} catch {
|
|
20
|
+
// Fall through to the legacy path below.
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const textarea = document.createElement("textarea")
|
|
24
|
+
textarea.value = value
|
|
25
|
+
textarea.style.position = "fixed"
|
|
26
|
+
textarea.style.opacity = "0"
|
|
27
|
+
document.body.appendChild(textarea)
|
|
28
|
+
textarea.select()
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
document.execCommand("copy")
|
|
32
|
+
} catch {
|
|
33
|
+
// Nothing else to try — leave `copied` false via the caller's error path.
|
|
34
|
+
} finally {
|
|
35
|
+
document.body.removeChild(textarea)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface UseClipboard {
|
|
40
|
+
copied: boolean
|
|
41
|
+
copy: (value: string) => Promise<void>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @param resetMs - How long the `copied` flag stays true after a successful copy. */
|
|
45
|
+
export function useClipboard(resetMs = 1500): UseClipboard {
|
|
46
|
+
const [copied, setCopied] = useState(false)
|
|
47
|
+
const timer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
48
|
+
|
|
49
|
+
// Clear a pending reset timer on unmount so it never fires against a torn-down component.
|
|
50
|
+
useEffect(
|
|
51
|
+
() => () => {
|
|
52
|
+
if (timer.current) {
|
|
53
|
+
clearTimeout(timer.current)
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
[]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
const copy = useCallback(
|
|
60
|
+
async (value: string) => {
|
|
61
|
+
await writeToClipboard(value)
|
|
62
|
+
setCopied(true)
|
|
63
|
+
|
|
64
|
+
if (timer.current) {
|
|
65
|
+
clearTimeout(timer.current)
|
|
66
|
+
}
|
|
67
|
+
timer.current = setTimeout(() => setCopied(false), resetMs)
|
|
68
|
+
},
|
|
69
|
+
[resetMs]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return { copied, copy }
|
|
73
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `useDebouncedValue` — returns a copy of `value` that only updates after `delayMs` of quiet. Used by
|
|
7
|
+
* the POI explorer to avoid re-classifying on every keystroke. The timer is a genuine external-sync
|
|
8
|
+
* effect (a scheduled clock), so it belongs in an effect.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useState } from "react"
|
|
12
|
+
|
|
13
|
+
export function useDebouncedValue<T>(value: T, delayMs: number): T {
|
|
14
|
+
const [debounced, setDebounced] = useState(value)
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const id = setTimeout(() => setDebounced(value), delayMs)
|
|
18
|
+
|
|
19
|
+
return () => clearTimeout(id)
|
|
20
|
+
}, [value, delayMs])
|
|
21
|
+
|
|
22
|
+
return debounced
|
|
23
|
+
}
|
package/index.ts
CHANGED
|
@@ -5,9 +5,91 @@
|
|
|
5
5
|
*
|
|
6
6
|
* `@mailwoman/react` — React components + headless hooks for mailwoman.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Two composed explorers plus the small presentational units and headless hooks they decompose into:
|
|
9
|
+
*
|
|
10
|
+
* - `POIExplorer` — a self-contained POI-intent tester (classify → subject → OverpassQL), with an
|
|
11
|
+
* optional injected live poi.db search. No weights, no network on the intent path.
|
|
12
|
+
* - `PipelineExplorer` — a parse+resolve tester driven by an INJECTED `PipelineRuntime`, so the
|
|
13
|
+
* model/gazetteer plumbing (ONNX, httpvfs, node builtins) stays in the host, never in this graph.
|
|
14
|
+
*
|
|
15
|
+
* Styling ships separately as `@mailwoman/react/styles.css` (plain, `mw-`-prefixed, Infima-token
|
|
16
|
+
* aware) — no CSS is imported by the component modules, so the bare package import is node-safe.
|
|
11
17
|
*/
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
// ── Common primitives ──────────────────────────────────────────────────────
|
|
20
|
+
export { ClientOnly } from "./common/ClientOnly.tsx"
|
|
21
|
+
export type { ClientOnlyProps } from "./common/ClientOnly.tsx"
|
|
22
|
+
export { CopyButton } from "./common/CopyButton.tsx"
|
|
23
|
+
export type { CopyButtonProps } from "./common/CopyButton.tsx"
|
|
24
|
+
export { cx } from "./common/cx.ts"
|
|
25
|
+
export type { ClassValue } from "./common/cx.ts"
|
|
26
|
+
export { KindBadge } from "./common/KindBadge.tsx"
|
|
27
|
+
export type { KindBadgeProps, KindBadgeResult } from "./common/KindBadge.tsx"
|
|
28
|
+
export { LoadingIndicator } from "./common/LoadingIndicator.tsx"
|
|
29
|
+
export type { LoadingIndicatorProps, LoadingMode } from "./common/LoadingIndicator.tsx"
|
|
30
|
+
export { PresetChips } from "./common/PresetChips.tsx"
|
|
31
|
+
export type { Preset, PresetChipsProps } from "./common/PresetChips.tsx"
|
|
32
|
+
export { useClipboard } from "./common/useClipboard.ts"
|
|
33
|
+
export type { UseClipboard } from "./common/useClipboard.ts"
|
|
34
|
+
export { useDebouncedValue } from "./common/useDebouncedValue.ts"
|
|
35
|
+
|
|
36
|
+
// ── POI explorer ───────────────────────────────────────────────────────────
|
|
37
|
+
export { AbstainPanel } from "./poi/AbstainPanel.tsx"
|
|
38
|
+
export type { AbstainPanelProps } from "./poi/AbstainPanel.tsx"
|
|
39
|
+
export { LiveResultsBlock } from "./poi/LiveResultsBlock.tsx"
|
|
40
|
+
export type { LiveResultsBlockProps } from "./poi/LiveResultsBlock.tsx"
|
|
41
|
+
export { OverpassBlock } from "./poi/OverpassBlock.tsx"
|
|
42
|
+
export type { OverpassBlockProps } from "./poi/OverpassBlock.tsx"
|
|
43
|
+
export { POIExplorer } from "./poi/POIExplorer.tsx"
|
|
44
|
+
export type { POIExplorerProps } from "./poi/POIExplorer.tsx"
|
|
45
|
+
export { QueryInput } from "./poi/QueryInput.tsx"
|
|
46
|
+
export type { QueryInputProps } from "./poi/QueryInput.tsx"
|
|
47
|
+
export { formatDistance, loadPOIRuntime, POI_DEFAULT_TEXT, POI_PRESETS } from "./poi/runtime.ts"
|
|
48
|
+
export { SubjectPanel } from "./poi/SubjectPanel.tsx"
|
|
49
|
+
export type { SubjectPanelProps } from "./poi/SubjectPanel.tsx"
|
|
50
|
+
export type {
|
|
51
|
+
CategoryRecord,
|
|
52
|
+
LiveSearchState,
|
|
53
|
+
LoadPOIRuntime,
|
|
54
|
+
POIBrandSubject,
|
|
55
|
+
POICategorySubject,
|
|
56
|
+
POIExplorerResult,
|
|
57
|
+
POILiveSearch,
|
|
58
|
+
POILiveSearchResult,
|
|
59
|
+
POIRuntime,
|
|
60
|
+
POISearchHit,
|
|
61
|
+
POISubject,
|
|
62
|
+
POISubjectBase,
|
|
63
|
+
TaxonomyLookup,
|
|
64
|
+
} from "./poi/types.ts"
|
|
65
|
+
export { usePOISearch } from "./poi/usePOISearch.ts"
|
|
66
|
+
export type { UsePOISearch, UsePOISearchOptions } from "./poi/usePOISearch.ts"
|
|
67
|
+
|
|
68
|
+
// ── Pipeline explorer ──────────────────────────────────────────────────────
|
|
69
|
+
export { CandidatePicker } from "./pipeline/CandidatePicker.tsx"
|
|
70
|
+
export type { CandidatePickerProps } from "./pipeline/CandidatePicker.tsx"
|
|
71
|
+
export { ComponentTable } from "./pipeline/ComponentTable.tsx"
|
|
72
|
+
export type { ComponentTableProps } from "./pipeline/ComponentTable.tsx"
|
|
73
|
+
export { ConfidenceCell } from "./pipeline/ConfidenceCell.tsx"
|
|
74
|
+
export type { ConfidenceCellProps } from "./pipeline/ConfidenceCell.tsx"
|
|
75
|
+
export { buildParsePayload } from "./pipeline/copy.ts"
|
|
76
|
+
export { PIPELINE_DEFAULT_ADDRESS, PIPELINE_PRESETS } from "./pipeline/presets.ts"
|
|
77
|
+
export { PipelineExplorer } from "./pipeline/PipelineExplorer.tsx"
|
|
78
|
+
export type { PipelineExplorerProps } from "./pipeline/PipelineExplorer.tsx"
|
|
79
|
+
export { QueryForm } from "./pipeline/QueryForm.tsx"
|
|
80
|
+
export type { QueryFormProps } from "./pipeline/QueryForm.tsx"
|
|
81
|
+
export { ResolvedPlace } from "./pipeline/ResolvedPlace.tsx"
|
|
82
|
+
export type { ResolvedPlaceProps } from "./pipeline/ResolvedPlace.tsx"
|
|
83
|
+
export type {
|
|
84
|
+
DualRoleView,
|
|
85
|
+
FSTProvenance,
|
|
86
|
+
ParsedComponent,
|
|
87
|
+
ParseResult,
|
|
88
|
+
PipelineLoadingState,
|
|
89
|
+
PipelinePanels,
|
|
90
|
+
PipelineRuntime,
|
|
91
|
+
ResolvedPlaceView,
|
|
92
|
+
StageTiming,
|
|
93
|
+
} from "./pipeline/types.ts"
|
|
94
|
+
export { useParsePipeline } from "./pipeline/useParsePipeline.ts"
|
|
95
|
+
export type { UseParsePipeline, UseParsePipelineOptions } from "./pipeline/useParsePipeline.ts"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `ClientOnly` — a portable SSR boundary. The docs explorers wrapped Docusaurus's `<BrowserOnly>`;
|
|
7
|
+
* this is the framework-agnostic equivalent so the components stay usable in any React app (Next,
|
|
8
|
+
* Remix, Docusaurus). It renders `fallback` on the server and the first client paint, then swaps to
|
|
9
|
+
* `children()` once mounted — keeping timers, clipboard, and dynamic imports off the server render.
|
|
10
|
+
*
|
|
11
|
+
* The mount flag is derived from a `useEffect` that fires only in the browser; this is a legitimate
|
|
12
|
+
* external-sync effect (bridging the server/client rendering boundary), not derived state.
|
|
13
|
+
*/
|
|
14
|
+
import { type ReactNode } from "react";
|
|
15
|
+
export interface ClientOnlyProps {
|
|
16
|
+
/** Rendered once mounted in the browser. A thunk so its (browser-only) imports never run on the server. */
|
|
17
|
+
children: () => ReactNode;
|
|
18
|
+
/** Rendered on the server and until the first client mount. */
|
|
19
|
+
fallback?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
export declare function ClientOnly({ children, fallback }: ClientOnlyProps): ReactNode;
|
|
22
|
+
//# sourceMappingURL=ClientOnly.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientOnly.d.ts","sourceRoot":"","sources":["../../common/ClientOnly.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAA;AAE3D,MAAM,WAAW,eAAe;IAC/B,2GAA2G;IAC3G,QAAQ,EAAE,MAAM,SAAS,CAAA;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAe,EAAE,EAAE,eAAe,GAAG,SAAS,CAMpF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `ClientOnly` — a portable SSR boundary. The docs explorers wrapped Docusaurus's `<BrowserOnly>`;
|
|
7
|
+
* this is the framework-agnostic equivalent so the components stay usable in any React app (Next,
|
|
8
|
+
* Remix, Docusaurus). It renders `fallback` on the server and the first client paint, then swaps to
|
|
9
|
+
* `children()` once mounted — keeping timers, clipboard, and dynamic imports off the server render.
|
|
10
|
+
*
|
|
11
|
+
* The mount flag is derived from a `useEffect` that fires only in the browser; this is a legitimate
|
|
12
|
+
* external-sync effect (bridging the server/client rendering boundary), not derived state.
|
|
13
|
+
*/
|
|
14
|
+
import { useEffect, useState } from "react";
|
|
15
|
+
export function ClientOnly({ children, fallback = null }) {
|
|
16
|
+
const [mounted, setMounted] = useState(false);
|
|
17
|
+
useEffect(() => setMounted(true), []);
|
|
18
|
+
return mounted ? children() : fallback;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=ClientOnly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ClientOnly.js","sourceRoot":"","sources":["../../common/ClientOnly.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAkB,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAS3D,MAAM,UAAU,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,EAAmB;IACxE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAE7C,SAAS,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;IAErC,OAAO,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAA;AACvC,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `CopyButton` — a small presentational button that copies a string and shows a transient checkmark.
|
|
7
|
+
* Wraps {@link useClipboard}. Used by both explorers (OverpassQL copy, parse-JSON copy).
|
|
8
|
+
*/
|
|
9
|
+
import type { ReactNode } from "react";
|
|
10
|
+
export interface CopyButtonProps {
|
|
11
|
+
/** The text to copy. If a thunk, it's evaluated at click time (so callers can copy the latest value). */
|
|
12
|
+
value: string | (() => string);
|
|
13
|
+
/** Label in the idle state. @default "Copy" */
|
|
14
|
+
label?: ReactNode;
|
|
15
|
+
/** Label shown briefly after a successful copy. @default "✓ Copied" */
|
|
16
|
+
copiedLabel?: ReactNode;
|
|
17
|
+
className?: string;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function CopyButton({ value, label, copiedLabel, className, disabled, }: CopyButtonProps): ReactNode;
|
|
21
|
+
//# sourceMappingURL=CopyButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CopyButton.d.ts","sourceRoot":"","sources":["../../common/CopyButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,MAAM,WAAW,eAAe;IAC/B,yGAAyG;IACzG,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAA;IAC9B,+CAA+C;IAC/C,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB,uEAAuE;IACvE,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,wBAAgB,UAAU,CAAC,EAC1B,KAAK,EACL,KAAc,EACd,WAAwB,EACxB,SAAoB,EACpB,QAAQ,GACR,EAAE,eAAe,GAAG,SAAS,CAa7B"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useClipboard } from "./useClipboard.js";
|
|
3
|
+
export function CopyButton({ value, label = "Copy", copiedLabel = "✓ Copied", className = "mw-btn", disabled, }) {
|
|
4
|
+
const { copied, copy } = useClipboard();
|
|
5
|
+
return (_jsx("button", { type: "button", className: className, disabled: disabled, onClick: () => copy(typeof value === "function" ? value() : value), children: copied ? copiedLabel : label }));
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=CopyButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CopyButton.js","sourceRoot":"","sources":["../../common/CopyButton.tsx"],"names":[],"mappings":";AAWA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAahD,MAAM,UAAU,UAAU,CAAC,EAC1B,KAAK,EACL,KAAK,GAAG,MAAM,EACd,WAAW,GAAG,UAAU,EACxB,SAAS,GAAG,QAAQ,EACpB,QAAQ,GACS;IACjB,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,YAAY,EAAE,CAAA;IAEvC,OAAO,CACN,iBACC,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,YAEjE,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,GACrB,CACT,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `KindBadge` — compact display of the Stage 2.5 kind-classifier verdict: the top kind + confidence
|
|
7
|
+
* as a pill, expanding to the alternatives on click. Shared by both explorers. The result type is
|
|
8
|
+
* kept structural (`kind: string`) so both the POI query classifier's `QueryKindResult` and the
|
|
9
|
+
* pipeline's projection satisfy it without a hard type dependency.
|
|
10
|
+
*/
|
|
11
|
+
import type { ReactNode } from "react";
|
|
12
|
+
export interface KindBadgeResult {
|
|
13
|
+
kind: string;
|
|
14
|
+
confidence: number;
|
|
15
|
+
alternatives: ReadonlyArray<{
|
|
16
|
+
kind: string;
|
|
17
|
+
confidence: number;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export interface KindBadgeProps {
|
|
21
|
+
kindResult: KindBadgeResult;
|
|
22
|
+
}
|
|
23
|
+
export declare function KindBadge({ kindResult }: KindBadgeProps): ReactNode;
|
|
24
|
+
//# sourceMappingURL=KindBadge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KindBadge.d.ts","sourceRoot":"","sources":["../../common/KindBadge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACjE;AAED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,eAAe,CAAA;CAC3B;AAID,wBAAgB,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,cAAc,GAAG,SAAS,CAkBnE"}
|