@neurocode-ai/session-ui 1.18.8
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/package.json +71 -0
- package/src/components/apply-patch-file.test.ts +43 -0
- package/src/components/apply-patch-file.ts +78 -0
- package/src/components/basic-tool.css +358 -0
- package/src/components/basic-tool.stories.tsx +133 -0
- package/src/components/basic-tool.tsx +343 -0
- package/src/components/dock-prompt.stories.tsx +62 -0
- package/src/components/dock-prompt.tsx +23 -0
- package/src/components/file-media.tsx +291 -0
- package/src/components/file-search.tsx +72 -0
- package/src/components/file-ssr.tsx +197 -0
- package/src/components/file.css +46 -0
- package/src/components/file.tsx +1202 -0
- package/src/components/line-comment-annotations.tsx +637 -0
- package/src/components/line-comment-styles.ts +292 -0
- package/src/components/line-comment.stories.tsx +115 -0
- package/src/components/line-comment.tsx +439 -0
- package/src/components/markdown-cache.tsx +78 -0
- package/src/components/markdown-code-state.test.ts +32 -0
- package/src/components/markdown-code-state.ts +22 -0
- package/src/components/markdown-inline-code-kind.test.ts +46 -0
- package/src/components/markdown-inline-code-kind.ts +1915 -0
- package/src/components/markdown-preload.test.ts +18 -0
- package/src/components/markdown-shiki.worker.ts +104 -0
- package/src/components/markdown-stream.test.ts +194 -0
- package/src/components/markdown-stream.ts +110 -0
- package/src/components/markdown-worker-protocol.test.ts +81 -0
- package/src/components/markdown-worker-protocol.ts +48 -0
- package/src/components/markdown-worker-queue.test.ts +49 -0
- package/src/components/markdown-worker-queue.ts +64 -0
- package/src/components/markdown-worker-transport.test.ts +56 -0
- package/src/components/markdown-worker-transport.ts +41 -0
- package/src/components/markdown-worker.ts +122 -0
- package/src/components/markdown.css +377 -0
- package/src/components/markdown.stories.tsx +53 -0
- package/src/components/markdown.tsx +678 -0
- package/src/components/message-file.test.ts +63 -0
- package/src/components/message-file.ts +34 -0
- package/src/components/message-nav.css +127 -0
- package/src/components/message-nav.stories.tsx +7 -0
- package/src/components/message-nav.tsx +102 -0
- package/src/components/message-part-text.ts +3 -0
- package/src/components/message-part.css +1569 -0
- package/src/components/message-part.stories.tsx +7 -0
- package/src/components/message-part.test.ts +28 -0
- package/src/components/message-part.tsx +2662 -0
- package/src/components/session-diff.test.ts +135 -0
- package/src/components/session-diff.ts +145 -0
- package/src/components/session-retry.tsx +74 -0
- package/src/components/session-review.css +247 -0
- package/src/components/session-review.stories.tsx +7 -0
- package/src/components/session-review.tsx +657 -0
- package/src/components/session-turn.css +241 -0
- package/src/components/session-turn.stories.tsx +7 -0
- package/src/components/session-turn.tsx +543 -0
- package/src/components/shell-submessage-motion.stories.tsx +346 -0
- package/src/components/shell-submessage.css +23 -0
- package/src/components/timeline-playground.stories.tsx +2090 -0
- package/src/components/tool-count-label.css +57 -0
- package/src/components/tool-count-label.tsx +58 -0
- package/src/components/tool-count-summary.css +102 -0
- package/src/components/tool-count-summary.stories.tsx +238 -0
- package/src/components/tool-count-summary.tsx +52 -0
- package/src/components/tool-error-card.css +91 -0
- package/src/components/tool-error-card.stories.tsx +92 -0
- package/src/components/tool-error-card.tsx +157 -0
- package/src/components/tool-status-title.css +89 -0
- package/src/components/tool-status-title.tsx +133 -0
- package/src/context/data.tsx +64 -0
- package/src/context/index.ts +1 -0
- package/src/pierre/comment-hover.ts +83 -0
- package/src/pierre/commented-lines.ts +91 -0
- package/src/pierre/diff-selection.ts +71 -0
- package/src/pierre/file-find.ts +485 -0
- package/src/pierre/file-runtime.ts +114 -0
- package/src/pierre/file-selection.ts +85 -0
- package/src/pierre/index.ts +186 -0
- package/src/pierre/media.ts +110 -0
- package/src/pierre/selection-bridge.ts +132 -0
- package/src/pierre/virtualizer.ts +100 -0
- package/src/pierre/worker.ts +52 -0
- package/src/styles/index.css +14 -0
- package/src/v2/components/attachment-card-v2.css +70 -0
- package/src/v2/components/attachment-card-v2.tsx +33 -0
- package/src/v2/components/basic-tool-v2.css +163 -0
- package/src/v2/components/basic-tool-v2.stories.tsx +137 -0
- package/src/v2/components/basic-tool-v2.tsx +139 -0
- package/src/v2/components/comment-card-v2.tsx +64 -0
- package/src/v2/components/line-comment-annotations-v2.tsx +220 -0
- package/src/v2/components/prompt-input/attachments.ts +266 -0
- package/src/v2/components/prompt-input/index.tsx +706 -0
- package/src/v2/components/prompt-input/interaction.ts +482 -0
- package/src/v2/components/prompt-input/machine.test.ts +164 -0
- package/src/v2/components/prompt-input/machine.ts +261 -0
- package/src/v2/components/prompt-input/prompt-input.stories.tsx +221 -0
- package/src/v2/components/prompt-input/store.test.ts +116 -0
- package/src/v2/components/prompt-input/store.ts +152 -0
- package/src/v2/components/prompt-input/types.ts +106 -0
- package/src/v2/components/session-file-panel-v2.tsx +43 -0
- package/src/v2/components/session-progress-indicator-v2.css +875 -0
- package/src/v2/components/session-progress-indicator-v2.stories.tsx +66 -0
- package/src/v2/components/session-progress-indicator-v2.tsx +32 -0
- package/src/v2/components/session-review-empty-changes-v2.tsx +17 -0
- package/src/v2/components/session-review-empty-no-git-v2.tsx +28 -0
- package/src/v2/components/session-review-file-preview-v2-virtualize.test.ts +13 -0
- package/src/v2/components/session-review-file-preview-v2-virtualize.ts +5 -0
- package/src/v2/components/session-review-file-preview-v2.tsx +290 -0
- package/src/v2/components/session-review-v2.css +458 -0
- package/src/v2/components/session-review-v2.tsx +340 -0
- package/src/v2/components/tool-error-card-v2.css +200 -0
- package/src/v2/components/tool-error-card-v2.stories.tsx +91 -0
- package/src/v2/components/tool-error-card-v2.tsx +166 -0
- package/sst-env.d.ts +10 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import { createEffect, For, Match, on, onCleanup, onMount, Show, Switch, type Accessor, type JSX } from "solid-js"
|
|
2
|
+
import { animate, type AnimationPlaybackControls } from "motion"
|
|
3
|
+
import { useI18n } from "@neurocode-ai/ui/context/i18n"
|
|
4
|
+
import { createStore } from "solid-js/store"
|
|
5
|
+
import { Collapsible } from "@neurocode-ai/ui/collapsible"
|
|
6
|
+
import type { IconProps } from "@neurocode-ai/ui/icon"
|
|
7
|
+
import { TextShimmer } from "@neurocode-ai/ui/text-shimmer"
|
|
8
|
+
|
|
9
|
+
export type TriggerTitle = {
|
|
10
|
+
title: string
|
|
11
|
+
titleClass?: string
|
|
12
|
+
subtitle?: string
|
|
13
|
+
subtitleClass?: string
|
|
14
|
+
args?: string[]
|
|
15
|
+
argsClass?: string
|
|
16
|
+
action?: JSX.Element
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isTriggerTitle = (val: any): val is TriggerTitle => {
|
|
20
|
+
return (
|
|
21
|
+
typeof val === "object" && val !== null && "title" in val && (typeof Node === "undefined" || !(val instanceof Node))
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface BasicToolProps {
|
|
26
|
+
icon: IconProps["name"]
|
|
27
|
+
trigger: TriggerTitle | JSX.Element | ((open: Accessor<boolean>) => JSX.Element)
|
|
28
|
+
children?: JSX.Element
|
|
29
|
+
status?: string
|
|
30
|
+
hideDetails?: boolean
|
|
31
|
+
defaultOpen?: boolean
|
|
32
|
+
open?: boolean
|
|
33
|
+
onOpenChange?: (open: boolean) => void
|
|
34
|
+
forceOpen?: boolean
|
|
35
|
+
allowOpenWhilePending?: boolean
|
|
36
|
+
defer?: boolean
|
|
37
|
+
locked?: boolean
|
|
38
|
+
animated?: boolean
|
|
39
|
+
onSubtitleClick?: () => void
|
|
40
|
+
onTriggerClick?: JSX.EventHandlerUnion<HTMLElement, MouseEvent>
|
|
41
|
+
onTriggerKeyDown?: JSX.EventHandlerUnion<HTMLElement, KeyboardEvent>
|
|
42
|
+
triggerHref?: string
|
|
43
|
+
triggerAsLink?: boolean
|
|
44
|
+
clickable?: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const SPRING = { type: "spring" as const, visualDuration: 0.35, bounce: 0 }
|
|
48
|
+
const deferredMounts: Array<{ active: boolean; fn: () => void }> = []
|
|
49
|
+
let deferredFrame: number | undefined
|
|
50
|
+
|
|
51
|
+
function flushDeferredMounts() {
|
|
52
|
+
while (deferredMounts.length > 0) {
|
|
53
|
+
// Timeline tools are mounted top-to-bottom, but the viewport starts at the latest turn.
|
|
54
|
+
// Pop from the end so heavy default-open bodies near the bottom become interactive first.
|
|
55
|
+
const item = deferredMounts.pop()!
|
|
56
|
+
if (item.active) {
|
|
57
|
+
deferredFrame = deferredMounts.length > 0 ? requestAnimationFrame(flushDeferredMounts) : undefined
|
|
58
|
+
item.fn()
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
deferredFrame = undefined
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function scheduleDeferredFlush() {
|
|
66
|
+
if (deferredFrame !== undefined) return
|
|
67
|
+
deferredFrame = requestAnimationFrame(() => {
|
|
68
|
+
deferredFrame = requestAnimationFrame(flushDeferredMounts)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function scheduleDeferredMount(fn: () => void) {
|
|
73
|
+
const item = { active: true, fn }
|
|
74
|
+
deferredMounts.push(item)
|
|
75
|
+
scheduleDeferredFlush()
|
|
76
|
+
return () => {
|
|
77
|
+
item.active = false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function scheduleFrameMount(fn: () => void) {
|
|
82
|
+
const frame = requestAnimationFrame(fn)
|
|
83
|
+
return () => cancelAnimationFrame(frame)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function BasicTool(props: BasicToolProps) {
|
|
87
|
+
const [state, setState] = createStore({
|
|
88
|
+
open: props.defaultOpen ?? false,
|
|
89
|
+
ready: !props.defer && (props.defaultOpen ?? false),
|
|
90
|
+
})
|
|
91
|
+
const open = () => props.open ?? state.open
|
|
92
|
+
const ready = () => state.ready
|
|
93
|
+
const pending = () => props.status === "pending" || props.status === "running"
|
|
94
|
+
const hasChildren = () => (props.defer ? "children" in props : props.children)
|
|
95
|
+
const dynamicTrigger = typeof props.trigger === "function" ? props.trigger(open) : undefined
|
|
96
|
+
|
|
97
|
+
let cancelReady: (() => void) | undefined
|
|
98
|
+
|
|
99
|
+
const cancel = () => {
|
|
100
|
+
cancelReady?.()
|
|
101
|
+
cancelReady = undefined
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const scheduleReady = (initial = false) => {
|
|
105
|
+
cancel()
|
|
106
|
+
cancelReady = (initial ? scheduleDeferredMount : scheduleFrameMount)(() => {
|
|
107
|
+
cancelReady = undefined
|
|
108
|
+
if (!open()) return
|
|
109
|
+
setState("ready", true)
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
onCleanup(cancel)
|
|
114
|
+
|
|
115
|
+
onMount(() => {
|
|
116
|
+
if (props.defer && open()) scheduleReady(true)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const setOpen = (value: boolean) => {
|
|
120
|
+
if (props.open === undefined) setState("open", value)
|
|
121
|
+
props.onOpenChange?.(value)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
createEffect(() => {
|
|
125
|
+
if (!props.forceOpen) return
|
|
126
|
+
if (open()) return
|
|
127
|
+
setOpen(true)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
createEffect(
|
|
131
|
+
on(
|
|
132
|
+
open,
|
|
133
|
+
(value) => {
|
|
134
|
+
if (!props.defer) return
|
|
135
|
+
if (!value) {
|
|
136
|
+
cancel()
|
|
137
|
+
setState("ready", false)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
scheduleReady()
|
|
142
|
+
},
|
|
143
|
+
{ defer: true },
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
// Animated height for collapsible open/close
|
|
148
|
+
let contentRef: HTMLDivElement | undefined
|
|
149
|
+
let heightAnim: AnimationPlaybackControls | undefined
|
|
150
|
+
const initialOpen = open()
|
|
151
|
+
|
|
152
|
+
createEffect(
|
|
153
|
+
on(
|
|
154
|
+
open,
|
|
155
|
+
(isOpen) => {
|
|
156
|
+
if (!props.animated || !contentRef) return
|
|
157
|
+
heightAnim?.stop()
|
|
158
|
+
if (isOpen) {
|
|
159
|
+
contentRef.style.overflow = "hidden"
|
|
160
|
+
heightAnim = animate(contentRef, { height: "auto" }, SPRING)
|
|
161
|
+
void heightAnim.finished.then(() => {
|
|
162
|
+
if (!contentRef || !open()) return
|
|
163
|
+
contentRef.style.overflow = "visible"
|
|
164
|
+
contentRef.style.height = "auto"
|
|
165
|
+
})
|
|
166
|
+
} else {
|
|
167
|
+
contentRef.style.overflow = "hidden"
|
|
168
|
+
heightAnim = animate(contentRef, { height: "0px" }, SPRING)
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
{ defer: true },
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
onCleanup(() => {
|
|
176
|
+
heightAnim?.stop()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const handleOpenChange = (value: boolean) => {
|
|
180
|
+
if (pending() && !props.allowOpenWhilePending) return
|
|
181
|
+
if (props.locked && !value) return
|
|
182
|
+
setOpen(value)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const trigger = () => (
|
|
186
|
+
<div
|
|
187
|
+
data-component="tool-trigger"
|
|
188
|
+
data-clickable={props.clickable ? "true" : undefined}
|
|
189
|
+
data-hide-details={props.hideDetails ? "true" : undefined}
|
|
190
|
+
>
|
|
191
|
+
<div data-slot="basic-tool-tool-trigger-content">
|
|
192
|
+
<div data-slot="basic-tool-tool-info">
|
|
193
|
+
<Switch>
|
|
194
|
+
<Match when={dynamicTrigger !== undefined}>{dynamicTrigger}</Match>
|
|
195
|
+
<Match when={isTriggerTitle(props.trigger) && props.trigger}>
|
|
196
|
+
{(title) => (
|
|
197
|
+
<div data-slot="basic-tool-tool-info-structured">
|
|
198
|
+
<div data-slot="basic-tool-tool-info-main">
|
|
199
|
+
<span
|
|
200
|
+
data-slot="basic-tool-tool-title"
|
|
201
|
+
classList={{
|
|
202
|
+
[title().titleClass ?? ""]: !!title().titleClass,
|
|
203
|
+
}}
|
|
204
|
+
>
|
|
205
|
+
<TextShimmer text={title().title} active={pending()} />
|
|
206
|
+
</span>
|
|
207
|
+
<Show when={!pending()}>
|
|
208
|
+
<Show when={title().subtitle}>
|
|
209
|
+
<span
|
|
210
|
+
data-slot="basic-tool-tool-subtitle"
|
|
211
|
+
classList={{
|
|
212
|
+
[title().subtitleClass ?? ""]: !!title().subtitleClass,
|
|
213
|
+
clickable: !!props.onSubtitleClick,
|
|
214
|
+
}}
|
|
215
|
+
onClick={(e) => {
|
|
216
|
+
if (props.onSubtitleClick) {
|
|
217
|
+
e.stopPropagation()
|
|
218
|
+
props.onSubtitleClick()
|
|
219
|
+
}
|
|
220
|
+
}}
|
|
221
|
+
>
|
|
222
|
+
{title().subtitle}
|
|
223
|
+
</span>
|
|
224
|
+
</Show>
|
|
225
|
+
<Show when={title().args?.length}>
|
|
226
|
+
<For each={title().args}>
|
|
227
|
+
{(arg) => (
|
|
228
|
+
<span
|
|
229
|
+
data-slot="basic-tool-tool-arg"
|
|
230
|
+
classList={{
|
|
231
|
+
[title().argsClass ?? ""]: !!title().argsClass,
|
|
232
|
+
}}
|
|
233
|
+
>
|
|
234
|
+
{arg}
|
|
235
|
+
</span>
|
|
236
|
+
)}
|
|
237
|
+
</For>
|
|
238
|
+
</Show>
|
|
239
|
+
</Show>
|
|
240
|
+
</div>
|
|
241
|
+
<Show when={!pending() && title().action}>
|
|
242
|
+
<span data-slot="basic-tool-tool-action">{title().action}</span>
|
|
243
|
+
</Show>
|
|
244
|
+
</div>
|
|
245
|
+
)}
|
|
246
|
+
</Match>
|
|
247
|
+
<Match when={true}>{props.trigger as JSX.Element}</Match>
|
|
248
|
+
</Switch>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
<Show when={hasChildren() && !props.hideDetails && !props.locked && (!pending() || props.allowOpenWhilePending)}>
|
|
252
|
+
<Collapsible.Arrow />
|
|
253
|
+
</Show>
|
|
254
|
+
</div>
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<Collapsible open={open()} onOpenChange={handleOpenChange} class="tool-collapsible">
|
|
259
|
+
<Show
|
|
260
|
+
when={props.triggerAsLink || props.triggerHref}
|
|
261
|
+
fallback={
|
|
262
|
+
<Collapsible.Trigger
|
|
263
|
+
data-hide-details={props.hideDetails ? "true" : undefined}
|
|
264
|
+
onClick={props.onTriggerClick}
|
|
265
|
+
>
|
|
266
|
+
{trigger()}
|
|
267
|
+
</Collapsible.Trigger>
|
|
268
|
+
}
|
|
269
|
+
>
|
|
270
|
+
<Collapsible.Trigger
|
|
271
|
+
as="a"
|
|
272
|
+
href={props.triggerHref}
|
|
273
|
+
role={!props.triggerHref && props.clickable ? "button" : undefined}
|
|
274
|
+
tabIndex={!props.triggerHref && props.clickable ? 0 : undefined}
|
|
275
|
+
data-hide-details={props.hideDetails ? "true" : undefined}
|
|
276
|
+
onClick={props.onTriggerClick}
|
|
277
|
+
onKeyDown={props.onTriggerKeyDown}
|
|
278
|
+
>
|
|
279
|
+
{trigger()}
|
|
280
|
+
</Collapsible.Trigger>
|
|
281
|
+
</Show>
|
|
282
|
+
<Show when={props.animated && hasChildren() && !props.hideDetails}>
|
|
283
|
+
<div
|
|
284
|
+
ref={contentRef}
|
|
285
|
+
data-slot="collapsible-content"
|
|
286
|
+
data-animated
|
|
287
|
+
style={{
|
|
288
|
+
height: initialOpen ? "auto" : "0px",
|
|
289
|
+
overflow: initialOpen ? "visible" : "hidden",
|
|
290
|
+
}}
|
|
291
|
+
>
|
|
292
|
+
<Show when={!props.defer || ready()}>{props.children}</Show>
|
|
293
|
+
</div>
|
|
294
|
+
</Show>
|
|
295
|
+
<Show when={!props.animated && hasChildren() && !props.hideDetails}>
|
|
296
|
+
<Collapsible.Content>
|
|
297
|
+
<Show when={!props.defer || ready()}>{props.children}</Show>
|
|
298
|
+
</Collapsible.Content>
|
|
299
|
+
</Show>
|
|
300
|
+
</Collapsible>
|
|
301
|
+
)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function label(input: Record<string, unknown> | undefined) {
|
|
305
|
+
const keys = ["description", "query", "url", "filePath", "path", "pattern", "name"]
|
|
306
|
+
return keys.map((key) => input?.[key]).find((value): value is string => typeof value === "string" && value.length > 0)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function args(input: Record<string, unknown> | undefined) {
|
|
310
|
+
if (!input) return []
|
|
311
|
+
const skip = new Set(["description", "query", "url", "filePath", "path", "pattern", "name"])
|
|
312
|
+
return Object.entries(input)
|
|
313
|
+
.filter(([key]) => !skip.has(key))
|
|
314
|
+
.flatMap(([key, value]) => {
|
|
315
|
+
if (typeof value === "string") return [`${key}=${value}`]
|
|
316
|
+
if (typeof value === "number") return [`${key}=${value}`]
|
|
317
|
+
if (typeof value === "boolean") return [`${key}=${value}`]
|
|
318
|
+
return []
|
|
319
|
+
})
|
|
320
|
+
.slice(0, 3)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function GenericTool(props: {
|
|
324
|
+
tool: string
|
|
325
|
+
status?: string
|
|
326
|
+
hideDetails?: boolean
|
|
327
|
+
input?: Record<string, unknown>
|
|
328
|
+
}) {
|
|
329
|
+
const i18n = useI18n()
|
|
330
|
+
|
|
331
|
+
return (
|
|
332
|
+
<BasicTool
|
|
333
|
+
icon="mcp"
|
|
334
|
+
status={props.status}
|
|
335
|
+
trigger={{
|
|
336
|
+
title: i18n.t("ui.basicTool.called", { tool: props.tool }),
|
|
337
|
+
subtitle: label(props.input),
|
|
338
|
+
args: args(props.input),
|
|
339
|
+
}}
|
|
340
|
+
hideDetails={props.hideDetails}
|
|
341
|
+
/>
|
|
342
|
+
)
|
|
343
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import * as mod from "./dock-prompt"
|
|
3
|
+
import { create } from "@neurocode-ai/ui/storybook/scaffold"
|
|
4
|
+
|
|
5
|
+
const docs = `### Overview
|
|
6
|
+
Docked prompt layout for questions and permission requests.
|
|
7
|
+
|
|
8
|
+
Use with form controls or confirmation buttons in the footer.
|
|
9
|
+
|
|
10
|
+
### API
|
|
11
|
+
- Required: \`kind\` (question | permission), \`header\`, \`children\`, \`footer\`.
|
|
12
|
+
- Optional: \`ref\` for measuring or focus management.
|
|
13
|
+
|
|
14
|
+
### Variants and states
|
|
15
|
+
- Question and permission layouts (data attributes).
|
|
16
|
+
|
|
17
|
+
### Behavior
|
|
18
|
+
- Pure layout component; behavior handled by parent.
|
|
19
|
+
|
|
20
|
+
### Accessibility
|
|
21
|
+
- Ensure header and footer content provide clear context and actions.
|
|
22
|
+
|
|
23
|
+
### Theming/tokens
|
|
24
|
+
- Uses \`data-component="dock-prompt"\` with kind data attribute.
|
|
25
|
+
|
|
26
|
+
`
|
|
27
|
+
|
|
28
|
+
const story = create({
|
|
29
|
+
title: "UI/DockPrompt",
|
|
30
|
+
mod,
|
|
31
|
+
args: {
|
|
32
|
+
kind: "question",
|
|
33
|
+
header: "Header",
|
|
34
|
+
children: "Prompt content",
|
|
35
|
+
footer: "Footer",
|
|
36
|
+
},
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export default {
|
|
40
|
+
title: "UI/DockPrompt",
|
|
41
|
+
id: "components-dock-prompt",
|
|
42
|
+
component: story.meta.component,
|
|
43
|
+
tags: ["autodocs"],
|
|
44
|
+
parameters: {
|
|
45
|
+
docs: {
|
|
46
|
+
description: {
|
|
47
|
+
component: docs,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const Basic = story.Basic
|
|
54
|
+
|
|
55
|
+
export const Permission = {
|
|
56
|
+
args: {
|
|
57
|
+
kind: "permission",
|
|
58
|
+
header: "Allow access?",
|
|
59
|
+
children: "This action needs permission to proceed.",
|
|
60
|
+
footer: "Approve or deny",
|
|
61
|
+
},
|
|
62
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { JSX } from "solid-js"
|
|
2
|
+
import { DockShell, DockTray } from "@neurocode-ai/ui/dock-surface"
|
|
3
|
+
|
|
4
|
+
export function DockPrompt(props: {
|
|
5
|
+
kind: "question" | "permission"
|
|
6
|
+
header: JSX.Element
|
|
7
|
+
children: JSX.Element
|
|
8
|
+
footer: JSX.Element
|
|
9
|
+
ref?: (el: HTMLDivElement) => void
|
|
10
|
+
onKeyDown?: JSX.EventHandlerUnion<HTMLDivElement, KeyboardEvent>
|
|
11
|
+
}) {
|
|
12
|
+
const slot = (name: string) => `${props.kind}-${name}`
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div data-component="dock-prompt" data-kind={props.kind} ref={props.ref} onKeyDown={props.onKeyDown}>
|
|
16
|
+
<DockShell data-slot={slot("body")}>
|
|
17
|
+
<div data-slot={slot("header")}>{props.header}</div>
|
|
18
|
+
<div data-slot={slot("content")}>{props.children}</div>
|
|
19
|
+
</DockShell>
|
|
20
|
+
<DockTray data-slot={slot("footer")}>{props.footer}</DockTray>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|