ionbase-ui 0.22.0 → 0.25.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/dist/components/AgentActivity.d.ts +41 -0
- package/dist/components/AgentActivity.d.ts.map +1 -0
- package/dist/components/AgentActivity.js +84 -0
- package/dist/components/AgentActivity.js.map +1 -0
- package/dist/components/AgentStop.d.ts +58 -0
- package/dist/components/AgentStop.d.ts.map +1 -0
- package/dist/components/AgentStop.js +82 -0
- package/dist/components/AgentStop.js.map +1 -0
- package/dist/components/ApprovalGate.d.ts +72 -0
- package/dist/components/ApprovalGate.d.ts.map +1 -0
- package/dist/components/ApprovalGate.js +68 -0
- package/dist/components/ApprovalGate.js.map +1 -0
- package/dist/components/Citation.d.ts +51 -0
- package/dist/components/Citation.d.ts.map +1 -0
- package/dist/components/Citation.js +41 -0
- package/dist/components/Citation.js.map +1 -0
- package/dist/components/ConfidenceIndicator.d.ts +37 -0
- package/dist/components/ConfidenceIndicator.d.ts.map +1 -0
- package/dist/components/ConfidenceIndicator.js +41 -0
- package/dist/components/ConfidenceIndicator.js.map +1 -0
- package/dist/components/StreamingText.d.ts +47 -0
- package/dist/components/StreamingText.d.ts.map +1 -0
- package/dist/components/StreamingText.js +44 -0
- package/dist/components/StreamingText.js.map +1 -0
- package/dist/components/index.d.ts +12 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +6 -0
- package/dist/components/index.js.map +1 -1
- package/dist/figma-descriptions.json +147 -0
- package/dist/figma-map.json +3 -1
- package/dist/meta/AgentActivity.json +103 -0
- package/dist/meta/AgentActivityStep.json +117 -0
- package/dist/meta/AgentStop.json +164 -0
- package/dist/meta/ApprovalGate.json +255 -0
- package/dist/meta/Avatar.json +2 -2
- package/dist/meta/AvatarGroup.json +2 -2
- package/dist/meta/Citation.json +101 -0
- package/dist/meta/CitationList.json +94 -0
- package/dist/meta/CitationListItem.json +88 -0
- package/dist/meta/ConfidenceIndicator.json +115 -0
- package/dist/meta/Input.json +2 -2
- package/dist/meta/PhoneInput.json +38 -38
- package/dist/meta/StreamingText.json +99 -0
- package/dist/meta/components.json +2125 -989
- package/dist/meta/contrast.json +1086 -32
- package/dist/meta/index.json +100 -5
- package/dist/meta/patterns/index.json +1 -1
- package/dist/styles/agent-activity.css +95 -0
- package/dist/styles/agent-stop.css +109 -0
- package/dist/styles/approval-gate.css +181 -0
- package/dist/styles/citation.css +93 -0
- package/dist/styles/confidence-indicator.css +97 -0
- package/dist/styles/index.css +34 -0
- package/dist/styles/streaming-text.css +56 -0
- package/llms.txt +2 -2
- package/package.json +4 -3
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type AgentActivityStatus = 'pending' | 'active' | 'done' | 'failed' | 'skipped';
|
|
3
|
+
export interface AgentActivityProps extends React.HTMLAttributes<HTMLOListElement> {
|
|
4
|
+
children?: React.ReactNode;
|
|
5
|
+
/**
|
|
6
|
+
* Announce the step that just became active, once, in a polite live region.
|
|
7
|
+
*
|
|
8
|
+
* On by default because the whole point of an activity log is knowing what
|
|
9
|
+
* the agent is doing without watching it. Turn it off when several logs are
|
|
10
|
+
* on screen at once, or they narrate over each other.
|
|
11
|
+
*/
|
|
12
|
+
announceActive?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface AgentActivityStepProps extends React.LiHTMLAttributes<HTMLLIElement> {
|
|
15
|
+
/** What the agent did, in the user's language. Not a function name. */
|
|
16
|
+
children?: React.ReactNode;
|
|
17
|
+
status?: AgentActivityStatus;
|
|
18
|
+
/** The result, a tool name, a count — whatever makes the step checkable. */
|
|
19
|
+
detail?: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* AgentActivity — what the agent is doing, in plain language.
|
|
23
|
+
*
|
|
24
|
+
* An ordered list, because the steps happened in an order and a screen reader
|
|
25
|
+
* should say "3 of 7". Not a log viewer: this is the account a person reads to
|
|
26
|
+
* decide whether to let the run continue, so the text belongs in their
|
|
27
|
+
* language — "Searched the invoice archive", not `searchIndex(q, {limit:50})`.
|
|
28
|
+
*
|
|
29
|
+
* STATUS IS NEVER CARRIED BY THE ICON ALONE. Every step renders its status as
|
|
30
|
+
* text as well as a glyph. The glyphs differ in shape rather than only in
|
|
31
|
+
* colour, so the list survives greyscale, colour blindness and forced-colours
|
|
32
|
+
* mode — WCAG 1.4.1, which a row of coloured dots fails outright.
|
|
33
|
+
*
|
|
34
|
+
* ONE POLITE ANNOUNCEMENT PER STEP, not one per render. The active step's text
|
|
35
|
+
* is announced when it changes, so a user who is not watching still knows where
|
|
36
|
+
* the run has got to. Watching the DOM instead would re-announce on every
|
|
37
|
+
* unrelated update, which is the failure mode that makes people turn logs off.
|
|
38
|
+
*/
|
|
39
|
+
export declare const AgentActivity: React.ForwardRefExoticComponent<AgentActivityProps & React.RefAttributes<HTMLOListElement>>;
|
|
40
|
+
export declare const AgentActivityStep: React.ForwardRefExoticComponent<AgentActivityStepProps & React.RefAttributes<HTMLLIElement>>;
|
|
41
|
+
//# sourceMappingURL=AgentActivity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AgentActivity.d.ts","sourceRoot":"","sources":["../../src/components/AgentActivity.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAEvE,MAAM,MAAM,mBAAmB,GAC7B,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEvD,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC;IAChF,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAuB,SAAQ,KAAK,CAAC,gBAAgB,CAAC,aAAa,CAAC;IACnF,uEAAuE;IACvE,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,mBAAmB,CAAC;IAC7B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC1B;AAsED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,aAAa,6FAyDzB,CAAC;AAIF,eAAO,MAAM,iBAAiB,8FA6B5B,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
|
4
|
+
const STATUS_TEXT = {
|
|
5
|
+
pending: 'Not started',
|
|
6
|
+
active: 'In progress',
|
|
7
|
+
done: 'Done',
|
|
8
|
+
failed: 'Failed',
|
|
9
|
+
skipped: 'Skipped',
|
|
10
|
+
};
|
|
11
|
+
const Glyphs = {
|
|
12
|
+
pending: () => (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: _jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" }) })),
|
|
13
|
+
active: () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: [_jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2", opacity: "0.3" }), _jsx("path", { d: "M21 12a9 9 0 0 0-9-9", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })] })),
|
|
14
|
+
done: () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: [_jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" }), _jsx("path", { d: "m8.5 12 2.5 2.5 4.5-5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })] })),
|
|
15
|
+
failed: () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: [_jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" }), _jsx("path", { d: "m9 9 6 6M15 9l-6 6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })] })),
|
|
16
|
+
skipped: () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: [_jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" }), _jsx("path", { d: "M8.5 12h7", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })] })),
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* AgentActivity — what the agent is doing, in plain language.
|
|
20
|
+
*
|
|
21
|
+
* An ordered list, because the steps happened in an order and a screen reader
|
|
22
|
+
* should say "3 of 7". Not a log viewer: this is the account a person reads to
|
|
23
|
+
* decide whether to let the run continue, so the text belongs in their
|
|
24
|
+
* language — "Searched the invoice archive", not `searchIndex(q, {limit:50})`.
|
|
25
|
+
*
|
|
26
|
+
* STATUS IS NEVER CARRIED BY THE ICON ALONE. Every step renders its status as
|
|
27
|
+
* text as well as a glyph. The glyphs differ in shape rather than only in
|
|
28
|
+
* colour, so the list survives greyscale, colour blindness and forced-colours
|
|
29
|
+
* mode — WCAG 1.4.1, which a row of coloured dots fails outright.
|
|
30
|
+
*
|
|
31
|
+
* ONE POLITE ANNOUNCEMENT PER STEP, not one per render. The active step's text
|
|
32
|
+
* is announced when it changes, so a user who is not watching still knows where
|
|
33
|
+
* the run has got to. Watching the DOM instead would re-announce on every
|
|
34
|
+
* unrelated update, which is the failure mode that makes people turn logs off.
|
|
35
|
+
*/
|
|
36
|
+
export const AgentActivity = forwardRef(({ children, announceActive = true, className, ...rest }, ref) => {
|
|
37
|
+
/*
|
|
38
|
+
* Read the active step out of the children rather than asking the caller to
|
|
39
|
+
* repeat it in a prop. Two sources for one fact is how they disagree.
|
|
40
|
+
*/
|
|
41
|
+
const activeLabel = React.Children.toArray(children).reduce((found, child) => {
|
|
42
|
+
if (found)
|
|
43
|
+
return found;
|
|
44
|
+
if (!React.isValidElement(child))
|
|
45
|
+
return found;
|
|
46
|
+
if (child.props.status !== 'active')
|
|
47
|
+
return found;
|
|
48
|
+
return typeof child.props.children === 'string'
|
|
49
|
+
? child.props.children
|
|
50
|
+
: 'In progress';
|
|
51
|
+
}, '');
|
|
52
|
+
const [announcement, setAnnouncement] = useState('');
|
|
53
|
+
/*
|
|
54
|
+
* Seeded with '' rather than the current label, so the FIRST active step is
|
|
55
|
+
* announced too. Seeding it with `activeLabel` reads as "only announce
|
|
56
|
+
* changes" and silently swallows the one announcement that matters most —
|
|
57
|
+
* the log usually mounts at the moment the run starts, and that first step
|
|
58
|
+
* is exactly what a user who is not watching needs to hear.
|
|
59
|
+
*/
|
|
60
|
+
const previous = useRef('');
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!announceActive)
|
|
63
|
+
return;
|
|
64
|
+
if (activeLabel && activeLabel !== previous.current)
|
|
65
|
+
setAnnouncement(activeLabel);
|
|
66
|
+
previous.current = activeLabel;
|
|
67
|
+
}, [activeLabel, announceActive]);
|
|
68
|
+
return (_jsxs(_Fragment, { children: [_jsx("ol", { ...rest, ref: ref, className: ['ion-agent-activity', className]
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.join(' '), children: children }), announceActive && (_jsx("span", { className: "ion-visually-hidden", role: "status", "aria-live": "polite", children: announcement }))] }));
|
|
71
|
+
});
|
|
72
|
+
AgentActivity.displayName = 'AgentActivity';
|
|
73
|
+
export const AgentActivityStep = forwardRef(({ children, status = 'pending', detail, className, ...rest }, ref) => {
|
|
74
|
+
const Glyph = Glyphs[status];
|
|
75
|
+
return (_jsxs("li", { ...rest, ref: ref, "data-status": status, className: [
|
|
76
|
+
'ion-agent-activity__step',
|
|
77
|
+
`ion-agent-activity__step--${status}`,
|
|
78
|
+
className || '',
|
|
79
|
+
]
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.join(' '), children: [_jsx("span", { className: "ion-agent-activity__glyph", "aria-hidden": "true", children: _jsx(Glyph, {}) }), _jsxs("span", { className: "ion-agent-activity__body", children: [_jsx("span", { className: "ion-agent-activity__label", children: children }), detail && _jsx("span", { className: "ion-agent-activity__detail", children: detail })] }), _jsx("span", { className: "ion-visually-hidden", children: STATUS_TEXT[status] })] }));
|
|
82
|
+
});
|
|
83
|
+
AgentActivityStep.displayName = 'AgentActivityStep';
|
|
84
|
+
//# sourceMappingURL=AgentActivity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AgentActivity.js","sourceRoot":"","sources":["../../src/components/AgentActivity.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAyBvE,MAAM,WAAW,GAAwC;IACvD,OAAO,EAAE,aAAa;IACtB,MAAM,EAAE,aAAa;IACrB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;CACnB,CAAC;AAEF,MAAM,MAAM,GAA0D;IACpE,OAAO,EAAE,GAAG,EAAE,CAAC,CACb,cAAK,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,iBAAa,MAAM,EAAC,SAAS,EAAC,OAAO,YACvE,iBAAQ,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,CAAC,EAAC,GAAG,EAAC,MAAM,EAAC,cAAc,EAAC,WAAW,EAAC,GAAG,GAAG,GAClE,CACP;IACD,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,eAAK,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,iBAAa,MAAM,EAAC,SAAS,EAAC,OAAO,aACvE,iBACE,EAAE,EAAC,IAAI,EACP,EAAE,EAAC,IAAI,EACP,CAAC,EAAC,GAAG,EACL,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,OAAO,EAAC,KAAK,GACb,EACF,eACE,CAAC,EAAC,sBAAsB,EACxB,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,GACrB,IACE,CACP;IACD,IAAI,EAAE,GAAG,EAAE,CAAC,CACV,eAAK,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,iBAAa,MAAM,EAAC,SAAS,EAAC,OAAO,aACvE,iBAAQ,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,CAAC,EAAC,GAAG,EAAC,MAAM,EAAC,cAAc,EAAC,WAAW,EAAC,GAAG,GAAG,EACtE,eACE,CAAC,EAAC,uBAAuB,EACzB,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,GACtB,IACE,CACP;IACD,MAAM,EAAE,GAAG,EAAE,CAAC,CACZ,eAAK,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,iBAAa,MAAM,EAAC,SAAS,EAAC,OAAO,aACvE,iBAAQ,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,CAAC,EAAC,GAAG,EAAC,MAAM,EAAC,cAAc,EAAC,WAAW,EAAC,GAAG,GAAG,EACtE,eACE,CAAC,EAAC,oBAAoB,EACtB,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,GACrB,IACE,CACP;IACD,OAAO,EAAE,GAAG,EAAE,CAAC,CACb,eAAK,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,iBAAa,MAAM,EAAC,SAAS,EAAC,OAAO,aACvE,iBAAQ,EAAE,EAAC,IAAI,EAAC,EAAE,EAAC,IAAI,EAAC,CAAC,EAAC,GAAG,EAAC,MAAM,EAAC,cAAc,EAAC,WAAW,EAAC,GAAG,GAAG,EACtE,eACE,CAAC,EAAC,WAAW,EACb,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,GACrB,IACE,CACP;CACF,CAAC;AAEF;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CACrC,CAAC,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE;IAC/D;;;OAGG;IACH,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CACzD,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACf,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;QACxB,IAAI,CAAC,KAAK,CAAC,cAAc,CAAyB,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACvE,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAClD,OAAO,OAAO,KAAK,CAAC,KAAK,CAAC,QAAQ,KAAK,QAAQ;YAC7C,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ;YACtB,CAAC,CAAC,aAAa,CAAC;IACpB,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACrD;;;;;;OAMG;IACH,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;IAC5B,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,cAAc;YAAE,OAAO;QAC5B,IAAI,WAAW,IAAI,WAAW,KAAK,QAAQ,CAAC,OAAO;YACjD,eAAe,CAAC,WAAW,CAAC,CAAC;QAC/B,QAAQ,CAAC,OAAO,GAAG,WAAW,CAAC;IACjC,CAAC,EAAE,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC,CAAC;IAElC,OAAO,CACL,8BACE,gBACM,IAAI,EACR,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,CAAC,oBAAoB,EAAE,SAAS,CAAC;qBACzC,MAAM,CAAC,OAAO,CAAC;qBACf,IAAI,CAAC,GAAG,CAAC,YAEX,QAAQ,GACN,EACJ,cAAc,IAAI,CACjB,eACE,SAAS,EAAC,qBAAqB,EAC/B,IAAI,EAAC,QAAQ,eACH,QAAQ,YAEjB,YAAY,GACR,CACR,IACA,CACJ,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,aAAa,CAAC,WAAW,GAAG,eAAe,CAAC;AAE5C,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAGzC,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE;IACtE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC7B,OAAO,CACL,iBACM,IAAI,EACR,GAAG,EAAE,GAAG,iBACK,MAAM,EACnB,SAAS,EAAE;YACT,0BAA0B;YAC1B,6BAA6B,MAAM,EAAE;YACrC,SAAS,IAAI,EAAE;SAChB;aACE,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,GAAG,CAAC,aAEZ,eAAM,SAAS,EAAC,2BAA2B,iBAAa,MAAM,YAC5D,KAAC,KAAK,KAAG,GACJ,EACP,gBAAM,SAAS,EAAC,0BAA0B,aACxC,eAAM,SAAS,EAAC,2BAA2B,YAAE,QAAQ,GAAQ,EAC5D,MAAM,IAAI,eAAM,SAAS,EAAC,4BAA4B,YAAE,MAAM,GAAQ,IAClE,EAEP,eAAM,SAAS,EAAC,qBAAqB,YAAE,WAAW,CAAC,MAAM,CAAC,GAAQ,IAC/D,CACN,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,iBAAiB,CAAC,WAAW,GAAG,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type AgentStopSize = 'sm' | 'md' | 'lg';
|
|
3
|
+
export interface AgentStopProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type' | 'children'> {
|
|
4
|
+
/** Called when the user asks the run to stop. */
|
|
5
|
+
onStop: () => void;
|
|
6
|
+
/**
|
|
7
|
+
* The request is in flight. The control stays visible and keeps its place;
|
|
8
|
+
* it does not disappear while the thing it cancels is still running.
|
|
9
|
+
*/
|
|
10
|
+
isStopping?: boolean;
|
|
11
|
+
/** Visible label. */
|
|
12
|
+
label?: string;
|
|
13
|
+
/** Visible label once `isStopping` is set. */
|
|
14
|
+
stoppingLabel?: string;
|
|
15
|
+
size?: AgentStopSize;
|
|
16
|
+
/**
|
|
17
|
+
* Bind Escape, at the document, to stop the run.
|
|
18
|
+
*
|
|
19
|
+
* Off by default and deliberately so: Escape already means "dismiss the
|
|
20
|
+
* thing in front of me" to every modal, popover and menu in this system, and
|
|
21
|
+
* a document-level handler would cancel a background run when the user meant
|
|
22
|
+
* to close a dialog. Turn it on only where the run IS the foreground task.
|
|
23
|
+
*/
|
|
24
|
+
stopOnEscape?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* AgentStop — the always-visible way to end a run.
|
|
28
|
+
*
|
|
29
|
+
* NOT OPTIONAL, AND NOT BEHIND A MENU. A user who cannot stop an agent is
|
|
30
|
+
* watching it, not supervising it. Human oversight of an automated process is
|
|
31
|
+
* a compliance surface in regulated contexts, not a UX preference, and the
|
|
32
|
+
* control that provides it has to be present and reachable at the moment it is
|
|
33
|
+
* wanted — not two clicks into an overflow menu.
|
|
34
|
+
*
|
|
35
|
+
* WHY IT IS NOT JUST `<Button variant="destructive">`
|
|
36
|
+
*
|
|
37
|
+
* Three things it owns that a Button does not:
|
|
38
|
+
*
|
|
39
|
+
* It keeps its place while stopping. A control that vanishes the instant it
|
|
40
|
+
* is pressed leaves the user unsure whether the press registered, and the
|
|
41
|
+
* run is usually still going. It stays, relabels, and disables — the layout
|
|
42
|
+
* does not move.
|
|
43
|
+
*
|
|
44
|
+
* It announces. The label change is only announced to a screen reader if the
|
|
45
|
+
* button happens to hold focus, which it usually does not when a run was
|
|
46
|
+
* started elsewhere. A polite live region says "Stopping" once.
|
|
47
|
+
*
|
|
48
|
+
* It is not destructive-red. Stopping is a normal, expected, reversible-in-
|
|
49
|
+
* spirit action — you can run it again. Colouring it like `delete` teaches
|
|
50
|
+
* hesitation about the one control that must never be hesitated over.
|
|
51
|
+
*
|
|
52
|
+
* WHAT IT DOES NOT DO. It does not stop anything. It reports intent; the
|
|
53
|
+
* caller aborts the request, closes the stream and settles the state. A stop
|
|
54
|
+
* button that resolves optimistically while tokens keep arriving is worse than
|
|
55
|
+
* none, because it lies about a guarantee the user is relying on.
|
|
56
|
+
*/
|
|
57
|
+
export declare const AgentStop: React.ForwardRefExoticComponent<AgentStopProps & React.RefAttributes<HTMLButtonElement>>;
|
|
58
|
+
//# sourceMappingURL=AgentStop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AgentStop.d.ts","sourceRoot":"","sources":["../../src/components/AgentStop.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAkD,MAAM,OAAO,CAAC;AAEvE,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/C,MAAM,WAAW,cAAe,SAAQ,IAAI,CAC1C,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAC7C,MAAM,GAAG,UAAU,CACpB;IACC,iDAAiD;IACjD,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAiBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,SAAS,0FAiFrB,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useEffect, useRef, useState } from 'react';
|
|
4
|
+
/** A square, not a glyph with meaning to learn. Universal stop. */
|
|
5
|
+
const StopGlyph = () => (_jsx("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: _jsx("rect", { x: "6", y: "6", width: "12", height: "12", rx: "1.5", fill: "currentColor", stroke: "none" }) }));
|
|
6
|
+
/**
|
|
7
|
+
* AgentStop — the always-visible way to end a run.
|
|
8
|
+
*
|
|
9
|
+
* NOT OPTIONAL, AND NOT BEHIND A MENU. A user who cannot stop an agent is
|
|
10
|
+
* watching it, not supervising it. Human oversight of an automated process is
|
|
11
|
+
* a compliance surface in regulated contexts, not a UX preference, and the
|
|
12
|
+
* control that provides it has to be present and reachable at the moment it is
|
|
13
|
+
* wanted — not two clicks into an overflow menu.
|
|
14
|
+
*
|
|
15
|
+
* WHY IT IS NOT JUST `<Button variant="destructive">`
|
|
16
|
+
*
|
|
17
|
+
* Three things it owns that a Button does not:
|
|
18
|
+
*
|
|
19
|
+
* It keeps its place while stopping. A control that vanishes the instant it
|
|
20
|
+
* is pressed leaves the user unsure whether the press registered, and the
|
|
21
|
+
* run is usually still going. It stays, relabels, and disables — the layout
|
|
22
|
+
* does not move.
|
|
23
|
+
*
|
|
24
|
+
* It announces. The label change is only announced to a screen reader if the
|
|
25
|
+
* button happens to hold focus, which it usually does not when a run was
|
|
26
|
+
* started elsewhere. A polite live region says "Stopping" once.
|
|
27
|
+
*
|
|
28
|
+
* It is not destructive-red. Stopping is a normal, expected, reversible-in-
|
|
29
|
+
* spirit action — you can run it again. Colouring it like `delete` teaches
|
|
30
|
+
* hesitation about the one control that must never be hesitated over.
|
|
31
|
+
*
|
|
32
|
+
* WHAT IT DOES NOT DO. It does not stop anything. It reports intent; the
|
|
33
|
+
* caller aborts the request, closes the stream and settles the state. A stop
|
|
34
|
+
* button that resolves optimistically while tokens keep arriving is worse than
|
|
35
|
+
* none, because it lies about a guarantee the user is relying on.
|
|
36
|
+
*/
|
|
37
|
+
export const AgentStop = forwardRef(({ onStop, isStopping = false, label = 'Stop', stoppingLabel = 'Stopping…', size = 'md', stopOnEscape = false, className, ...rest }, ref) => {
|
|
38
|
+
/*
|
|
39
|
+
* Announce the transition, not the state. A live region that always holds
|
|
40
|
+
* "Stopping…" is re-read on unrelated updates in some screen readers;
|
|
41
|
+
* writing it once on the edge, and clearing it after, says it exactly once.
|
|
42
|
+
*/
|
|
43
|
+
const [announcement, setAnnouncement] = useState('');
|
|
44
|
+
const wasStopping = useRef(isStopping);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (isStopping && !wasStopping.current)
|
|
47
|
+
setAnnouncement(stoppingLabel);
|
|
48
|
+
if (!isStopping && wasStopping.current)
|
|
49
|
+
setAnnouncement('');
|
|
50
|
+
wasStopping.current = isStopping;
|
|
51
|
+
}, [isStopping, stoppingLabel]);
|
|
52
|
+
const stopRef = useRef(onStop);
|
|
53
|
+
stopRef.current = onStop;
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!stopOnEscape || isStopping)
|
|
56
|
+
return;
|
|
57
|
+
const onKeyDown = (event) => {
|
|
58
|
+
if (event.key !== 'Escape')
|
|
59
|
+
return;
|
|
60
|
+
// An overlay that is open owns Escape. Do not cancel a run because the
|
|
61
|
+
// user tried to close a dialog on top of it.
|
|
62
|
+
if (event.target?.closest?.('[role="dialog"]'))
|
|
63
|
+
return;
|
|
64
|
+
stopRef.current();
|
|
65
|
+
};
|
|
66
|
+
document.addEventListener('keydown', onKeyDown);
|
|
67
|
+
return () => document.removeEventListener('keydown', onKeyDown);
|
|
68
|
+
}, [stopOnEscape, isStopping]);
|
|
69
|
+
return (_jsxs(_Fragment, { children: [_jsxs("button", { ...rest, ref: ref, type: "button", disabled: isStopping, "data-stopping": isStopping || undefined, onClick: (event) => {
|
|
70
|
+
rest.onClick?.(event);
|
|
71
|
+
if (!event.defaultPrevented)
|
|
72
|
+
onStop();
|
|
73
|
+
}, className: [
|
|
74
|
+
'ion-agent-stop',
|
|
75
|
+
size !== 'md' ? `ion-agent-stop--${size}` : '',
|
|
76
|
+
className || '',
|
|
77
|
+
]
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
.join(' '), children: [_jsx("span", { className: "ion-agent-stop__glyph", children: _jsx(StopGlyph, {}) }), _jsx("span", { className: "ion-agent-stop__label", children: isStopping ? stoppingLabel : label })] }), _jsx("span", { className: "ion-agent-stop__status ion-visually-hidden", role: "status", "aria-live": "polite", children: announcement })] }));
|
|
80
|
+
});
|
|
81
|
+
AgentStop.displayName = 'AgentStop';
|
|
82
|
+
//# sourceMappingURL=AgentStop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AgentStop.js","sourceRoot":"","sources":["../../src/components/AgentStop.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type ApprovalGateRisk = 'low' | 'medium' | 'high';
|
|
3
|
+
export type ApprovalGateStatus = 'pending' | 'approved' | 'rejected' | 'expired';
|
|
4
|
+
export interface ApprovalGateProps extends Omit<React.HTMLAttributes<HTMLElement>, 'title' | 'onChange'> {
|
|
5
|
+
/**
|
|
6
|
+
* What is being approved, stated as the action and its object — "Delete 14
|
|
7
|
+
* projects", not "Confirm action". Required: it is the region's accessible
|
|
8
|
+
* name, and a decision the user cannot name is not a decision.
|
|
9
|
+
*/
|
|
10
|
+
title: React.ReactNode;
|
|
11
|
+
/** Why it stopped here, and what happens on approval. */
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* How much is at stake. Drives emphasis only — it does NOT change what the
|
|
15
|
+
* component enforces, because the component enforces nothing.
|
|
16
|
+
*/
|
|
17
|
+
risk?: ApprovalGateRisk;
|
|
18
|
+
/** Where the decision has got to. `pending` is the only state with buttons. */
|
|
19
|
+
status?: ApprovalGateStatus;
|
|
20
|
+
onApprove?: () => void;
|
|
21
|
+
onReject?: () => void;
|
|
22
|
+
/**
|
|
23
|
+
* Offer "edit" as a third way out. Omit it when the proposal cannot be
|
|
24
|
+
* amended — an approve/reject pair the user cannot influence is honest;
|
|
25
|
+
* an edit button that discards their edit is not.
|
|
26
|
+
*/
|
|
27
|
+
onEdit?: () => void;
|
|
28
|
+
approveLabel?: string;
|
|
29
|
+
rejectLabel?: string;
|
|
30
|
+
editLabel?: string;
|
|
31
|
+
/** A decision is in flight. Both actions disable; neither disappears. */
|
|
32
|
+
isSubmitting?: boolean;
|
|
33
|
+
/** Shown in place of the actions once the decision is made. */
|
|
34
|
+
resolution?: React.ReactNode;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* ApprovalGate — a proposed action, held until a person decides.
|
|
38
|
+
*
|
|
39
|
+
* The human-in-the-loop control. An agent proposes; this is where a person
|
|
40
|
+
* approves, rejects, or amends before anything happens. In regulated contexts
|
|
41
|
+
* demonstrable human oversight is a compliance surface rather than a nicety,
|
|
42
|
+
* which is why this is a first-class component and not a Modal with two
|
|
43
|
+
* buttons in it.
|
|
44
|
+
*
|
|
45
|
+
* IT ENFORCES NOTHING, AND SAYING SO IS THE POINT
|
|
46
|
+
*
|
|
47
|
+
* This renders a decision. It does not gate execution — the caller does, by not
|
|
48
|
+
* acting until `onApprove` fires. A component that *looked* like it enforced a
|
|
49
|
+
* policy would be the worst possible thing to ship here: teams would rely on a
|
|
50
|
+
* guarantee that lives entirely in their own call site. `risk` changes emphasis
|
|
51
|
+
* and nothing else, for the same reason.
|
|
52
|
+
*
|
|
53
|
+
* NOT A MODAL, DELIBERATELY
|
|
54
|
+
*
|
|
55
|
+
* A modal steals focus and hides the page. But the page is the evidence: the
|
|
56
|
+
* user needs the plan, the diff, the tool call and the context in front of them
|
|
57
|
+
* while deciding. This sits inline, keeps everything visible, and never
|
|
58
|
+
* traps focus — an approval a user was rushed through is not oversight.
|
|
59
|
+
*
|
|
60
|
+
* NEITHER BUTTON IS FOCUSED ON MOUNT. Autofocusing approve turns a decision
|
|
61
|
+
* into an Enter keypress on a page the user has not read. Focus is moved to the
|
|
62
|
+
* region's heading instead, so a screen-reader user lands on what is being
|
|
63
|
+
* asked rather than on the answer.
|
|
64
|
+
*
|
|
65
|
+
* THE RESOLVED STATES ARE NOT DECORATION. `approved`, `rejected` and `expired`
|
|
66
|
+
* replace the actions with what happened, so the record of the decision stays
|
|
67
|
+
* on the page. `expired` exists because an approval request that nobody answers
|
|
68
|
+
* is the common real outcome, and a gate that sits pending for ever is
|
|
69
|
+
* indistinguishable from one that is broken.
|
|
70
|
+
*/
|
|
71
|
+
export declare const ApprovalGate: React.ForwardRefExoticComponent<ApprovalGateProps & React.RefAttributes<HTMLElement>>;
|
|
72
|
+
//# sourceMappingURL=ApprovalGate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApprovalGate.d.ts","sourceRoot":"","sources":["../../src/components/ApprovalGate.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAyD,MAAM,OAAO,CAAC;AAE9E,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AACzD,MAAM,MAAM,kBAAkB,GAC5B,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;AAElD,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EACjC,OAAO,GAAG,UAAU,CACrB;IACC;;;;OAIG;IACH,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B;;;OAGG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+DAA+D;IAC/D,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC9B;AAyBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,YAAY,uFA4HxB,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useEffect, useId, useRef, useState } from 'react';
|
|
4
|
+
const ShieldQuestion = () => (_jsxs("svg", { viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", focusable: "false", children: [_jsx("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z", stroke: "currentColor", strokeWidth: "2", strokeLinejoin: "round" }), _jsx("path", { d: "M9.5 9a2.5 2.5 0 1 1 3.5 2.3c-.6.3-1 .9-1 1.7M12 16h.01", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })] }));
|
|
5
|
+
const STATUS_TEXT = {
|
|
6
|
+
approved: 'Approved',
|
|
7
|
+
rejected: 'Rejected',
|
|
8
|
+
expired: 'Expired without a decision',
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* ApprovalGate — a proposed action, held until a person decides.
|
|
12
|
+
*
|
|
13
|
+
* The human-in-the-loop control. An agent proposes; this is where a person
|
|
14
|
+
* approves, rejects, or amends before anything happens. In regulated contexts
|
|
15
|
+
* demonstrable human oversight is a compliance surface rather than a nicety,
|
|
16
|
+
* which is why this is a first-class component and not a Modal with two
|
|
17
|
+
* buttons in it.
|
|
18
|
+
*
|
|
19
|
+
* IT ENFORCES NOTHING, AND SAYING SO IS THE POINT
|
|
20
|
+
*
|
|
21
|
+
* This renders a decision. It does not gate execution — the caller does, by not
|
|
22
|
+
* acting until `onApprove` fires. A component that *looked* like it enforced a
|
|
23
|
+
* policy would be the worst possible thing to ship here: teams would rely on a
|
|
24
|
+
* guarantee that lives entirely in their own call site. `risk` changes emphasis
|
|
25
|
+
* and nothing else, for the same reason.
|
|
26
|
+
*
|
|
27
|
+
* NOT A MODAL, DELIBERATELY
|
|
28
|
+
*
|
|
29
|
+
* A modal steals focus and hides the page. But the page is the evidence: the
|
|
30
|
+
* user needs the plan, the diff, the tool call and the context in front of them
|
|
31
|
+
* while deciding. This sits inline, keeps everything visible, and never
|
|
32
|
+
* traps focus — an approval a user was rushed through is not oversight.
|
|
33
|
+
*
|
|
34
|
+
* NEITHER BUTTON IS FOCUSED ON MOUNT. Autofocusing approve turns a decision
|
|
35
|
+
* into an Enter keypress on a page the user has not read. Focus is moved to the
|
|
36
|
+
* region's heading instead, so a screen-reader user lands on what is being
|
|
37
|
+
* asked rather than on the answer.
|
|
38
|
+
*
|
|
39
|
+
* THE RESOLVED STATES ARE NOT DECORATION. `approved`, `rejected` and `expired`
|
|
40
|
+
* replace the actions with what happened, so the record of the decision stays
|
|
41
|
+
* on the page. `expired` exists because an approval request that nobody answers
|
|
42
|
+
* is the common real outcome, and a gate that sits pending for ever is
|
|
43
|
+
* indistinguishable from one that is broken.
|
|
44
|
+
*/
|
|
45
|
+
export const ApprovalGate = forwardRef(({ title, children, risk = 'medium', status = 'pending', onApprove, onReject, onEdit, approveLabel = 'Approve', rejectLabel = 'Reject', editLabel = 'Edit', isSubmitting = false, resolution, className, ...rest }, ref) => {
|
|
46
|
+
const headingRef = useRef(null);
|
|
47
|
+
// A <section> is only a landmark once it has a name, and the name a
|
|
48
|
+
// reviewer needs is the decision itself.
|
|
49
|
+
const titleId = useId();
|
|
50
|
+
const [announcement, setAnnouncement] = useState('');
|
|
51
|
+
const previous = useRef(status);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (status !== previous.current && status !== 'pending')
|
|
54
|
+
setAnnouncement(STATUS_TEXT[status]);
|
|
55
|
+
previous.current = status;
|
|
56
|
+
}, [status]);
|
|
57
|
+
const pending = status === 'pending';
|
|
58
|
+
return (_jsxs("section", { ...rest, ref: ref, "aria-labelledby": titleId, "data-risk": risk, "data-status": status, className: [
|
|
59
|
+
'ion-approval-gate',
|
|
60
|
+
`ion-approval-gate--${risk}`,
|
|
61
|
+
!pending ? `ion-approval-gate--${status}` : '',
|
|
62
|
+
className || '',
|
|
63
|
+
]
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.join(' '), children: [_jsx("span", { className: "ion-approval-gate__icon", "aria-hidden": "true", children: _jsx(ShieldQuestion, {}) }), _jsxs("div", { className: "ion-approval-gate__body", children: [_jsx("p", { ref: headingRef, id: titleId, tabIndex: -1, className: "ion-approval-gate__title", children: title }), children && (_jsx("div", { className: "ion-approval-gate__detail", children: children })), pending ? (_jsxs("div", { className: "ion-approval-gate__actions", children: [_jsx("button", { type: "button", className: "ion-approval-gate__action ion-approval-gate__action--reject", disabled: isSubmitting || !onReject, onClick: onReject, children: rejectLabel }), onEdit && (_jsx("button", { type: "button", className: "ion-approval-gate__action ion-approval-gate__action--edit", disabled: isSubmitting, onClick: onEdit, children: editLabel })), _jsx("button", { type: "button", className: "ion-approval-gate__action ion-approval-gate__action--approve", disabled: isSubmitting || !onApprove, onClick: onApprove, children: approveLabel })] })) : (_jsx("p", { className: "ion-approval-gate__resolution", children: resolution ?? STATUS_TEXT[status] }))] }), _jsx("span", { className: "ion-approval-gate__status ion-visually-hidden", role: "status", "aria-live": "polite", children: announcement })] }));
|
|
66
|
+
});
|
|
67
|
+
ApprovalGate.displayName = 'ApprovalGate';
|
|
68
|
+
//# sourceMappingURL=ApprovalGate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApprovalGate.js","sourceRoot":"","sources":["../../src/components/ApprovalGate.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAc,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AA0C9E,MAAM,cAAc,GAAG,GAAG,EAAE,CAAC,CAC3B,eAAK,OAAO,EAAC,WAAW,EAAC,IAAI,EAAC,MAAM,iBAAa,MAAM,EAAC,SAAS,EAAC,OAAO,aACvE,eACE,CAAC,EAAC,6CAA6C,EAC/C,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,cAAc,EAAC,OAAO,GACtB,EACF,eACE,CAAC,EAAC,yDAAyD,EAC3D,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,GACrB,IACE,CACP,CAAC;AAEF,MAAM,WAAW,GAA2D;IAC1E,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,4BAA4B;CACtC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CACpC,CACE,EACE,KAAK,EACL,QAAQ,EACR,IAAI,GAAG,QAAQ,EACf,MAAM,GAAG,SAAS,EAClB,SAAS,EACT,QAAQ,EACR,MAAM,EACN,YAAY,GAAG,SAAS,EACxB,WAAW,GAAG,QAAQ,EACtB,SAAS,GAAG,MAAM,EAClB,YAAY,GAAG,KAAK,EACpB,UAAU,EACV,SAAS,EACT,GAAG,IAAI,EACR,EACD,GAAG,EACH,EAAE;IACF,MAAM,UAAU,GAAG,MAAM,CAAuB,IAAI,CAAC,CAAC;IACtD,oEAAoE;IACpE,yCAAyC;IACzC,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC;IACxB,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAEhC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,KAAK,QAAQ,CAAC,OAAO,IAAI,MAAM,KAAK,SAAS;YACrD,eAAe,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC;QACvC,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC;IAC5B,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,MAAM,OAAO,GAAG,MAAM,KAAK,SAAS,CAAC;IAErC,OAAO,CACL,sBACM,IAAI,EACR,GAAG,EAAE,GAA6B,qBACjB,OAAO,eACb,IAAI,iBACF,MAAM,EACnB,SAAS,EAAE;YACT,mBAAmB;YACnB,sBAAsB,IAAI,EAAE;YAC5B,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE;YAC9C,SAAS,IAAI,EAAE;SAChB;aACE,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,GAAG,CAAC,aAEZ,eAAM,SAAS,EAAC,yBAAyB,iBAAa,MAAM,YAC1D,KAAC,cAAc,KAAG,GACb,EAEP,eAAK,SAAS,EAAC,yBAAyB,aAKtC,YACE,GAAG,EAAE,UAAU,EACf,EAAE,EAAE,OAAO,EACX,QAAQ,EAAE,CAAC,CAAC,EACZ,SAAS,EAAC,0BAA0B,YAEnC,KAAK,GACJ,EAEH,QAAQ,IAAI,CACX,cAAK,SAAS,EAAC,2BAA2B,YAAE,QAAQ,GAAO,CAC5D,EAEA,OAAO,CAAC,CAAC,CAAC,CACT,eAAK,SAAS,EAAC,4BAA4B,aAMzC,iBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,6DAA6D,EACvE,QAAQ,EAAE,YAAY,IAAI,CAAC,QAAQ,EACnC,OAAO,EAAE,QAAQ,YAEhB,WAAW,GACL,EACR,MAAM,IAAI,CACT,iBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,2DAA2D,EACrE,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,MAAM,YAEd,SAAS,GACH,CACV,EACD,iBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,8DAA8D,EACxE,QAAQ,EAAE,YAAY,IAAI,CAAC,SAAS,EACpC,OAAO,EAAE,SAAS,YAEjB,YAAY,GACN,IACL,CACP,CAAC,CAAC,CAAC,CACF,YAAG,SAAS,EAAC,+BAA+B,YACzC,UAAU,IAAI,WAAW,CAAC,MAAM,CAAC,GAChC,CACL,IACG,EAEN,eACE,SAAS,EAAC,+CAA+C,EACzD,IAAI,EAAC,QAAQ,eACH,QAAQ,YAEjB,YAAY,GACR,IACC,CACX,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,YAAY,CAAC,WAAW,GAAG,cAAc,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface CitationProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'children'> {
|
|
3
|
+
/** The marker shown inline — usually a number matching the footer list. */
|
|
4
|
+
index: number | string;
|
|
5
|
+
/**
|
|
6
|
+
* What is being cited. Required: it is the link's accessible name, and a
|
|
7
|
+
* citation a reader cannot identify without following it is not attribution.
|
|
8
|
+
*/
|
|
9
|
+
source: string;
|
|
10
|
+
/** Where it goes. Omit for a source with no address — a document, a call. */
|
|
11
|
+
href?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface CitationListProps extends React.OlHTMLAttributes<HTMLOListElement> {
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
/** Heading above the list. Rendered as plain text, not a heading element. */
|
|
16
|
+
label?: React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
export interface CitationListItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
|
|
19
|
+
index: number | string;
|
|
20
|
+
source: string;
|
|
21
|
+
href?: string;
|
|
22
|
+
/** The quoted or summarised passage this citation supports. */
|
|
23
|
+
children?: React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Citation — the inline marker.
|
|
27
|
+
*
|
|
28
|
+
* THE MARKER IS NOT THE NAME. Rendered naively, a superscript "1" announces as
|
|
29
|
+
* "link, 1", which tells a screen-reader user nothing about whether to follow
|
|
30
|
+
* it. The visible marker stays a number; the accessible name is
|
|
31
|
+
* "Source 1: <source>". The number is for the sighted reader's eye and the
|
|
32
|
+
* sentence is for everyone.
|
|
33
|
+
*
|
|
34
|
+
* NOT A TOOLTIP. A tooltip cannot be reached on touch and closes on the way to
|
|
35
|
+
* it; attribution has to survive both. It is a real link, or a real
|
|
36
|
+
* non-interactive marker when there is nowhere to go.
|
|
37
|
+
*
|
|
38
|
+
* WITHOUT `href` IT IS NOT A LINK. A citation to a phone call or an internal
|
|
39
|
+
* document has no address, and rendering a dead anchor for it puts an
|
|
40
|
+
* unfollowable link in the page's link list. It becomes a plain marked-up
|
|
41
|
+
* reference instead.
|
|
42
|
+
*/
|
|
43
|
+
export declare const Citation: React.ForwardRefExoticComponent<CitationProps & React.RefAttributes<HTMLAnchorElement>>;
|
|
44
|
+
/**
|
|
45
|
+
* The footer list. An `<ol>` so the numbering is structural rather than typed
|
|
46
|
+
* into each row, and so a screen reader can say how many sources there are
|
|
47
|
+
* before the reader commits to hearing them.
|
|
48
|
+
*/
|
|
49
|
+
export declare const CitationList: React.ForwardRefExoticComponent<CitationListProps & React.RefAttributes<HTMLOListElement>>;
|
|
50
|
+
export declare const CitationListItem: React.ForwardRefExoticComponent<CitationListItemProps & React.RefAttributes<HTMLLIElement>>;
|
|
51
|
+
//# sourceMappingURL=Citation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Citation.d.ts","sourceRoot":"","sources":["../../src/components/Citation.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqB,MAAM,OAAO,CAAC;AAE1C,MAAM,WAAW,aAAc,SAAQ,IAAI,CACzC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAC7C,UAAU,CACX;IACC,2EAA2E;IAC3E,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,gBAAgB,CAAC,gBAAgB,CAAC;IACjF,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACzB;AAED,MAAM,WAAW,qBAAsB,SAAQ,KAAK,CAAC,gBAAgB,CAAC,aAAa,CAAC;IAClF,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,QAAQ,yFAmBpB,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,YAAY,4FAexB,CAAC;AAIF,eAAO,MAAM,gBAAgB,6FAyB3B,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Citation — the inline marker.
|
|
5
|
+
*
|
|
6
|
+
* THE MARKER IS NOT THE NAME. Rendered naively, a superscript "1" announces as
|
|
7
|
+
* "link, 1", which tells a screen-reader user nothing about whether to follow
|
|
8
|
+
* it. The visible marker stays a number; the accessible name is
|
|
9
|
+
* "Source 1: <source>". The number is for the sighted reader's eye and the
|
|
10
|
+
* sentence is for everyone.
|
|
11
|
+
*
|
|
12
|
+
* NOT A TOOLTIP. A tooltip cannot be reached on touch and closes on the way to
|
|
13
|
+
* it; attribution has to survive both. It is a real link, or a real
|
|
14
|
+
* non-interactive marker when there is nowhere to go.
|
|
15
|
+
*
|
|
16
|
+
* WITHOUT `href` IT IS NOT A LINK. A citation to a phone call or an internal
|
|
17
|
+
* document has no address, and rendering a dead anchor for it puts an
|
|
18
|
+
* unfollowable link in the page's link list. It becomes a plain marked-up
|
|
19
|
+
* reference instead.
|
|
20
|
+
*/
|
|
21
|
+
export const Citation = forwardRef(({ index, source, href, className, ...rest }, ref) => {
|
|
22
|
+
const name = `Source ${index}: ${source}`;
|
|
23
|
+
const classes = ['ion-citation', className].filter(Boolean).join(' ');
|
|
24
|
+
if (!href) {
|
|
25
|
+
return (_jsx("span", { className: classes, role: "note", "aria-label": name, children: _jsx("span", { "aria-hidden": "true", children: index }) }));
|
|
26
|
+
}
|
|
27
|
+
return (_jsx("a", { ...rest, ref: ref, href: href, className: classes, "aria-label": name, children: _jsx("span", { "aria-hidden": "true", children: index }) }));
|
|
28
|
+
});
|
|
29
|
+
Citation.displayName = 'Citation';
|
|
30
|
+
/**
|
|
31
|
+
* The footer list. An `<ol>` so the numbering is structural rather than typed
|
|
32
|
+
* into each row, and so a screen reader can say how many sources there are
|
|
33
|
+
* before the reader commits to hearing them.
|
|
34
|
+
*/
|
|
35
|
+
export const CitationList = forwardRef(({ children, label = 'Sources', className, ...rest }, ref) => (_jsxs("div", { className: "ion-citation-list", children: [label && _jsx("p", { className: "ion-citation-list__label", children: label }), _jsx("ol", { ...rest, ref: ref, className: ['ion-citation-list__items', className]
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.join(' '), children: children })] })));
|
|
38
|
+
CitationList.displayName = 'CitationList';
|
|
39
|
+
export const CitationListItem = forwardRef(({ index, source, href, children, className, ...rest }, ref) => (_jsxs("li", { ...rest, ref: ref, className: ['ion-citation-list__item', className].filter(Boolean).join(' '), children: [_jsx("span", { className: "ion-citation-list__marker", "aria-hidden": "true", children: index }), _jsxs("span", { className: "ion-citation-list__body", children: [href ? (_jsx("a", { className: "ion-citation-list__source", href: href, children: source })) : (_jsx("span", { className: "ion-citation-list__source", children: source })), children && (_jsx("span", { className: "ion-citation-list__passage", children: children }))] })] })));
|
|
40
|
+
CitationListItem.displayName = 'CitationListItem';
|
|
41
|
+
//# sourceMappingURL=Citation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Citation.js","sourceRoot":"","sources":["../../src/components/Citation.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AA+B1C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAChC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE;IACnD,MAAM,IAAI,GAAG,UAAU,KAAK,KAAK,MAAM,EAAE,CAAC;IAC1C,MAAM,OAAO,GAAG,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEtE,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CACL,eAAM,SAAS,EAAE,OAAO,EAAE,IAAI,EAAC,MAAM,gBAAa,IAAI,YACpD,8BAAkB,MAAM,YAAE,KAAK,GAAQ,GAClC,CACR,CAAC;IACJ,CAAC;IAED,OAAO,CACL,eAAO,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,gBAAc,IAAI,YACrE,8BAAkB,MAAM,YAAE,KAAK,GAAQ,GACrC,CACL,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,QAAQ,CAAC,WAAW,GAAG,UAAU,CAAC;AAElC;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CACpC,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAG,SAAS,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAC5D,eAAK,SAAS,EAAC,mBAAmB,aAC/B,KAAK,IAAI,YAAG,SAAS,EAAC,0BAA0B,YAAE,KAAK,GAAK,EAC7D,gBACM,IAAI,EACR,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,CAAC,0BAA0B,EAAE,SAAS,CAAC;iBAC/C,MAAM,CAAC,OAAO,CAAC;iBACf,IAAI,CAAC,GAAG,CAAC,YAEX,QAAQ,GACN,IACD,CACP,CACF,CAAC;AAEF,YAAY,CAAC,WAAW,GAAG,cAAc,CAAC;AAE1C,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAGxC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAChE,iBACM,IAAI,EACR,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,CAAC,yBAAyB,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,aAE3E,eAAM,SAAS,EAAC,2BAA2B,iBAAa,MAAM,YAC3D,KAAK,GACD,EACP,gBAAM,SAAS,EAAC,yBAAyB,aACtC,IAAI,CAAC,CAAC,CAAC,CACN,YAAG,SAAS,EAAC,2BAA2B,EAAC,IAAI,EAAE,IAAI,YAChD,MAAM,GACL,CACL,CAAC,CAAC,CAAC,CACF,eAAM,SAAS,EAAC,2BAA2B,YAAE,MAAM,GAAQ,CAC5D,EACA,QAAQ,IAAI,CACX,eAAM,SAAS,EAAC,4BAA4B,YAAE,QAAQ,GAAQ,CAC/D,IACI,IACJ,CACN,CAAC,CAAC;AAEH,gBAAgB,CAAC,WAAW,GAAG,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type ConfidenceLevel = 'low' | 'medium' | 'high';
|
|
3
|
+
export interface ConfidenceIndicatorProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'> {
|
|
4
|
+
level: ConfidenceLevel;
|
|
5
|
+
/**
|
|
6
|
+
* What the level is based on. REQUIRED, and the reason this component exists
|
|
7
|
+
* rather than a coloured dot: a confidence with no stated basis is a number
|
|
8
|
+
* the reader has no way to weigh. "3 of 4 sources agree", "no matching
|
|
9
|
+
* records found", "single unverified source".
|
|
10
|
+
*/
|
|
11
|
+
basis: string;
|
|
12
|
+
/** Override the level's word. Keep it a word, not a percentage. */
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* ConfidenceIndicator — how much to trust the thing next to it.
|
|
17
|
+
*
|
|
18
|
+
* THERE IS NO PERCENTAGE PROP, AND THERE WILL NOT BE ONE
|
|
19
|
+
*
|
|
20
|
+
* "87% confident" reads as a measurement. Almost nowhere is it one: it is
|
|
21
|
+
* usually a softmax score, a heuristic, or a number a model produced about
|
|
22
|
+
* itself — none of which are calibrated probabilities, and all of which invite
|
|
23
|
+
* a reader to treat two digits of precision as real. Three levels cannot
|
|
24
|
+
* overclaim in that way.
|
|
25
|
+
*
|
|
26
|
+
* `basis` IS REQUIRED FOR THE SAME REASON. A level with nothing behind it is
|
|
27
|
+
* decoration that changes behaviour: people act on "high confidence" whether or
|
|
28
|
+
* not anything justifies it. Making the justification a required prop is the
|
|
29
|
+
* only enforcement available here, and it is a type error rather than a policy
|
|
30
|
+
* — which is the strongest kind this system can offer.
|
|
31
|
+
*
|
|
32
|
+
* It renders as text plus a three-bar meter, never the meter alone. The bars
|
|
33
|
+
* differ in filled COUNT, not only in colour, so the reading survives greyscale
|
|
34
|
+
* and forced-colours mode.
|
|
35
|
+
*/
|
|
36
|
+
export declare const ConfidenceIndicator: React.ForwardRefExoticComponent<ConfidenceIndicatorProps & React.RefAttributes<HTMLSpanElement>>;
|
|
37
|
+
//# sourceMappingURL=ConfidenceIndicator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfidenceIndicator.d.ts","sourceRoot":"","sources":["../../src/components/ConfidenceIndicator.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAqB,MAAM,OAAO,CAAC;AAE1C,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAExD,MAAM,WAAW,wBAAyB,SAAQ,IAAI,CACpD,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,EACrC,UAAU,CACX;IACC,KAAK,EAAE,eAAe,CAAC;IACvB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAQD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mBAAmB,kGAiC9B,CAAC"}
|