@anyknown/ui 0.1.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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -0
  3. package/dist/components/action-bar/ActionBar.d.ts +28 -0
  4. package/dist/components/action-bar/ActionBar.js +73 -0
  5. package/dist/components/badge/Badge.d.ts +28 -0
  6. package/dist/components/badge/Badge.js +79 -0
  7. package/dist/components/button/Button.d.ts +7 -0
  8. package/dist/components/button/Button.js +86 -0
  9. package/dist/components/card/Card.d.ts +2 -0
  10. package/dist/components/card/Card.js +17 -0
  11. package/dist/components/checkbox/Checkbox.d.ts +8 -0
  12. package/dist/components/checkbox/Checkbox.js +102 -0
  13. package/dist/components/code-block/CodeBlock.d.ts +10 -0
  14. package/dist/components/code-block/CodeBlock.js +103 -0
  15. package/dist/components/composer/Composer.d.ts +27 -0
  16. package/dist/components/composer/Composer.js +278 -0
  17. package/dist/components/data-table/DataTable.d.ts +38 -0
  18. package/dist/components/data-table/DataTable.js +256 -0
  19. package/dist/components/dialog/Dialog.d.ts +36 -0
  20. package/dist/components/dialog/Dialog.js +77 -0
  21. package/dist/components/diff-viewer/DiffViewer.d.ts +13 -0
  22. package/dist/components/diff-viewer/DiffViewer.js +133 -0
  23. package/dist/components/dropdown/DropdownMenu.d.ts +43 -0
  24. package/dist/components/dropdown/DropdownMenu.js +86 -0
  25. package/dist/components/dropzone/Dropzone.d.ts +32 -0
  26. package/dist/components/dropzone/Dropzone.js +242 -0
  27. package/dist/components/empty-state/EmptyState.d.ts +9 -0
  28. package/dist/components/empty-state/EmptyState.js +41 -0
  29. package/dist/components/file-row/FileRow.d.ts +32 -0
  30. package/dist/components/file-row/FileRow.js +153 -0
  31. package/dist/components/handoff-receipt/HandoffReceipt.d.ts +15 -0
  32. package/dist/components/handoff-receipt/HandoffReceipt.js +147 -0
  33. package/dist/components/input/Input.d.ts +48 -0
  34. package/dist/components/input/Input.js +64 -0
  35. package/dist/components/interaction-card/InteractionCard.d.ts +59 -0
  36. package/dist/components/interaction-card/InteractionCard.js +238 -0
  37. package/dist/components/kbd/Kbd.d.ts +9 -0
  38. package/dist/components/kbd/Kbd.js +43 -0
  39. package/dist/components/label/Field.d.ts +11 -0
  40. package/dist/components/label/Field.js +28 -0
  41. package/dist/components/label/Label.d.ts +7 -0
  42. package/dist/components/label/Label.js +21 -0
  43. package/dist/components/label/fieldContext.d.ts +26 -0
  44. package/dist/components/label/fieldContext.js +16 -0
  45. package/dist/components/message/Message.d.ts +29 -0
  46. package/dist/components/message/Message.js +94 -0
  47. package/dist/components/password-input/PasswordInput.d.ts +20 -0
  48. package/dist/components/password-input/PasswordInput.js +141 -0
  49. package/dist/components/popover/Popover.d.ts +38 -0
  50. package/dist/components/popover/Popover.js +37 -0
  51. package/dist/components/progress/Progress.d.ts +36 -0
  52. package/dist/components/progress/Progress.js +200 -0
  53. package/dist/components/radio/Radio.d.ts +7 -0
  54. package/dist/components/radio/Radio.js +89 -0
  55. package/dist/components/radio/RadioGroup.d.ts +21 -0
  56. package/dist/components/radio/RadioGroup.js +34 -0
  57. package/dist/components/reasoning-fold/ReasoningFold.d.ts +10 -0
  58. package/dist/components/reasoning-fold/ReasoningFold.js +92 -0
  59. package/dist/components/recovery-key/RecoveryKey.d.ts +17 -0
  60. package/dist/components/recovery-key/RecoveryKey.js +132 -0
  61. package/dist/components/select/Select.d.ts +31 -0
  62. package/dist/components/select/Select.js +209 -0
  63. package/dist/components/skeleton/Skeleton.d.ts +20 -0
  64. package/dist/components/skeleton/Skeleton.js +57 -0
  65. package/dist/components/switch/Switch.d.ts +7 -0
  66. package/dist/components/switch/Switch.js +108 -0
  67. package/dist/components/tabs/Tabs.d.ts +28 -0
  68. package/dist/components/tabs/Tabs.js +133 -0
  69. package/dist/components/text/Text.d.ts +7 -0
  70. package/dist/components/text/Text.js +31 -0
  71. package/dist/components/textarea/Textarea.d.ts +7 -0
  72. package/dist/components/textarea/Textarea.js +25 -0
  73. package/dist/components/toast/Toast.d.ts +56 -0
  74. package/dist/components/toast/Toast.js +175 -0
  75. package/dist/components/tool-card/ToolCard.d.ts +55 -0
  76. package/dist/components/tool-card/ToolCard.js +281 -0
  77. package/dist/components/tooltip/Tooltip.d.ts +12 -0
  78. package/dist/components/tooltip/Tooltip.js +38 -0
  79. package/dist/components/voice-indicator/VoiceIndicator.d.ts +7 -0
  80. package/dist/components/voice-indicator/VoiceIndicator.js +51 -0
  81. package/dist/index.d.ts +55 -0
  82. package/dist/index.js +38 -0
  83. package/dist/lib/diff.d.ts +28 -0
  84. package/dist/lib/diff.js +133 -0
  85. package/dist/lib/format.d.ts +2 -0
  86. package/dist/lib/format.js +24 -0
  87. package/dist/lib/format.test.d.ts +1 -0
  88. package/dist/lib/format.test.js +37 -0
  89. package/dist/lib/mergeRefs.d.ts +2 -0
  90. package/dist/lib/mergeRefs.js +8 -0
  91. package/dist/lib/motion.d.ts +1 -0
  92. package/dist/lib/motion.js +10 -0
  93. package/dist/lib/paths.d.ts +3 -0
  94. package/dist/lib/paths.js +11 -0
  95. package/dist/lib/popup.d.ts +29 -0
  96. package/dist/lib/popup.js +27 -0
  97. package/dist/lib/progress.d.ts +2 -0
  98. package/dist/lib/progress.js +28 -0
  99. package/dist/lib/silk.d.ts +66 -0
  100. package/dist/lib/silk.js +337 -0
  101. package/dist/lib/styled.d.ts +29 -0
  102. package/dist/lib/styled.js +30 -0
  103. package/dist/lib/svgId.d.ts +1 -0
  104. package/dist/lib/svgId.js +4 -0
  105. package/dist/lib/useAnimationFrame.d.ts +1 -0
  106. package/dist/lib/useAnimationFrame.js +21 -0
  107. package/dist/lib/useControllableState.d.ts +1 -0
  108. package/dist/lib/useControllableState.js +14 -0
  109. package/dist/lib/useCopy.d.ts +4 -0
  110. package/dist/lib/useCopy.js +19 -0
  111. package/dist/lib/voice.d.ts +2 -0
  112. package/dist/lib/voice.js +30 -0
  113. package/dist/lib/weave.d.ts +28 -0
  114. package/dist/lib/weave.js +83 -0
  115. package/dist/scrollbar.css +29 -0
  116. package/dist/themes.stylex.d.ts +49 -0
  117. package/dist/themes.stylex.js +52 -0
  118. package/dist/tokens.css +109 -0
  119. package/dist/tokens.stylex.d.ts +126 -0
  120. package/dist/tokens.stylex.js +149 -0
  121. package/package.json +81 -0
