@particle-academy/react-fancy 3.0.1 → 3.2.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 +11 -0
- package/dist/index.cjs +1283 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +430 -2
- package/dist/index.d.ts +430 -2
- package/dist/index.js +1274 -3
- package/dist/index.js.map +1 -1
- package/docs/ChatDrawer.md +75 -0
- package/docs/InputTag.md +155 -0
- package/docs/MagicWand.md +65 -0
- package/docs/MoodMeter.md +63 -0
- package/docs/PromptInput.md +70 -0
- package/docs/ReasonTag.md +63 -0
- package/package.json +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# ChatDrawer
|
|
2
|
+
|
|
3
|
+
A tabbed, collapsible drawer that mounts in `PromptInput`'s `aboveInput` slot so the drawer and composer share one rounded shell. Each tab gets a numbered chip and a content panel; only one panel renders at a time. Slot-driven — you decide what each tab shows.
|
|
4
|
+
|
|
5
|
+
Added in `3.2.0`.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { ChatDrawer, PromptInput } from "@particle-academy/react-fancy";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
const [tab, setTab] = useState("tools");
|
|
17
|
+
const [open, setOpen] = useState(true);
|
|
18
|
+
|
|
19
|
+
<PromptInput
|
|
20
|
+
budgetTokens={200_000}
|
|
21
|
+
placeholder={tab === "deal" ? "Ask about this deal…" : "Type a message…"}
|
|
22
|
+
onSubmit={send}
|
|
23
|
+
aboveInput={
|
|
24
|
+
<ChatDrawer
|
|
25
|
+
tabs={[
|
|
26
|
+
{ id: "files", label: "Files" },
|
|
27
|
+
{ id: "tools", label: "Chat Tools" },
|
|
28
|
+
{ id: "prompts", label: "Chat Prompts" },
|
|
29
|
+
{ id: "deal", label: "IBM Analytics Platform", number: null },
|
|
30
|
+
]}
|
|
31
|
+
activeTabId={tab}
|
|
32
|
+
onTabChange={setTab}
|
|
33
|
+
open={open}
|
|
34
|
+
onToggle={setOpen}
|
|
35
|
+
>
|
|
36
|
+
{tab === "files" && <FilesPanel />}
|
|
37
|
+
{tab === "tools" && <ToolsGrid />}
|
|
38
|
+
{tab === "prompts" && <PromptsList />}
|
|
39
|
+
{tab === "deal" && <DealContext />}
|
|
40
|
+
</ChatDrawer>
|
|
41
|
+
}
|
|
42
|
+
/>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Props
|
|
46
|
+
|
|
47
|
+
| Prop | Type | Default | Description |
|
|
48
|
+
| --------------- | -------------------------- | ------------ | ------------------------------------------------------ |
|
|
49
|
+
| `tabs` | `ChatDrawerTab[]` | — | Ordered list of tabs. |
|
|
50
|
+
| `activeTabId` | `string` | — | Currently selected tab. |
|
|
51
|
+
| `onTabChange` | `(id: string) => void` | — | Fires when a chip is clicked. |
|
|
52
|
+
| `open` | `boolean` | `true` | Body visibility. |
|
|
53
|
+
| `onToggle` | `(open: boolean) => void` | — | Fires when the chevron is clicked. |
|
|
54
|
+
| `children` | `ReactNode` | — | Body content for the active tab. |
|
|
55
|
+
| `minBodyHeight` | `number` | `140` | Min height of the body (px) when open. |
|
|
56
|
+
| `className` | `string` | — | Extra class on the outer container. |
|
|
57
|
+
|
|
58
|
+
### Tab
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
type ChatDrawerTab = {
|
|
62
|
+
id: string;
|
|
63
|
+
label: string;
|
|
64
|
+
/** Override the numbered chip. `null` hides the number entirely
|
|
65
|
+
* (handy for context-specific tabs like "IBM Analytics Platform"). */
|
|
66
|
+
number?: number | null;
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
By default tabs are numbered by position (1, 2, 3, …). Pass `number: null` to suppress.
|
|
71
|
+
|
|
72
|
+
## See Also
|
|
73
|
+
|
|
74
|
+
- [PromptInput](./PromptInput.md) — pairs via the `aboveInput` slot so they share one rounded panel
|
|
75
|
+
- [InputTag](./InputTag.md) — drop into the same composer to add `/` and `@` autocomplete
|
package/docs/InputTag.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# InputTag
|
|
2
|
+
|
|
3
|
+
A trigger-driven autocomplete picker that attaches to *any* text surface via an adapter. Type a configured trigger character (`/`, `@`, `#`, `:`, anything) at a word boundary to open a filtered menu; ↑↓ to move, Enter or Tab to insert, Esc to dismiss.
|
|
4
|
+
|
|
5
|
+
Added in `3.2.0`.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import {
|
|
11
|
+
InputTag,
|
|
12
|
+
textareaAdapter,
|
|
13
|
+
inputAdapter,
|
|
14
|
+
contentEditableAdapter,
|
|
15
|
+
controlledAdapter,
|
|
16
|
+
} from "@particle-academy/react-fancy";
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Basic Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { useRef, useState } from "react";
|
|
23
|
+
import { InputTag, textareaAdapter } from "@particle-academy/react-fancy";
|
|
24
|
+
|
|
25
|
+
const COMMANDS = [
|
|
26
|
+
{ name: "/explain", hint: "explain the selection" },
|
|
27
|
+
{ name: "/rewrite", hint: "rewrite in a different tone" },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const MENTIONS = [
|
|
31
|
+
{ id: "ada", name: "Ada", kind: "person" },
|
|
32
|
+
{ id: "readme", name: "README.md", kind: "file" },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
function ChatInput() {
|
|
36
|
+
const [text, setText] = useState("");
|
|
37
|
+
const ref = useRef<HTMLTextAreaElement>(null);
|
|
38
|
+
const adapter = useMemo(() => textareaAdapter(ref), []);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
<textarea ref={ref} value={text} onChange={(e) => setText(e.target.value)} />
|
|
43
|
+
<InputTag
|
|
44
|
+
adapter={adapter}
|
|
45
|
+
triggers={{
|
|
46
|
+
"/": { items: COMMANDS, insert: (c) => `${c.name} ` },
|
|
47
|
+
"@": { items: MENTIONS, insert: (m) => `@${m.name} ` },
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
</>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The component renders nothing until a trigger is active. When the trigger fires, a floating menu anchored to the input shows filtered items. Picking inserts the configured replacement and closes the menu.
|
|
56
|
+
|
|
57
|
+
## Triggers
|
|
58
|
+
|
|
59
|
+
Each trigger character maps to a config:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
{
|
|
63
|
+
"/": {
|
|
64
|
+
items: COMMANDS,
|
|
65
|
+
insert: (item, query) => `${item.name} `, // replaces "/<query>" with this
|
|
66
|
+
filter?: (item, query) => boolean, // default: prefix match against name/id
|
|
67
|
+
render?: (item, active) => ReactNode, // default: item.name | item.id
|
|
68
|
+
keyOf?: (item) => string, // default: same as render fallback
|
|
69
|
+
label?: "Commands", // optional header
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`insert` receives the matched item and the current query (text typed after the trigger character).
|
|
75
|
+
|
|
76
|
+
`filter` defaults to a case-insensitive substring check against `keyOf(item)`. Pass `() => true` to bypass filtering — handy when `items` is being driven by an async source.
|
|
77
|
+
|
|
78
|
+
`render` lets you customize each row. The second arg is `active` (current keyboard cursor).
|
|
79
|
+
|
|
80
|
+
## Adapters
|
|
81
|
+
|
|
82
|
+
`<InputTag>` is surface-agnostic. The component calls into an adapter for read, write, anchor positioning, and key interception. Four built-in adapters cover the DOM cases:
|
|
83
|
+
|
|
84
|
+
| Adapter | Surface |
|
|
85
|
+
| ------------------------------------ | ------------------------------------ |
|
|
86
|
+
| `textareaAdapter(ref)` | `<textarea>` |
|
|
87
|
+
| `inputAdapter(ref)` | `<input>` |
|
|
88
|
+
| `contentEditableAdapter(ref)` | any `contenteditable` element |
|
|
89
|
+
| `controlledAdapter({ anchorRef, onReplaceRange })` | hosts that fully own text state |
|
|
90
|
+
|
|
91
|
+
Non-DOM surfaces (code editors, sheet cell editors, whiteboard sticky notes) ship adapters from their own packages — e.g. `<CodeEditorInputTag>` from `@particle-academy/fancy-code`. For ad-hoc cases, write your own (~30 lines) against the contract:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
type InputTagAdapter = {
|
|
95
|
+
subscribe: (fn: (state: { text: string; caretIndex: number }) => void) => () => void;
|
|
96
|
+
replaceRange: (start: number, end: number, replacement: string) => void;
|
|
97
|
+
getAnchorRect: () => DOMRect | null;
|
|
98
|
+
onKey: (handler: (e: KeyboardEvent) => boolean) => () => void;
|
|
99
|
+
};
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The DOM adapters use React's native value setter so the consumer's controlled `onChange` fires correctly when the picker writes back — no state-sync gotchas.
|
|
103
|
+
|
|
104
|
+
## Props
|
|
105
|
+
|
|
106
|
+
| Prop | Type | Default | Description |
|
|
107
|
+
| ------------- | ----------------------------- | ---------------- | ------------------------------------------- |
|
|
108
|
+
| `adapter` | `InputTagAdapter` | — | Surface adapter. |
|
|
109
|
+
| `triggers` | `Record<string, …>` | — | Per-trigger-char config (see above). |
|
|
110
|
+
| `maxItems` | `number` | `8` | Max rows shown. |
|
|
111
|
+
| `placement` | `"bottom-left"` \| `"bottom-right"` \| `"top-left"` \| `"top-right"` | `"bottom-left"` | Anchor position relative to surface. |
|
|
112
|
+
| `className` | `string` | — | Class on the popover container. |
|
|
113
|
+
| `style` | `CSSProperties` | — | Inline style on the popover container. |
|
|
114
|
+
| `onPick` | `(info) => void` | — | Fires after each insertion. |
|
|
115
|
+
|
|
116
|
+
## Trigger Detection
|
|
117
|
+
|
|
118
|
+
A trigger is active when one of the configured characters appears between the caret and the nearest preceding whitespace (or start of text), with no other non-trigger / non-whitespace breaks. The "query" is everything between the trigger character and the caret.
|
|
119
|
+
|
|
120
|
+
Examples:
|
|
121
|
+
|
|
122
|
+
- `hello /expl|` → query is `expl`, opens `/` picker
|
|
123
|
+
- `email me at user@gma|` → query is `gma`, opens `@` picker
|
|
124
|
+
- `path/to/fi|` → no trigger (caret is in a word that's not preceded by whitespace)
|
|
125
|
+
- `#urgent #wo|nt` → opens `#` picker with query `wo`
|
|
126
|
+
|
|
127
|
+
## Custom Surfaces
|
|
128
|
+
|
|
129
|
+
For non-DOM surfaces, write an adapter. Minimal `controlledAdapter` example for a host that owns text state:
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
const anchorRef = useRef<HTMLDivElement>(null);
|
|
133
|
+
|
|
134
|
+
const adapter = useMemo(
|
|
135
|
+
() =>
|
|
136
|
+
controlledAdapter({
|
|
137
|
+
anchorRef,
|
|
138
|
+
onReplaceRange: (start, end, replacement) => {
|
|
139
|
+
setText((cur) => cur.slice(0, start) + replacement + cur.slice(end));
|
|
140
|
+
setCaret(start + replacement.length);
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
[],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// host pushes updates whenever text/caret changes
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
adapter.notify({ text, caretIndex: caret });
|
|
149
|
+
}, [text, caret]);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## See Also
|
|
153
|
+
|
|
154
|
+
- [PromptInput](./PromptInput.md) — uses similar trigger logic internally; use `InputTag` when you want the picker without the rest of the composer
|
|
155
|
+
- [ChatDrawer](./ChatDrawer.md) — composes with `PromptInput`'s `aboveInput` slot for tabbed-tools drawer UX
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# MagicWand
|
|
2
|
+
|
|
3
|
+
Selection-anchored floating toolbar for text inputs. When the user highlights text in the wrapped `<Textarea>`, a small pill of AI quick-actions floats above the selection. Clicking an action runs a host callback whose return value replaces the highlighted text in-place.
|
|
4
|
+
|
|
5
|
+
Promoted from the dreaming sandbox on 2026-05-12.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { MagicWand, type MagicWandAction } from "@particle-academy/react-fancy";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
const [body, setBody] = useState("…");
|
|
17
|
+
|
|
18
|
+
const actions: MagicWandAction[] = [
|
|
19
|
+
{
|
|
20
|
+
id: "rephrase",
|
|
21
|
+
label: "Rephrase",
|
|
22
|
+
hint: "same meaning, different words",
|
|
23
|
+
run: async (selection) => await ai.rephrase(selection),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "shorten",
|
|
27
|
+
label: "Shorten",
|
|
28
|
+
run: async (selection) => await ai.shorten(selection),
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
<MagicWand value={body} onValueChange={setBody} actions={actions} />;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Props
|
|
36
|
+
|
|
37
|
+
| Prop | Type | Default | Description |
|
|
38
|
+
|------|------|---------|-------------|
|
|
39
|
+
| value | `string` | — | Controlled textarea value. |
|
|
40
|
+
| onValueChange | `(v: string) => void` | — | Fires on every edit. |
|
|
41
|
+
| actions | `MagicWandAction[]` | — | Action list. Each invokes a callback with the selection. |
|
|
42
|
+
| appearance | `"floating" \| "pill"` | `"floating"` | `"pill"` is icon-only and compact. |
|
|
43
|
+
| autoHide | `boolean` | `true` | Hide the wand on click-away or scroll. |
|
|
44
|
+
| rows | `number` | `6` | Textarea rows. |
|
|
45
|
+
| placeholder | `string` | — | Textarea placeholder. |
|
|
46
|
+
| onAction | `(action, selection, replacement) => void` | — | Fires after an action successfully runs. |
|
|
47
|
+
|
|
48
|
+
## Action shape
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
type MagicWandAction = {
|
|
52
|
+
id: string;
|
|
53
|
+
label: string;
|
|
54
|
+
hint?: string;
|
|
55
|
+
tag?: string;
|
|
56
|
+
run: (selection: string, range: { start: number; end: number; text: string }) => Promise<string> | string;
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The string `run` returns becomes the replacement for the selected range. The wand patches `value` and emits `onAction`.
|
|
61
|
+
|
|
62
|
+
## Notes
|
|
63
|
+
|
|
64
|
+
- The toolbar position is computed by mirroring the textarea into a hidden div and measuring the selected substring's bounding rect — works for typical chat composers; less accurate for textareas that resize while the toolbar is open.
|
|
65
|
+
- In `"pill"` mode each action shows only the first character of its label, useful when toolbar real estate is tight.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# MoodMeter
|
|
2
|
+
|
|
3
|
+
A 2D draggable pad that captures a value AND the confidence in that value on the same handle. The halo around the handle shrinks as confidence rises — uncertain readings literally look fuzzier.
|
|
4
|
+
|
|
5
|
+
Promoted from the dreaming sandbox on 2026-05-12.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { MoodMeter } from "@particle-academy/react-fancy";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
const [value, setValue] = useState(60);
|
|
17
|
+
const [confidence, setConfidence] = useState(0.74);
|
|
18
|
+
|
|
19
|
+
<MoodMeter
|
|
20
|
+
min={30}
|
|
21
|
+
max={200}
|
|
22
|
+
value={value}
|
|
23
|
+
confidence={confidence}
|
|
24
|
+
onChange={(v, c) => {
|
|
25
|
+
setValue(v);
|
|
26
|
+
setConfidence(c);
|
|
27
|
+
}}
|
|
28
|
+
posted={{ value: 60, confidence: 0.74 }}
|
|
29
|
+
prefix="$"
|
|
30
|
+
suffix="k"
|
|
31
|
+
/>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Why
|
|
35
|
+
|
|
36
|
+
When AIs post a number, humans immediately ask "how sure are you?". Making confidence an explicit sibling of value — and letting the user drag either independently — turns vague "AI suggestion" UX into something you can argue with precisely.
|
|
37
|
+
|
|
38
|
+
## Props
|
|
39
|
+
|
|
40
|
+
| Prop | Type | Default | Description |
|
|
41
|
+
|------|------|---------|-------------|
|
|
42
|
+
| min | `number` | — | Range minimum. |
|
|
43
|
+
| max | `number` | — | Range maximum. |
|
|
44
|
+
| step | `number` | `(max-min)/100` | Step for value snapping. |
|
|
45
|
+
| value | `number` | — | Current value (controlled). |
|
|
46
|
+
| confidence | `number` | — | Current confidence 0..1 (controlled). |
|
|
47
|
+
| onChange | `(value, confidence) => void` | — | Fires on drag / pointer events. |
|
|
48
|
+
| posted | `{ value, confidence }` | — | Optional agent post — renders as a dashed ghost handle. |
|
|
49
|
+
| width | `number` | `320` | Pad width in pixels. |
|
|
50
|
+
| height | `number` | `220` | Pad height in pixels. |
|
|
51
|
+
| showGrid | `boolean` | `true` | Show the grid + axis labels. |
|
|
52
|
+
| color | `string` | `"#0ea5e9"` | User handle colour. |
|
|
53
|
+
| postedColor | `string` | `"#a855f7"` | Ghost handle colour. |
|
|
54
|
+
| prefix | `string` | `""` | Prefix for the value label (e.g. `"$"`). |
|
|
55
|
+
| suffix | `string` | `""` | Suffix for the value label (e.g. `"k"`, `"%"`). |
|
|
56
|
+
| formatValue | `(v) => string` | — | Override the label formatting. |
|
|
57
|
+
| className | `string` | — | Applied to the pad. |
|
|
58
|
+
|
|
59
|
+
## Pad Axes
|
|
60
|
+
|
|
61
|
+
- **x-axis** → value range `[min..max]`
|
|
62
|
+
- **y-axis** → confidence `[0..1]` (top = sure)
|
|
63
|
+
- **halo radius** → scales inversely with confidence (large halo = unsure)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# PromptInput
|
|
2
|
+
|
|
3
|
+
The chat composer every AI app rebuilds. Auto-growing multi-line input with slash commands, @ mentions, drop-to-attach, keyboard submit, and a live token-budget meter.
|
|
4
|
+
|
|
5
|
+
Promoted from the dreaming sandbox on 2026-05-12.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { PromptInput } from "@particle-academy/react-fancy";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<PromptInput
|
|
17
|
+
budgetTokens={32000}
|
|
18
|
+
commands={[
|
|
19
|
+
{ name: "/explain", hint: "explain the selected text" },
|
|
20
|
+
{ name: "/rewrite", hint: "rewrite in a different tone" },
|
|
21
|
+
]}
|
|
22
|
+
mentions={[
|
|
23
|
+
{ id: "planner", name: "Planner", kind: "agent" },
|
|
24
|
+
{ id: "ada", name: "Ada", kind: "person" },
|
|
25
|
+
{ id: "readme", name: "README.md", kind: "file" },
|
|
26
|
+
]}
|
|
27
|
+
onSubmit={(text, attachments) => send(text, attachments)}
|
|
28
|
+
/>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Auto-resizes up to `maxHeight`.
|
|
34
|
+
- `/` opens a filtered command palette. ↑/↓ navigate, Enter inserts.
|
|
35
|
+
- `@` opens a mention picker filtered against `mentions`.
|
|
36
|
+
- Drop files anywhere on the input to attach. Chip bar shows attachments.
|
|
37
|
+
- ⌘+Enter (or Ctrl+Enter) submits. Plain Enter inserts a newline.
|
|
38
|
+
- Token-budget meter colours green → amber → red as headroom drops.
|
|
39
|
+
|
|
40
|
+
## Props
|
|
41
|
+
|
|
42
|
+
| Prop | Type | Default | Description |
|
|
43
|
+
|------|------|---------|-------------|
|
|
44
|
+
| budgetTokens | `number` | — | Token budget for the meter. |
|
|
45
|
+
| commands | `PromptCmd[]` | `[]` | Slash commands. Names must start with `/`. |
|
|
46
|
+
| mentions | `PromptMention[]` | `[]` | `{ id, name, kind }`. `kind` is free-form (`"agent"`, `"file"`, `"person"`, …). |
|
|
47
|
+
| onSubmit | `(text, attachments) => void` | — | Called on ⌘/Ctrl+Enter or send button. |
|
|
48
|
+
| showHint | `boolean` | `true` | Show the "⌘ + Enter to send" hint. |
|
|
49
|
+
| placeholder | `string` | `"Ask anything…"` | Placeholder text. |
|
|
50
|
+
| charsPerToken | `number` | `4` | Rough estimator for the token meter. |
|
|
51
|
+
| mentionColor | `Record<string, string>` | sensible defaults | Override the chip colour per mention kind. |
|
|
52
|
+
| maxHeight | `number` | `280` | Max textarea height in pixels. |
|
|
53
|
+
| aboveInput | `ReactNode` | — | Rendered inside the rounded shell, above the textarea. Use this for a drawer of tools/files/prompts/etc. so the drawer and composer share one visual panel — see [ChatDrawer](./ChatDrawer.md). Added in `3.2.0`. |
|
|
54
|
+
|
|
55
|
+
## Types
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
type PromptCmd = { name: string; hint: string };
|
|
59
|
+
type PromptMention = { id: string; name: string; kind: string };
|
|
60
|
+
type PromptAttachment = { id: string; name: string; bytes: number };
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Defaults
|
|
64
|
+
|
|
65
|
+
| Mention kind | Default colour |
|
|
66
|
+
|--------------|----------------|
|
|
67
|
+
| `"agent"` | violet |
|
|
68
|
+
| `"file"` | emerald |
|
|
69
|
+
| `"person"` | blue |
|
|
70
|
+
| other | zinc |
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# ReasonTag
|
|
2
|
+
|
|
3
|
+
Wrap any value with a small affordance that reveals the agent's reasoning, sources, and confidence on hover or click. Explainability becomes a primitive instead of an afterthought.
|
|
4
|
+
|
|
5
|
+
Promoted from the dreaming sandbox on 2026-05-12.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { ReasonTag } from "@particle-academy/react-fancy";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<ReasonTag
|
|
17
|
+
value="$1.4M"
|
|
18
|
+
reason="Projected Q3 ARR after stacking renewals."
|
|
19
|
+
confidence={0.91}
|
|
20
|
+
sources={[{ label: "Q2 actuals · CRM export" }]}
|
|
21
|
+
by="Forecaster"
|
|
22
|
+
/>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Props
|
|
26
|
+
|
|
27
|
+
| Prop | Type | Default | Description |
|
|
28
|
+
|------|------|---------|-------------|
|
|
29
|
+
| value | `ReactNode` | — | The wrapped value (number, label, short phrase). |
|
|
30
|
+
| reason | `string` | — | Free-text rationale shown in the popover. |
|
|
31
|
+
| confidence | `number` | `1` | 0..1 — drives the colour tier (green ≥ 0.85, amber ≥ 0.6, red below). |
|
|
32
|
+
| sources | `ReasonTagSource[]` | `[]` | `{ label, href? }` list rendered in the popover. |
|
|
33
|
+
| by | `string` | — | Optional author / agent name shown in the popover header. |
|
|
34
|
+
| theme | `"dot" \| "underline" \| "chip"` | `"dot"` | Visual treatment for the trigger. |
|
|
35
|
+
| pinned | `boolean` | `false` | When true, the reason is rendered inline as a margin annotation (no popover). |
|
|
36
|
+
| onFollowUp | `() => void` | — | Optional callback for the "ask follow-up" action in the popover. |
|
|
37
|
+
| className | `string` | — | Applied to the trigger element. |
|
|
38
|
+
|
|
39
|
+
## Confidence Tiers
|
|
40
|
+
|
|
41
|
+
| Range | Colour | Label |
|
|
42
|
+
|-------|--------|-------|
|
|
43
|
+
| ≥ 0.85 | emerald | high |
|
|
44
|
+
| ≥ 0.6 | amber | medium |
|
|
45
|
+
| < 0.6 | red | low |
|
|
46
|
+
|
|
47
|
+
Quick visual scan tells you which suggestions deserve a closer look without opening any tooltips.
|
|
48
|
+
|
|
49
|
+
## Examples
|
|
50
|
+
|
|
51
|
+
### Three visual styles
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<ReasonTag value="42" reason="…" theme="dot" />
|
|
55
|
+
<ReasonTag value="42" reason="…" theme="underline" />
|
|
56
|
+
<ReasonTag value="42" reason="…" theme="chip" />
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Always-visible inline annotation
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<ReasonTag value="$60k" reason="Expansion uplift applied at the historical rate." pinned />
|
|
63
|
+
```
|