@@ -0,0 +1,56 @@
1
+ import { Toast as BaseToast } from "@base-ui/react/toast";
2
+ import * as stylex from "@stylexjs/stylex";
3
+ export type ToastOptions = {
4
+ description?: string;
5
+ action?: {
6
+ label: string;
7
+ onClick: () => void;
8
+ };
9
+ timeout?: number;
10
+ };
11
+ type Manager = ReturnType<typeof BaseToast.createToastManager>;
12
+ /**
13
+ * Fallback manager for apps with a single <Toaster/>: importing `toast` works
14
+ * without threading context. A Toaster mounted with its own manager registers
15
+ * itself here so the module-level `toast` keeps reaching the live viewport.
16
+ */
17
+ export declare const toastManager: import("@base-ui/react").ToastManager<any>;
18
+ export declare const toast: ((title: string, options?: ToastOptions) => string) & {
19
+ success: (title: string, options?: ToastOptions) => string;
20
+ danger: (title: string, options?: ToastOptions) => string;
21
+ close: (id: string) => void;
22
+ };
23
+ export declare function useToast(): {
24
+ toast: ((title: string, options?: ToastOptions) => string) & {
25
+ success: (title: string, options?: ToastOptions) => string;
26
+ danger: (title: string, options?: ToastOptions) => string;
27
+ close: (id: string) => void;
28
+ };
29
+ };
30
+ declare const POSITIONS: {
31
+ readonly "bottom-right": Readonly<{
32
+ readonly bottom: stylex.StyleXClassNameFor<"bottom", string>;
33
+ readonly insetInlineEnd: stylex.StyleXClassNameFor<"insetInlineEnd", string>;
34
+ }>;
35
+ readonly "bottom-left": Readonly<{
36
+ readonly bottom: stylex.StyleXClassNameFor<"bottom", string>;
37
+ readonly insetInlineStart: stylex.StyleXClassNameFor<"insetInlineStart", string>;
38
+ }>;
39
+ readonly "top-right": Readonly<{
40
+ readonly top: stylex.StyleXClassNameFor<"top", string>;
41
+ readonly insetInlineEnd: stylex.StyleXClassNameFor<"insetInlineEnd", string>;
42
+ }>;
43
+ readonly "top-left": Readonly<{
44
+ readonly top: stylex.StyleXClassNameFor<"top", string>;
45
+ readonly insetInlineStart: stylex.StyleXClassNameFor<"insetInlineStart", string>;
46
+ }>;
47
+ };
48
+ export type ToasterProps = {
49
+ position?: keyof typeof POSITIONS;
50
+ timeout?: number;
51
+ limit?: number;
52
+ /** Pass a manager from `createToastManager()` to scope this viewport. */
53
+ manager?: Manager;
54
+ };
55
+ export declare function Toaster({ position, timeout, limit, manager, }: ToasterProps): import("react").JSX.Element;
56
+ export {};
@@ -0,0 +1,175 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Toast as BaseToast } from "@base-ui/react/toast";
3
+ import * as stylex from "@stylexjs/stylex";
4
+ import { createContext, useContext, useState } from "react";
5
+ import { reset } from "../../lib/styled.js";
6
+ import { usePrefersReducedMotion } from "../../lib/motion.js";
7
+ import { UNWEAVE_PATH } from "../../lib/paths.js";
8
+ import { color, font, motion, radius, shadow, space, text } from "../../tokens.stylex.js";
9
+ const REDUCED = "@media (prefers-reduced-motion: reduce)";
10
+ const DEFAULT_TIMEOUT = 5000;
11
+ const slideIn = stylex.keyframes({
12
+ from: { opacity: 0, translate: "1rem 0" },
13
+ to: { opacity: 1, translate: "0 0" },
14
+ });
15
+ const unweave = stylex.keyframes({
16
+ from: { strokeDashoffset: 0 },
17
+ to: { strokeDashoffset: -100 },
18
+ });
19
+ const styles = stylex.create({
20
+ viewport: {
21
+ position: "fixed",
22
+ zIndex: 80,
23
+ display: "flex",
24
+ gap: space.xs,
25
+ width: "min(20rem, calc(100vw - 2.5rem))",
26
+ },
27
+ fromBottom: { flexDirection: "column-reverse" },
28
+ fromTop: { flexDirection: "column" },
29
+ bottomRight: { bottom: space.md, insetInlineEnd: space.md },
30
+ bottomLeft: { bottom: space.md, insetInlineStart: space.md },
31
+ topRight: { top: space.md, insetInlineEnd: space.md },
32
+ topLeft: { top: space.md, insetInlineStart: space.md },
33
+ toast: {
34
+ position: "relative",
35
+ overflow: "hidden",
36
+ display: "flex",
37
+ alignItems: "center",
38
+ gap: space.xs,
39
+ backgroundColor: color.surface,
40
+ borderWidth: 1,
41
+ borderStyle: "solid",
42
+ borderColor: color.border,
43
+ borderRadius: radius.lg,
44
+ boxShadow: shadow.popover,
45
+ paddingBlock: space.xs,
46
+ paddingInline: space.xs,
47
+ fontFamily: font.body,
48
+ fontSize: text.sm,
49
+ color: color.text,
50
+ opacity: { default: 1, ":is([data-limited])": 0 },
51
+ animationName: { default: slideIn, [REDUCED]: "none" },
52
+ animationDuration: "180ms",
53
+ animationTimingFunction: "ease-out",
54
+ },
55
+ countdown: { position: "absolute", top: 0, insetInlineStart: 0, width: "100%", height: 5 },
56
+ countLine: {
57
+ fill: "none",
58
+ strokeWidth: 1.5,
59
+ strokeLinecap: "round",
60
+ strokeDasharray: 100,
61
+ animationName: unweave,
62
+ animationTimingFunction: "linear",
63
+ animationFillMode: "forwards",
64
+ },
65
+ lineDefault: { stroke: color.textFaint },
66
+ lineSuccess: { stroke: color.accent },
67
+ lineDanger: { stroke: color.danger },
68
+ running: (ms, paused) => ({
69
+ animationDuration: `${ms}ms`,
70
+ animationPlayState: paused ? "paused" : "running",
71
+ }),
72
+ dot: { flex: "none", width: "0.5rem", height: "0.5rem", borderRadius: radius.full },
73
+ dotDefault: { backgroundColor: color.textFaint },
74
+ dotSuccess: { backgroundColor: color.accent },
75
+ dotDanger: { backgroundColor: color.danger },
76
+ content: { flex: 1, display: "grid", gap: 0 },
77
+ title: { margin: 0, fontSize: text.sm, color: color.text },
78
+ description: { margin: 0, fontSize: "0.78rem", color: color.textMuted },
79
+ action: {
80
+ fontFamily: font.body,
81
+ fontSize: "0.8rem",
82
+ fontWeight: 500,
83
+ lineHeight: 1,
84
+ color: color.accent,
85
+ cursor: "pointer",
86
+ paddingBlock: space.xxs,
87
+ paddingInline: space.xxs,
88
+ borderRadius: radius.sm,
89
+ backgroundColor: { default: "transparent", ":hover": color.accentSubtle },
90
+ transitionProperty: "background-color",
91
+ transitionDuration: { default: motion.fast, [REDUCED]: "0s" },
92
+ outline: { default: "none", ":focus-visible": `2px solid ${color.focusRing}` },
93
+ outlineOffset: -1,
94
+ },
95
+ srOnly: {
96
+ position: "absolute",
97
+ width: 1,
98
+ height: 1,
99
+ padding: 0,
100
+ margin: -1,
101
+ overflow: "hidden",
102
+ clipPath: "inset(50%)",
103
+ whiteSpace: "nowrap",
104
+ borderWidth: 0,
105
+ },
106
+ });
107
+ const TONE = {
108
+ success: { dot: styles.dotSuccess, line: styles.lineSuccess, word: "成功:" },
109
+ danger: { dot: styles.dotDanger, line: styles.lineDanger, word: "錯誤:" },
110
+ default: { dot: styles.dotDefault, line: styles.lineDefault, word: "" },
111
+ };
112
+ function makeApi(getManager) {
113
+ function add(tone, title, options = {}) {
114
+ const manager = getManager();
115
+ const action = options.action;
116
+ let id = "";
117
+ id = manager.add({
118
+ title,
119
+ type: tone,
120
+ description: options.description,
121
+ timeout: options.timeout,
122
+ priority: tone === "danger" ? "high" : "low",
123
+ actionProps: action
124
+ ? {
125
+ children: action.label,
126
+ onClick: () => {
127
+ action.onClick();
128
+ manager.close(id);
129
+ },
130
+ }
131
+ : undefined,
132
+ });
133
+ return id;
134
+ }
135
+ return Object.assign((title, options) => add("default", title, options), {
136
+ success: (title, options) => add("success", title, options),
137
+ danger: (title, options) => add("danger", title, options),
138
+ close: (id) => getManager().close(id),
139
+ });
140
+ }
141
+ /**
142
+ * Fallback manager for apps with a single <Toaster/>: importing `toast` works
143
+ * without threading context. A Toaster mounted with its own manager registers
144
+ * itself here so the module-level `toast` keeps reaching the live viewport.
145
+ */
146
+ export const toastManager = BaseToast.createToastManager();
147
+ const ToastApiContext = createContext(null);
148
+ export const toast = makeApi(() => toastManager);
149
+ export function useToast() {
150
+ const scoped = useContext(ToastApiContext);
151
+ return { toast: scoped ?? toast };
152
+ }
153
+ const POSITIONS = {
154
+ "bottom-right": styles.bottomRight,
155
+ "bottom-left": styles.bottomLeft,
156
+ "top-right": styles.topRight,
157
+ "top-left": styles.topLeft,
158
+ };
159
+ export function Toaster({ position = "bottom-right", timeout = DEFAULT_TIMEOUT, limit = 3, manager, }) {
160
+ const [own] = useState(() => manager ?? toastManager);
161
+ const [api] = useState(() => makeApi(() => own));
162
+ const [paused, setPaused] = useState(false);
163
+ const fromBottom = position.startsWith("bottom");
164
+ return (_jsx(ToastApiContext, { value: api, children: _jsx(BaseToast.Provider, { toastManager: own, timeout: timeout, limit: limit, children: _jsx(BaseToast.Portal, { children: _jsx(BaseToast.Viewport, { onMouseEnter: () => setPaused(true), onMouseLeave: () => setPaused(false), onFocus: () => setPaused(true), onBlur: () => setPaused(false), ...stylex.props(styles.viewport, fromBottom ? styles.fromBottom : styles.fromTop, POSITIONS[position]), children: _jsx(ToastList, { fallbackTimeout: timeout, paused: paused }) }) }) }) }));
165
+ }
166
+ function ToastList({ fallbackTimeout, paused }) {
167
+ const { toasts } = BaseToast.useToastManager();
168
+ return toasts.map((item) => (_jsx(ToastItem, { toast: item, fallbackTimeout: fallbackTimeout, paused: paused }, item.id)));
169
+ }
170
+ function ToastItem({ toast: item, fallbackTimeout, paused }) {
171
+ const reduced = usePrefersReducedMotion();
172
+ const tone = TONE[item.type in TONE ? item.type : "default"];
173
+ const duration = item.timeout ?? fallbackTimeout;
174
+ return (_jsxs(BaseToast.Root, { toast: item, ...stylex.props(styles.toast), children: [!reduced && duration > 0 && (_jsx("svg", { viewBox: "0 0 320 6", preserveAspectRatio: "none", "aria-hidden": "true", ...stylex.props(styles.countdown), children: _jsx("path", { d: UNWEAVE_PATH, pathLength: "100", ...stylex.props(styles.countLine, tone.line, styles.running(duration, paused)) }) })), _jsx("span", { "aria-hidden": "true", ...stylex.props(styles.dot, tone.dot) }), _jsxs(BaseToast.Content, { ...stylex.props(styles.content), children: [tone.word !== "" && _jsx("span", { ...stylex.props(styles.srOnly), children: tone.word }), _jsx(BaseToast.Title, { ...stylex.props(styles.title) }), item.description != null && _jsx(BaseToast.Description, { ...stylex.props(styles.description) })] }), item.actionProps != null && _jsx(BaseToast.Action, { ...stylex.props(reset.control, styles.action) })] }));
175
+ }
@@ -0,0 +1,55 @@
1
+ import { type ReactNode } from "react";
2
+ export type ToolState = "running" | "completed" | "error";
3
+ export type ToolRetry = {
4
+ attempt: number;
5
+ max: number;
6
+ delayMs: number;
7
+ };
8
+ export type ToolCardProps = {
9
+ tool: string;
10
+ title?: string;
11
+ subtitle?: string;
12
+ state?: ToolState;
13
+ durationMs?: number;
14
+ durationLabel?: string;
15
+ defaultOpen?: boolean;
16
+ retry?: ToolRetry;
17
+ runningLabel?: string;
18
+ completedLabel?: string;
19
+ errorLabel?: string;
20
+ secondLine?: ReactNode;
21
+ footer?: ReactNode;
22
+ children?: ReactNode;
23
+ };
24
+ export declare function ToolCard({ tool, title, subtitle, state, durationMs, durationLabel, defaultOpen, retry, runningLabel, completedLabel, errorLabel, secondLine, footer, children, }: ToolCardProps): import("react").JSX.Element;
25
+ export declare function ToolInput({ json, label }: {
26
+ json: unknown;
27
+ label?: string;
28
+ }): import("react").JSX.Element;
29
+ export declare function ToolOutput({ text: value, label }: {
30
+ text: string;
31
+ label?: string;
32
+ }): import("react").JSX.Element;
33
+ export type ToolErrorProps = {
34
+ text: string;
35
+ label?: string;
36
+ copyLabel?: string;
37
+ copiedLabel?: string;
38
+ };
39
+ export declare function ToolError({ text: value, label, copyLabel, copiedLabel, }: ToolErrorProps): import("react").JSX.Element;
40
+ export type SubagentLineProps = {
41
+ model?: string;
42
+ now?: string;
43
+ toolCount?: number;
44
+ };
45
+ export declare function SubagentLine({ model, now, toolCount }: SubagentLineProps): import("react").JSX.Element;
46
+ export declare function SubagentSummary({ children }: {
47
+ children: ReactNode;
48
+ }): import("react").JSX.Element;
49
+ export declare function SubagentThread({ task, children }: {
50
+ task?: ReactNode;
51
+ children?: ReactNode;
52
+ }): import("react").JSX.Element;
53
+ export declare function SubagentText({ children }: {
54
+ children: ReactNode;
55
+ }): import("react").JSX.Element;
@@ -0,0 +1,281 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import * as stylex from "@stylexjs/stylex";
3
+ import { useId, useState } from "react";
4
+ import { reset } from "../../lib/styled.js";
5
+ import { useCopy } from "../../lib/useCopy.js";
6
+ import { formatDuration } from "../../lib/format.js";
7
+ import { color, font, radius, space, text } from "../../tokens.stylex.js";
8
+ const REDUCED = "@media (prefers-reduced-motion: reduce)";
9
+ const spin = stylex.keyframes({ to: { rotate: "360deg" } });
10
+ const shimmer = stylex.keyframes({ to: { backgroundPosition: "-200% 0" } });
11
+ const styles = stylex.create({
12
+ card: {
13
+ borderWidth: 1,
14
+ borderStyle: "solid",
15
+ borderColor: color.border,
16
+ borderRadius: radius.lg,
17
+ backgroundColor: color.surface,
18
+ overflow: "hidden",
19
+ },
20
+ cardError: { borderColor: `color-mix(in srgb, ${color.danger} 45%, ${color.border})` },
21
+ row: {
22
+ display: "flex",
23
+ alignItems: "center",
24
+ gap: space.xs,
25
+ width: "100%",
26
+ boxSizing: "border-box",
27
+ paddingBlock: space.xs,
28
+ paddingInline: space.sm,
29
+ fontFamily: font.body,
30
+ fontSize: text.xs,
31
+ lineHeight: text.leadingSnug,
32
+ color: color.text,
33
+ cursor: "pointer",
34
+ backgroundColor: {
35
+ default: "transparent",
36
+ ":hover": `color-mix(in srgb, ${color.border} 22%, ${color.surface})`,
37
+ },
38
+ outline: { default: "none", ":focus-visible": `2px solid ${color.focusRing}` },
39
+ outlineOffset: -2,
40
+ },
41
+ title: { fontWeight: 500, flex: "none" },
42
+ subtitle: {
43
+ color: color.textMuted,
44
+ fontFamily: font.mono,
45
+ overflow: "hidden",
46
+ textOverflow: "ellipsis",
47
+ whiteSpace: "nowrap",
48
+ },
49
+ time: {
50
+ marginInlineStart: "auto",
51
+ color: color.textFaint,
52
+ fontVariantNumeric: "tabular-nums",
53
+ flex: "none",
54
+ },
55
+ chevron: {
56
+ color: color.textFaint,
57
+ flex: "none",
58
+ transitionProperty: "rotate",
59
+ transitionDuration: { default: "140ms", [REDUCED]: "0s" },
60
+ },
61
+ chevronOpen: { rotate: "180deg" },
62
+ ok: { color: color.success, flex: "none" },
63
+ bad: { color: color.danger, flex: "none" },
64
+ spinner: {
65
+ width: 11,
66
+ height: 11,
67
+ flex: "none",
68
+ borderWidth: 1.5,
69
+ borderStyle: "solid",
70
+ borderColor: color.borderStrong,
71
+ borderTopColor: color.accent,
72
+ borderRadius: radius.full,
73
+ animationName: spin,
74
+ animationDuration: { default: "0.8s", [REDUCED]: "1.6s" },
75
+ animationTimingFunction: "linear",
76
+ animationIterationCount: "infinite",
77
+ },
78
+ spinnerWarning: { borderTopColor: color.warning },
79
+ detail: {
80
+ borderTopWidth: 1,
81
+ borderTopStyle: "solid",
82
+ borderTopColor: color.border,
83
+ paddingBlock: space.xs,
84
+ paddingInline: space.sm,
85
+ display: { default: "grid", ":is([hidden])": "none" },
86
+ gap: space.xxs,
87
+ },
88
+ ioLabel: {
89
+ fontFamily: font.mono,
90
+ fontSize: "0.62rem",
91
+ fontWeight: 600,
92
+ lineHeight: 1,
93
+ letterSpacing: "0.08em",
94
+ textTransform: "uppercase",
95
+ color: color.textFaint,
96
+ },
97
+ io: {
98
+ margin: 0,
99
+ fontFamily: font.mono,
100
+ fontSize: text.code,
101
+ lineHeight: text.leadingNormal,
102
+ backgroundColor: color.bg,
103
+ borderWidth: 1,
104
+ borderStyle: "solid",
105
+ borderColor: color.border,
106
+ borderRadius: radius.sm,
107
+ paddingBlock: space.xs,
108
+ paddingInline: space.xs,
109
+ overflowX: "auto",
110
+ whiteSpace: "pre",
111
+ color: color.text,
112
+ outline: { default: "none", ":focus-visible": `2px solid ${color.focusRing}` },
113
+ outlineOffset: -2,
114
+ },
115
+ ioError: {
116
+ backgroundColor: color.dangerSubtle,
117
+ borderColor: `color-mix(in srgb, ${color.danger} 35%, ${color.border})`,
118
+ color: color.danger,
119
+ whiteSpace: "pre-wrap",
120
+ },
121
+ copyError: {
122
+ fontFamily: font.body,
123
+ fontSize: text.xs,
124
+ color: color.danger,
125
+ cursor: "pointer",
126
+ justifySelf: "start",
127
+ borderWidth: 1,
128
+ borderStyle: "solid",
129
+ borderColor: `color-mix(in srgb, ${color.danger} 40%, ${color.border})`,
130
+ borderRadius: radius.sm,
131
+ paddingBlock: "0.2rem",
132
+ paddingInline: space.xxs,
133
+ outline: { default: "none", ":focus-visible": `2px solid ${color.danger}` },
134
+ outlineOffset: 1,
135
+ },
136
+ retryLine: {
137
+ display: "flex",
138
+ alignItems: "center",
139
+ gap: space.xxs,
140
+ paddingBlock: space.xxs,
141
+ paddingInline: space.sm,
142
+ borderTopWidth: 1,
143
+ borderTopStyle: "solid",
144
+ borderTopColor: color.border,
145
+ fontFamily: font.body,
146
+ fontSize: text.xs,
147
+ color: color.warning,
148
+ },
149
+ retryCount: { marginInlineStart: "auto", fontVariantNumeric: "tabular-nums" },
150
+ subLine: {
151
+ display: "flex",
152
+ alignItems: "center",
153
+ gap: space.xxs,
154
+ paddingInline: space.sm,
155
+ paddingBottom: space.xs,
156
+ fontFamily: font.body,
157
+ fontSize: text.xs,
158
+ color: color.textMuted,
159
+ },
160
+ chip: {
161
+ fontFamily: font.mono,
162
+ fontSize: "0.68rem",
163
+ lineHeight: text.leadingSnug,
164
+ color: color.textFaint,
165
+ borderWidth: 1,
166
+ borderStyle: "solid",
167
+ borderColor: color.border,
168
+ borderRadius: radius.sm,
169
+ paddingBlock: "0.05rem",
170
+ paddingInline: space.xxs,
171
+ flex: "none",
172
+ },
173
+ now: {
174
+ backgroundImage: {
175
+ default: `linear-gradient(90deg, ${color.textMuted} 30%, ${color.textFaint} 50%, ${color.textMuted} 70%)`,
176
+ [REDUCED]: "none",
177
+ },
178
+ backgroundSize: "200% 100%",
179
+ backgroundClip: { default: "text", [REDUCED]: "border-box" },
180
+ color: { default: "transparent", [REDUCED]: color.textMuted },
181
+ animationName: { default: shimmer, [REDUCED]: "none" },
182
+ animationDuration: "1.6s",
183
+ animationTimingFunction: "linear",
184
+ animationIterationCount: "infinite",
185
+ },
186
+ summary: {
187
+ paddingInline: space.sm,
188
+ paddingBottom: space.xs,
189
+ fontFamily: font.body,
190
+ fontSize: text.xs,
191
+ lineHeight: text.leadingSnug,
192
+ color: color.textMuted,
193
+ display: "-webkit-box",
194
+ WebkitLineClamp: 3,
195
+ WebkitBoxOrient: "vertical",
196
+ overflow: "hidden",
197
+ },
198
+ nested: {
199
+ borderInlineStartWidth: 2,
200
+ borderInlineStartStyle: "solid",
201
+ borderInlineStartColor: color.border,
202
+ marginBlock: space.xxs,
203
+ marginInline: space.sm,
204
+ paddingInlineStart: space.sm,
205
+ display: "grid",
206
+ gap: space.xs,
207
+ },
208
+ quote: {
209
+ borderInlineStartWidth: 2,
210
+ borderInlineStartStyle: "solid",
211
+ borderInlineStartColor: color.borderStrong,
212
+ paddingInlineStart: space.xs,
213
+ color: color.textMuted,
214
+ fontFamily: font.body,
215
+ fontSize: text.xs,
216
+ lineHeight: text.leadingSnug,
217
+ margin: 0,
218
+ },
219
+ nestedText: { fontFamily: font.body, fontSize: text.xs, lineHeight: text.leadingSnug, margin: 0 },
220
+ srOnly: {
221
+ position: "absolute",
222
+ width: 1,
223
+ height: 1,
224
+ padding: 0,
225
+ margin: -1,
226
+ overflow: "hidden",
227
+ clipPath: "inset(50%)",
228
+ whiteSpace: "nowrap",
229
+ borderWidth: 0,
230
+ },
231
+ });
232
+ const VERBS = {
233
+ read: "讀取",
234
+ edit: "編輯",
235
+ write: "寫入",
236
+ shell: "執行",
237
+ search: "搜尋",
238
+ fetch: "取得",
239
+ subagent: "委派",
240
+ };
241
+ const DEFAULT_OPEN_TOOLS = new Set(["shell", "edit", "write"]);
242
+ function Chevron({ open }) {
243
+ return (_jsx("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", ...stylex.props(styles.chevron, open && styles.chevronOpen), children: _jsx("path", { d: "m6 9 6 6 6-6" }) }));
244
+ }
245
+ export function ToolCard({ tool, title, subtitle, state = "completed", durationMs, durationLabel, defaultOpen, retry, runningLabel = "執行中", completedLabel = "完成", errorLabel = "失敗", secondLine, footer, children, }) {
246
+ const detailId = useId();
247
+ const [open, setOpen] = useState(defaultOpen ?? (state === "error" || DEFAULT_OPEN_TOOLS.has(tool)));
248
+ const [wasState, setWasState] = useState(state);
249
+ // A tool usually mounts as `running` and only later fails; NOTES says any
250
+ // error is expanded, so react to the transition, not just the initial state.
251
+ if (wasState !== state) {
252
+ setWasState(state);
253
+ if (state === "error" && defaultOpen == null)
254
+ setOpen(true);
255
+ }
256
+ const STATE_LABELS = { running: runningLabel, completed: completedLabel, error: errorLabel };
257
+ const retryText = retry != null ? `重試中(第 ${retry.attempt} 次,${Math.round(retry.delayMs / 1000)} 秒後)…` : "";
258
+ return (_jsxs("div", { ...stylex.props(styles.card, state === "error" && styles.cardError), children: [_jsxs("button", { type: "button", "aria-expanded": open, "aria-controls": detailId, onClick: () => setOpen((value) => !value), ...stylex.props(reset.control, styles.row), children: [state === "running" && _jsx("span", { "aria-hidden": "true", ...stylex.props(styles.spinner) }), state === "completed" && (_jsxs("span", { ...stylex.props(styles.ok), children: [_jsx("span", { "aria-hidden": "true", children: "\u2713" }), _jsx("span", { ...stylex.props(styles.srOnly), children: completedLabel })] })), state === "error" && (_jsxs("span", { ...stylex.props(styles.bad), children: [_jsx("span", { "aria-hidden": "true", children: "\u2717" }), _jsx("span", { ...stylex.props(styles.srOnly), children: errorLabel })] })), _jsx("span", { ...stylex.props(styles.title), children: title ?? VERBS[tool] ?? tool }), subtitle != null && _jsx("span", { ...stylex.props(styles.subtitle), children: subtitle }), _jsx("span", { ...stylex.props(styles.time), children: durationLabel ?? (durationMs != null ? formatDuration(durationMs) : "") }), _jsx(Chevron, { open: open })] }), secondLine, footer, _jsx("div", { id: detailId, hidden: !open, ...stylex.props(styles.detail), children: children }), retry != null && (_jsxs("div", { "aria-hidden": "true", ...stylex.props(styles.retryLine), children: [_jsx("span", { ...stylex.props(styles.spinner, styles.spinnerWarning) }), retryText, _jsx("span", { ...stylex.props(styles.retryCount), children: `重試 ${retry.attempt}/${retry.max}` })] })), _jsx("span", { role: "status", ...stylex.props(styles.srOnly), children: [STATE_LABELS[state], retryText].filter(Boolean).join(" · ") })] }));
259
+ }
260
+ export function ToolInput({ json, label = "輸入" }) {
261
+ return (_jsxs(_Fragment, { children: [_jsx("span", { ...stylex.props(styles.ioLabel), children: label }), _jsx("pre", { tabIndex: 0, role: "group", "aria-label": label, ...stylex.props(styles.io), children: typeof json === "string" ? json : JSON.stringify(json, null, 1) })] }));
262
+ }
263
+ export function ToolOutput({ text: value, label = "輸出" }) {
264
+ return (_jsxs(_Fragment, { children: [_jsx("span", { ...stylex.props(styles.ioLabel), children: label }), _jsx("pre", { tabIndex: 0, role: "group", "aria-label": label, ...stylex.props(styles.io), children: value })] }));
265
+ }
266
+ export function ToolError({ text: value, label = "錯誤", copyLabel = "複製錯誤", copiedLabel = "已複製 ✓", }) {
267
+ const { copied, copy } = useCopy();
268
+ return (_jsxs(_Fragment, { children: [_jsx("span", { ...stylex.props(styles.ioLabel), children: label }), _jsx("pre", { tabIndex: 0, role: "group", "aria-label": label, ...stylex.props(styles.io, styles.ioError), children: value }), _jsx("button", { type: "button", onClick: () => copy(value), ...stylex.props(reset.control, styles.copyError), children: copied ? copiedLabel : copyLabel })] }));
269
+ }
270
+ export function SubagentLine({ model, now, toolCount }) {
271
+ return (_jsxs("div", { ...stylex.props(styles.subLine), children: [model != null && _jsx("span", { ...stylex.props(styles.chip), children: model }), now != null ? (_jsx("span", { ...stylex.props(styles.now), children: now })) : toolCount != null ? (_jsx("span", { children: `${toolCount} 工具` })) : null] }));
272
+ }
273
+ export function SubagentSummary({ children }) {
274
+ return _jsx("div", { ...stylex.props(styles.summary), children: children });
275
+ }
276
+ export function SubagentThread({ task, children }) {
277
+ return (_jsxs("div", { ...stylex.props(styles.nested), children: [task != null && _jsx("p", { ...stylex.props(styles.quote), children: task }), children] }));
278
+ }
279
+ export function SubagentText({ children }) {
280
+ return _jsx("p", { ...stylex.props(styles.nestedText), children: children });
281
+ }
@@ -0,0 +1,12 @@
1
+ import { type ReactElement, type ReactNode } from "react";
2
+ export declare const TooltipProvider: import("react").FC<import("@base-ui/react").TooltipProviderProps>;
3
+ export type TooltipProps = {
4
+ content: ReactNode;
5
+ shortcut?: ReactNode;
6
+ side?: "top" | "bottom" | "left" | "right";
7
+ align?: "start" | "center" | "end";
8
+ delay?: number;
9
+ disabled?: boolean;
10
+ children: ReactElement;
11
+ };
12
+ export declare function Tooltip({ content, shortcut, side, align, delay, disabled, children, }: TooltipProps): import("react").JSX.Element;
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Tooltip as BaseTooltip } from "@base-ui/react/tooltip";
3
+ import * as stylex from "@stylexjs/stylex";
4
+ import { useId, useState } from "react";
5
+ import { usePrefersReducedMotion } from "../../lib/motion.js";
6
+ import { color, font, motion, radius, space, text } from "../../tokens.stylex.js";
7
+ import { KbdToneContext } from "../kbd/Kbd.js";
8
+ const REDUCED = "@media (prefers-reduced-motion: reduce)";
9
+ const fade = stylex.keyframes({ from: { opacity: 0 }, to: { opacity: 1 } });
10
+ const styles = stylex.create({
11
+ positioner: { zIndex: 60 },
12
+ bubble: {
13
+ display: "inline-flex",
14
+ alignItems: "center",
15
+ gap: space.xs,
16
+ maxWidth: "18rem",
17
+ backgroundColor: color.text,
18
+ color: color.bg,
19
+ borderRadius: radius.sm,
20
+ paddingBlock: "0.32rem",
21
+ paddingInline: space.xs,
22
+ fontFamily: font.body,
23
+ fontSize: "0.75rem",
24
+ lineHeight: text.leadingSnug,
25
+ animationName: { default: fade, [REDUCED]: "none" },
26
+ animationDuration: motion.fast,
27
+ animationTimingFunction: motion.ease,
28
+ },
29
+ });
30
+ export const TooltipProvider = BaseTooltip.Provider;
31
+ export function Tooltip({ content, shortcut, side = "top", align = "center", delay = 400, disabled = false, children, }) {
32
+ const id = useId();
33
+ const reduced = usePrefersReducedMotion();
34
+ const [open, setOpen] = useState(false);
35
+ if (disabled)
36
+ return children;
37
+ return (_jsxs(BaseTooltip.Root, { open: open, onOpenChange: setOpen, children: [_jsx(BaseTooltip.Trigger, { delay: reduced ? 0 : delay, "aria-describedby": open ? id : undefined, render: children }), _jsx(BaseTooltip.Portal, { children: _jsx(BaseTooltip.Positioner, { side: side, align: align, sideOffset: 6, ...stylex.props(styles.positioner), children: _jsx(BaseTooltip.Popup, { id: id, role: "tooltip", ...stylex.props(styles.bubble), children: _jsxs(KbdToneContext, { value: "inverted", children: [content, shortcut] }) }) }) })] }));
38
+ }
@@ -0,0 +1,7 @@
1
+ import { type VoiceState } from "../../lib/voice.js";
2
+ export type VoiceIndicatorProps = {
3
+ state: VoiceState;
4
+ level?: number;
5
+ statusLabel?: string;
6
+ };
7
+ export declare function VoiceIndicator({ state, level, statusLabel }: VoiceIndicatorProps): import("react").JSX.Element;