@kitnai/chat 0.8.1 → 0.9.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/custom-elements.json +247 -0
- package/dist/kitn-chat.es.js +29 -29
- package/dist/llms/llms-full.txt +32 -4
- package/dist/llms/llms.txt +3 -3
- package/frameworks/react/index.tsx +47 -5
- package/llms-full.txt +32 -4
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/components/artifact.tsx +241 -82
- package/src/components/card-fallback.tsx +28 -0
- package/src/components/card-renderer.tsx +52 -0
- package/src/components/component-meta.json +169 -12
- package/src/elements/artifact.stories.tsx +214 -0
- package/src/elements/artifact.tsx +95 -30
- package/src/elements/cards.stories.tsx +54 -0
- package/src/elements/cards.tsx +91 -0
- package/src/elements/compiled.css +1 -1
- package/src/elements/element-meta.json +156 -0
- package/src/elements/element-types.d.ts +34 -0
- package/src/elements/register.ts +1 -0
- package/src/elements/resizable.d.ts +27 -0
- package/src/elements/resizable.stories.tsx +226 -1
- package/src/elements/resizable.tsx +208 -3
- package/src/index.ts +10 -0
- package/src/primitives/card-registry.tsx +58 -0
- package/src/stories/docs/generative-ui-overview.mdx +59 -0
- package/src/ui/resizable.tsx +33 -4
|
@@ -53,6 +53,96 @@
|
|
|
53
53
|
"scalar": true,
|
|
54
54
|
"description": "Accessible title for the preview iframe.",
|
|
55
55
|
"displayType": "undefined | string"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "maximized",
|
|
59
|
+
"type": "undefined | false | true",
|
|
60
|
+
"optional": true,
|
|
61
|
+
"scalar": true,
|
|
62
|
+
"description": "Reflects the artifact's own maximized view-state (usually driven by the protocol).",
|
|
63
|
+
"default": "false",
|
|
64
|
+
"displayType": "undefined | false | true"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "expandable",
|
|
68
|
+
"type": "undefined | false | true",
|
|
69
|
+
"optional": true,
|
|
70
|
+
"scalar": true,
|
|
71
|
+
"description": "Show the expand-to-fill button (OPT-IN).",
|
|
72
|
+
"default": "false",
|
|
73
|
+
"displayType": "undefined | false | true"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "openInTab",
|
|
77
|
+
"type": "undefined | false | true",
|
|
78
|
+
"optional": true,
|
|
79
|
+
"scalar": true,
|
|
80
|
+
"description": "Show the open-in-new-tab button (OPT-IN).",
|
|
81
|
+
"default": "false",
|
|
82
|
+
"displayType": "undefined | false | true"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "noNav",
|
|
86
|
+
"type": "undefined | false | true",
|
|
87
|
+
"optional": true,
|
|
88
|
+
"scalar": true,
|
|
89
|
+
"description": "Hide back/forward.",
|
|
90
|
+
"default": "false",
|
|
91
|
+
"displayType": "undefined | false | true"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"name": "noReload",
|
|
95
|
+
"type": "undefined | false | true",
|
|
96
|
+
"optional": true,
|
|
97
|
+
"scalar": true,
|
|
98
|
+
"description": "Hide reload.",
|
|
99
|
+
"default": "false",
|
|
100
|
+
"displayType": "undefined | false | true"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "noHome",
|
|
104
|
+
"type": "undefined | false | true",
|
|
105
|
+
"optional": true,
|
|
106
|
+
"scalar": true,
|
|
107
|
+
"description": "Hide home.",
|
|
108
|
+
"default": "false",
|
|
109
|
+
"displayType": "undefined | false | true"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "noPathField",
|
|
113
|
+
"type": "undefined | false | true",
|
|
114
|
+
"optional": true,
|
|
115
|
+
"scalar": true,
|
|
116
|
+
"description": "Hide the address field.",
|
|
117
|
+
"default": "false",
|
|
118
|
+
"displayType": "undefined | false | true"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "noTabs",
|
|
122
|
+
"type": "undefined | false | true",
|
|
123
|
+
"optional": true,
|
|
124
|
+
"scalar": true,
|
|
125
|
+
"description": "Hide the Preview|Code toggle.",
|
|
126
|
+
"default": "false",
|
|
127
|
+
"displayType": "undefined | false | true"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "standalone",
|
|
131
|
+
"type": "undefined | false | true",
|
|
132
|
+
"optional": true,
|
|
133
|
+
"scalar": true,
|
|
134
|
+
"description": "Standalone chrome: rounded corners + border (else square, borderless in-panel).",
|
|
135
|
+
"default": "false",
|
|
136
|
+
"displayType": "undefined | false | true"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"name": "readonlyPath",
|
|
140
|
+
"type": "undefined | false | true",
|
|
141
|
+
"optional": true,
|
|
142
|
+
"scalar": true,
|
|
143
|
+
"description": "Show the address but make it read-only (visible, nav-tracking, non-editable).",
|
|
144
|
+
"default": "false",
|
|
145
|
+
"displayType": "undefined | false | true"
|
|
56
146
|
}
|
|
57
147
|
],
|
|
58
148
|
"events": [
|
|
@@ -62,6 +152,12 @@
|
|
|
62
152
|
"description": "Fired when a file is selected. `detail.path`.",
|
|
63
153
|
"displayDetail": "{ path: string }"
|
|
64
154
|
},
|
|
155
|
+
{
|
|
156
|
+
"name": "maximizechange",
|
|
157
|
+
"detail": "{ maximized: false | true }",
|
|
158
|
+
"description": "Artifact's own maximize button toggled (consumer-observable; non-bubbling).",
|
|
159
|
+
"displayDetail": "{ maximized: false | true }"
|
|
160
|
+
},
|
|
65
161
|
{
|
|
66
162
|
"name": "navigate",
|
|
67
163
|
"detail": "{ url: string }",
|
|
@@ -247,6 +343,45 @@
|
|
|
247
343
|
],
|
|
248
344
|
"tokens": []
|
|
249
345
|
},
|
|
346
|
+
{
|
|
347
|
+
"tag": "kc-cards",
|
|
348
|
+
"className": "KcCardsElement",
|
|
349
|
+
"props": [
|
|
350
|
+
{
|
|
351
|
+
"name": "cards",
|
|
352
|
+
"type": "undefined | { type: string; id: string; data: unknown; title?: undefined | string }[]",
|
|
353
|
+
"optional": true,
|
|
354
|
+
"scalar": false,
|
|
355
|
+
"description": "The stream of card envelopes to render. Set as a JS PROPERTY: `el.cards = [...]`.",
|
|
356
|
+
"displayType": "undefined | { type: string; id: string; data: unknown; title?: undefined | string }[]"
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
"name": "types",
|
|
360
|
+
"type": "undefined | Record<string, string>",
|
|
361
|
+
"optional": true,
|
|
362
|
+
"scalar": false,
|
|
363
|
+
"description": "Optional type→tag overrides/additions (merged over the built-ins). Property: `el.types`. Typed as a plain string map (not the `CardTagMap` alias) so the generated React wrapper inlines it instead of emitting an unresolved named type.",
|
|
364
|
+
"displayType": "undefined | Record<string, string>"
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
"name": "policy",
|
|
368
|
+
"type": "undefined | { onSubmitData?: undefined | (cardId: string, data: unknown) => void; onAction?: undefined | (cardId: string, action: string, payload?: unknown) => void; onSendPrompt?: undefined | (text: string, opts: { mode: \"compose\" | \"send\"; context?: unknown; }) => void; onOpen?: undefined | (url: string, target: \"tab\" | \"artifact\") => void; onState?: undefined | (cardId: string, patch: unknown) => void; onDismiss?: undefined | (cardId: string) => void; onError?: undefined | (cardId: string, message: string) => void; maxSendPromptMode?: undefined | \"compose\" | \"send\" }",
|
|
369
|
+
"optional": true,
|
|
370
|
+
"scalar": false,
|
|
371
|
+
"description": "Optional CardPolicy handling child events. Property: `el.policy`.",
|
|
372
|
+
"displayType": "undefined | { onSubmitData?: undefined | (cardId: string, data: unknown) => void; onAction?: undefined | (cardId: string, action: string, payload?: unknown) => void; onSendPrompt?: undefined | (text: string, opts: { mode: \"compose\" | \"send\"; context?: unknown; }) => void; onOpen?: undefined | (url: string, target: \"tab\" | \"artifact\") => void; onState?: undefined | (cardId: string, patch: unknown) => void; onDismiss?: undefined | (cardId: string) => void; onError?: undefined | (cardId: string, message: string) => void; maxSendPromptMode?: undefined | \"compose\" | \"send\" }"
|
|
373
|
+
}
|
|
374
|
+
],
|
|
375
|
+
"events": [],
|
|
376
|
+
"composedFrom": [
|
|
377
|
+
{
|
|
378
|
+
"name": "CardFallback",
|
|
379
|
+
"group": "Components",
|
|
380
|
+
"storyId": "components-cardfallback--docs"
|
|
381
|
+
}
|
|
382
|
+
],
|
|
383
|
+
"tokens": []
|
|
384
|
+
},
|
|
250
385
|
{
|
|
251
386
|
"tag": "kc-chain-of-thought",
|
|
252
387
|
"className": "KcChainOfThoughtElement",
|
|
@@ -1693,6 +1828,15 @@
|
|
|
1693
1828
|
"description": "Layout axis: `horizontal` (row, default) or `vertical` (column).",
|
|
1694
1829
|
"default": "'horizontal'",
|
|
1695
1830
|
"displayType": "undefined | \"horizontal\" | \"vertical\""
|
|
1831
|
+
},
|
|
1832
|
+
{
|
|
1833
|
+
"name": "maximizedIndex",
|
|
1834
|
+
"type": "undefined | null | number",
|
|
1835
|
+
"optional": true,
|
|
1836
|
+
"scalar": false,
|
|
1837
|
+
"description": "Which item index is maximized (null = none). Declarative source of truth.",
|
|
1838
|
+
"default": "null",
|
|
1839
|
+
"displayType": "undefined | null | number"
|
|
1696
1840
|
}
|
|
1697
1841
|
],
|
|
1698
1842
|
"events": [
|
|
@@ -1701,6 +1845,12 @@
|
|
|
1701
1845
|
"detail": "{ sizes: number[] }",
|
|
1702
1846
|
"description": "Fired on drag-end / keyboard resize / visibility change. `detail.sizes` = panel sizes in percent.",
|
|
1703
1847
|
"displayDetail": "{ sizes: number[] }"
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
"name": "maximizechange",
|
|
1851
|
+
"detail": "{ maximized: false | true; index: null | number }",
|
|
1852
|
+
"description": "Observe layout maximize state.",
|
|
1853
|
+
"displayDetail": "{ maximized: false | true; index: null | number }"
|
|
1704
1854
|
}
|
|
1705
1855
|
],
|
|
1706
1856
|
"composedFrom": [
|
|
@@ -1765,6 +1915,12 @@
|
|
|
1765
1915
|
"detail": "unknown",
|
|
1766
1916
|
"description": "",
|
|
1767
1917
|
"displayDetail": "unknown"
|
|
1918
|
+
},
|
|
1919
|
+
{
|
|
1920
|
+
"name": "maximizechange",
|
|
1921
|
+
"detail": "unknown",
|
|
1922
|
+
"description": "",
|
|
1923
|
+
"displayDetail": "unknown"
|
|
1768
1924
|
}
|
|
1769
1925
|
],
|
|
1770
1926
|
"composedFrom": [
|
|
@@ -23,6 +23,26 @@ export interface KcArtifactElement extends HTMLElement {
|
|
|
23
23
|
sandbox?: string;
|
|
24
24
|
/** Accessible title for the preview iframe. */
|
|
25
25
|
iframeTitle?: string;
|
|
26
|
+
/** Reflects the artifact's own maximized view-state (usually driven by the protocol). */
|
|
27
|
+
maximized?: boolean;
|
|
28
|
+
/** Show the expand-to-fill button (OPT-IN). */
|
|
29
|
+
expandable?: boolean;
|
|
30
|
+
/** Show the open-in-new-tab button (OPT-IN). */
|
|
31
|
+
openInTab?: boolean;
|
|
32
|
+
/** Hide back/forward. */
|
|
33
|
+
noNav?: boolean;
|
|
34
|
+
/** Hide reload. */
|
|
35
|
+
noReload?: boolean;
|
|
36
|
+
/** Hide home. */
|
|
37
|
+
noHome?: boolean;
|
|
38
|
+
/** Hide the address field. */
|
|
39
|
+
noPathField?: boolean;
|
|
40
|
+
/** Hide the Preview|Code toggle. */
|
|
41
|
+
noTabs?: boolean;
|
|
42
|
+
/** Standalone chrome: rounded corners + border (else square, borderless in-panel). */
|
|
43
|
+
standalone?: boolean;
|
|
44
|
+
/** Show the address but make it read-only (visible, nav-tracking, non-editable). */
|
|
45
|
+
readonlyPath?: boolean;
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
export interface KcAttachmentsElement extends HTMLElement {
|
|
@@ -55,6 +75,17 @@ export interface KcCardElement extends HTMLElement {
|
|
|
55
75
|
dense?: boolean;
|
|
56
76
|
}
|
|
57
77
|
|
|
78
|
+
export interface KcCardsElement extends HTMLElement {
|
|
79
|
+
/** Color mode (`auto` follows prefers-color-scheme). */
|
|
80
|
+
theme?: 'light' | 'dark' | 'auto';
|
|
81
|
+
/** The stream of card envelopes to render. Set as a JS PROPERTY: `el.cards = [...]`. */
|
|
82
|
+
cards?: { type: string; id: string; data: unknown; title?: string }[];
|
|
83
|
+
/** Optional type→tag overrides/additions (merged over the built-ins). Property: `el.types`. Typed as a plain string map (not the `CardTagMap` alias) so the generated React wrapper inlines it instead of emitting an unresolved named type. */
|
|
84
|
+
types?: Record<string, string>;
|
|
85
|
+
/** Optional CardPolicy handling child events. Property: `el.policy`. */
|
|
86
|
+
policy?: { onSubmitData?: (cardId: string, data: unknown) => void; onAction?: (cardId: string, action: string, payload?: unknown) => void; onSendPrompt?: (text: string, opts: { mode: "compose" | "send"; context?: unknown; }) => void; onOpen?: (url: string, target: "tab" | "artifact") => void; onState?: (cardId: string, patch: unknown) => void; onDismiss?: (cardId: string) => void; onError?: (cardId: string, message: string) => void; maxSendPromptMode?: "compose" | "send" };
|
|
87
|
+
}
|
|
88
|
+
|
|
58
89
|
export interface KcChainOfThoughtElement extends HTMLElement {
|
|
59
90
|
/** Color mode (`auto` follows prefers-color-scheme). */
|
|
60
91
|
theme?: 'light' | 'dark' | 'auto';
|
|
@@ -347,6 +378,8 @@ export interface KcResizableElement extends HTMLElement {
|
|
|
347
378
|
theme?: 'light' | 'dark' | 'auto';
|
|
348
379
|
/** Layout axis: `horizontal` (row, default) or `vertical` (column). */
|
|
349
380
|
orientation?: "horizontal" | "vertical";
|
|
381
|
+
/** Which item index is maximized (null = none). Declarative source of truth. */
|
|
382
|
+
maximizedIndex?: null | number;
|
|
350
383
|
}
|
|
351
384
|
|
|
352
385
|
export interface KcResizableItemElement extends HTMLElement {
|
|
@@ -531,6 +564,7 @@ declare global {
|
|
|
531
564
|
'kc-artifact': KcArtifactElement;
|
|
532
565
|
'kc-attachments': KcAttachmentsElement;
|
|
533
566
|
'kc-card': KcCardElement;
|
|
567
|
+
'kc-cards': KcCardsElement;
|
|
534
568
|
'kc-chain-of-thought': KcChainOfThoughtElement;
|
|
535
569
|
'kc-chat': KcChatElement;
|
|
536
570
|
'kc-checkpoint': KcCheckpointElement;
|
package/src/elements/register.ts
CHANGED
|
@@ -45,6 +45,7 @@ import './link-card';
|
|
|
45
45
|
import './embed';
|
|
46
46
|
import './confirm-card';
|
|
47
47
|
import './task-list-card';
|
|
48
|
+
import './cards';
|
|
48
49
|
|
|
49
50
|
export type { ChatMessage, ChatMessageAction } from './chat-types';
|
|
50
51
|
export { configureCodeHighlighting, isCodeHighlightingEnabled } from '../primitives/highlighter';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/elements/resizable.d.ts
|
|
2
|
+
// Hand-authored ambient types for <kc-resizable>'s maximize capability. The
|
|
3
|
+
// generated element-types.d.ts emits prop/event interfaces only (not methods),
|
|
4
|
+
// so the imperative host API + the cross-element protocol events live here.
|
|
5
|
+
// NOT regenerated by `npm run build`.
|
|
6
|
+
import type { KcMaximizeIntentDetail, KcMaximizeStateDetail } from './resizable';
|
|
7
|
+
|
|
8
|
+
declare global {
|
|
9
|
+
interface KcResizableElement extends HTMLElement {
|
|
10
|
+
/** Which item index is maximized (null = none). Setting it maximizes that
|
|
11
|
+
* item (or restores when set to null) — the declarative source of truth. */
|
|
12
|
+
maximizedIndex: number | null;
|
|
13
|
+
/** Imperatively maximize the item at `index` (thin wrapper over maximizedIndex). */
|
|
14
|
+
maximize(index: number): void;
|
|
15
|
+
/** Imperatively restore from the maximized layout. */
|
|
16
|
+
restore(): void;
|
|
17
|
+
}
|
|
18
|
+
interface HTMLElementTagNameMap {
|
|
19
|
+
'kc-resizable': KcResizableElement;
|
|
20
|
+
}
|
|
21
|
+
interface HTMLElementEventMap {
|
|
22
|
+
'kc-maximize-intent': CustomEvent<KcMaximizeIntentDetail>;
|
|
23
|
+
'kc-maximize-state': CustomEvent<KcMaximizeStateDetail>;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export {};
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
-
import { createSignal, type JSX } from 'solid-js';
|
|
2
|
+
import { createSignal, onMount, type JSX } from 'solid-js';
|
|
3
3
|
import './register'; // side effect: registers the custom elements
|
|
4
4
|
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
5
|
+
import { Resizable, ResizablePanel } from '../ui/resizable';
|
|
6
|
+
import type { ArtifactFile } from '../components/artifact';
|
|
7
|
+
import { Artifact } from '../components/artifact';
|
|
5
8
|
|
|
6
9
|
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
7
10
|
declare module 'solid-js' {
|
|
@@ -12,10 +15,23 @@ declare module 'solid-js' {
|
|
|
12
15
|
'kc-resizable-item': JSX.HTMLAttributes<HTMLElement> & {
|
|
13
16
|
size?: string; min?: string; max?: string; locked?: boolean | string; hidden?: boolean | string;
|
|
14
17
|
};
|
|
18
|
+
// `kc-artifact` JSX type is augmented (with the full attr set) in
|
|
19
|
+
// artifact.stories.tsx — declaring it again here with a different shape
|
|
20
|
+
// would trip TS2717 (module-augmentation merges must match). Reuse that one.
|
|
15
21
|
}
|
|
16
22
|
}
|
|
17
23
|
}
|
|
18
24
|
|
|
25
|
+
// Fixture base URL (served by Storybook staticDirs from examples/artifact-fixtures).
|
|
26
|
+
const BASE = '/artifact-fixtures';
|
|
27
|
+
|
|
28
|
+
const ARTIFACT_FILES: ArtifactFile[] = [
|
|
29
|
+
{ path: 'index.html', url: `${BASE}/index.html`, type: 'html', language: 'html',
|
|
30
|
+
code: `<!DOCTYPE html><html lang="en"><head><title>Preview</title></head><body><h1>Starboard</h1></body></html>` },
|
|
31
|
+
{ path: 'about.html', url: `${BASE}/about.html`, type: 'html', language: 'html',
|
|
32
|
+
code: `<!DOCTYPE html><html lang="en"><head><title>About</title></head><body><h1>About</h1></body></html>` },
|
|
33
|
+
];
|
|
34
|
+
|
|
19
35
|
/** A labelled placeholder pane so the layout is visible in stories. */
|
|
20
36
|
function Pane(props: { label: string; tone?: 'muted' | 'plain' }) {
|
|
21
37
|
return (
|
|
@@ -198,3 +214,212 @@ export const HiddenToggle: Story = {
|
|
|
198
214
|
);
|
|
199
215
|
},
|
|
200
216
|
};
|
|
217
|
+
|
|
218
|
+
const EXPAND_TO_FILL_SNIPPET = `<!-- The artifact's "Expand" button (opt-in: expandable) fires a bubbling
|
|
219
|
+
kc-maximize-intent event. The nearest enclosing <kc-resizable> catches it
|
|
220
|
+
automatically and hides siblings so the preview panel fills the container.
|
|
221
|
+
No wiring needed between the two elements — the protocol is zero-config. -->
|
|
222
|
+
|
|
223
|
+
<kc-resizable orientation="horizontal" style="display:block;height:480px">
|
|
224
|
+
<kc-resizable-item size="20%" min="120px"> …list… </kc-resizable-item>
|
|
225
|
+
<kc-resizable-item> …chat… </kc-resizable-item>
|
|
226
|
+
<kc-resizable-item size="35%" min="200px">
|
|
227
|
+
<!-- expandable opt-in: adds the Expand/Collapse button -->
|
|
228
|
+
<kc-artifact
|
|
229
|
+
src="…/index.html"
|
|
230
|
+
expandable
|
|
231
|
+
></kc-artifact>
|
|
232
|
+
</kc-resizable-item>
|
|
233
|
+
</kc-resizable>
|
|
234
|
+
|
|
235
|
+
<script type="module">
|
|
236
|
+
import '@kitnai/chat/elements';
|
|
237
|
+
|
|
238
|
+
// Optional: observe the layout events.
|
|
239
|
+
document.querySelector('kc-resizable')
|
|
240
|
+
.addEventListener('change', (e) => console.log('change', e.detail.sizes));
|
|
241
|
+
document.querySelector('kc-resizable')
|
|
242
|
+
.addEventListener('maximizechange', (e) => console.log('maximizechange', e.detail));
|
|
243
|
+
document.querySelector('kc-artifact')
|
|
244
|
+
.addEventListener('maximizechange', (e) => console.log('artifact maximizechange', e.detail));
|
|
245
|
+
</script>
|
|
246
|
+
|
|
247
|
+
<!-- Cross-element protocol (hand-authored; not generated):
|
|
248
|
+
• kc-maximize-intent — CustomEvent, bubbles:true, composed:true
|
|
249
|
+
Fired by <kc-artifact> when the Expand/Collapse button is clicked.
|
|
250
|
+
detail: { requested: boolean } (true = maximize, false = restore)
|
|
251
|
+
• kc-maximize-state — CustomEvent, bubbles:false, composed:true
|
|
252
|
+
Dispatched by <kc-resizable> back DOWN onto the maximized <kc-resizable-item>
|
|
253
|
+
(on maximize) or the group host + the formerly-maximized item (on restore)
|
|
254
|
+
so the artifact can reconcile its button label.
|
|
255
|
+
detail: { maximized: boolean }
|
|
256
|
+
These protocol events are NOT listed in the generated web-components.md because
|
|
257
|
+
they are cross-element — the generator only documents per-element dispatch events. -->`;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* **Expand to fill** — the headline integration: the artifact's expand button
|
|
261
|
+
* fills the preview panel to the full container width. No wiring between the
|
|
262
|
+
* two elements is needed — clicking **Expand** fires a `kc-maximize-intent` event
|
|
263
|
+
* that bubbles up to the nearest enclosing `<kc-resizable>`, which hides siblings
|
|
264
|
+
* and lets the preview panel fill. **Collapse** (or **Escape**) restores the
|
|
265
|
+
* original layout.
|
|
266
|
+
*
|
|
267
|
+
* **Cross-element protocol (hand-authored docs):**
|
|
268
|
+
* - `kc-maximize-intent` — `bubbles:true, composed:true`; fired by `<kc-artifact>`
|
|
269
|
+
* when Expand/Collapse is toggled. `detail: { requested: boolean }`.
|
|
270
|
+
* - `kc-maximize-state` — `bubbles:false, composed:true`; dispatched by
|
|
271
|
+
* `<kc-resizable>` back down to the affected `<kc-resizable-item>` so the
|
|
272
|
+
* artifact can reconcile its button. `detail: { maximized: boolean }`.
|
|
273
|
+
*
|
|
274
|
+
* These protocol events are NOT in the generated `web-components.md`
|
|
275
|
+
* (the generator only documents per-element `dispatch` events — resolved decision #1).
|
|
276
|
+
*/
|
|
277
|
+
export const ExpandToFill: Story = {
|
|
278
|
+
name: 'Expand to fill',
|
|
279
|
+
render: () => {
|
|
280
|
+
const [log, setLog] = createSignal<string[]>([]);
|
|
281
|
+
let artifactEl: HTMLElement & { files?: ArtifactFile[] };
|
|
282
|
+
let resizableEl: HTMLElement;
|
|
283
|
+
onMount(() => {
|
|
284
|
+
if (artifactEl) artifactEl.files = ARTIFACT_FILES;
|
|
285
|
+
if (resizableEl) {
|
|
286
|
+
resizableEl.addEventListener('change', (e: Event) =>
|
|
287
|
+
setLog((l) => [`change → ${JSON.stringify((e as CustomEvent).detail.sizes)}`, ...l].slice(0, 6)),
|
|
288
|
+
);
|
|
289
|
+
resizableEl.addEventListener('maximizechange', (e: Event) =>
|
|
290
|
+
setLog((l) => [`maximizechange → ${JSON.stringify((e as CustomEvent).detail)}`, ...l].slice(0, 6)),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
if (artifactEl) {
|
|
294
|
+
artifactEl.addEventListener('maximizechange', (e: Event) =>
|
|
295
|
+
setLog((l) => [`artifact maximizechange → ${JSON.stringify((e as CustomEvent).detail)}`, ...l].slice(0, 6)),
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
return (
|
|
300
|
+
<div style={{ display: 'flex', 'flex-direction': 'column', gap: '8px' }}>
|
|
301
|
+
<div
|
|
302
|
+
style={{
|
|
303
|
+
height: '480px',
|
|
304
|
+
width: '100%',
|
|
305
|
+
'max-width': '900px',
|
|
306
|
+
border: '1px solid var(--color-border, #e4e4e7)',
|
|
307
|
+
'border-radius': '8px',
|
|
308
|
+
overflow: 'hidden',
|
|
309
|
+
}}
|
|
310
|
+
>
|
|
311
|
+
<kc-resizable
|
|
312
|
+
ref={(e) => (resizableEl = e as HTMLElement)}
|
|
313
|
+
orientation="horizontal"
|
|
314
|
+
>
|
|
315
|
+
<kc-resizable-item size="20%" min="120px">
|
|
316
|
+
<Pane label="List" />
|
|
317
|
+
</kc-resizable-item>
|
|
318
|
+
<kc-resizable-item min="140px">
|
|
319
|
+
<Pane label="Chat" tone="plain" />
|
|
320
|
+
</kc-resizable-item>
|
|
321
|
+
<kc-resizable-item size="35%" min="200px">
|
|
322
|
+
<kc-artifact
|
|
323
|
+
ref={(e) => (artifactEl = e as HTMLElement & { files?: ArtifactFile[] })}
|
|
324
|
+
src={`${BASE}/index.html`}
|
|
325
|
+
iframe-title="Starboard artifact preview"
|
|
326
|
+
expandable
|
|
327
|
+
/>
|
|
328
|
+
</kc-resizable-item>
|
|
329
|
+
</kc-resizable>
|
|
330
|
+
</div>
|
|
331
|
+
<pre
|
|
332
|
+
style={{
|
|
333
|
+
'font-size': '12px',
|
|
334
|
+
'max-width': '900px',
|
|
335
|
+
padding: '10px 12px',
|
|
336
|
+
'border-radius': '8px',
|
|
337
|
+
background: 'var(--color-muted, #f4f4f5)',
|
|
338
|
+
color: 'var(--color-muted-foreground, #71717a)',
|
|
339
|
+
'min-height': '72px',
|
|
340
|
+
margin: '0',
|
|
341
|
+
}}
|
|
342
|
+
>
|
|
343
|
+
{log().length ? log().join('\n') : '(click the ⤢ Expand button in the preview toolbar…)'}
|
|
344
|
+
</pre>
|
|
345
|
+
</div>
|
|
346
|
+
);
|
|
347
|
+
},
|
|
348
|
+
parameters: { docs: { source: { code: EXPAND_TO_FILL_SNIPPET, language: 'html' } } },
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const SOLID_PARITY_SNIPPET = `// SolidJS — Artifact inside Resizable with maximizedIndex/onMaximizeChange.
|
|
352
|
+
// No web components needed; works in a pure-Solid app.
|
|
353
|
+
import { createSignal } from 'solid-js';
|
|
354
|
+
import { Artifact } from '@kitnai/chat/components';
|
|
355
|
+
import { Resizable, ResizablePanel } from '@kitnai/chat/ui';
|
|
356
|
+
|
|
357
|
+
function App() {
|
|
358
|
+
const [maximizedIndex, setMaximizedIndex] = createSignal<number | null>(null);
|
|
359
|
+
return (
|
|
360
|
+
<Resizable
|
|
361
|
+
maximizedIndex={maximizedIndex()}
|
|
362
|
+
onMaximizeChange={setMaximizedIndex}
|
|
363
|
+
style="height:480px"
|
|
364
|
+
>
|
|
365
|
+
<ResizablePanel defaultSize="20%" minSize="120px">…list…</ResizablePanel>
|
|
366
|
+
<ResizablePanel minSize="140px">…chat…</ResizablePanel>
|
|
367
|
+
<ResizablePanel defaultSize="35%" minSize="200px">
|
|
368
|
+
<Artifact
|
|
369
|
+
src="…"
|
|
370
|
+
expandable
|
|
371
|
+
maximized={maximizedIndex() === 2}
|
|
372
|
+
onMaximizeChange={(m) => setMaximizedIndex(m ? 2 : null)}
|
|
373
|
+
/>
|
|
374
|
+
</ResizablePanel>
|
|
375
|
+
</Resizable>
|
|
376
|
+
);
|
|
377
|
+
}`;
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* **SolidJS parity** — the same `list | chat | artifact` layout using the Solid
|
|
381
|
+
* `Resizable` convenience and the `Artifact` component directly (no web components).
|
|
382
|
+
* `maximizedIndex` / `onMaximizeChange` on `Resizable` mirror the web-component
|
|
383
|
+
* protocol at the Solid level. Wire `Artifact`'s `onMaximizeChange` to set the
|
|
384
|
+
* index and pass `maximized` back down to drive the button.
|
|
385
|
+
*/
|
|
386
|
+
export const SolidParity: Story = {
|
|
387
|
+
name: 'SolidJS parity (Resizable + Artifact)',
|
|
388
|
+
render: () => {
|
|
389
|
+
const [maximizedIndex, setMaximizedIndex] = createSignal<number | null>(null);
|
|
390
|
+
return (
|
|
391
|
+
<div
|
|
392
|
+
style={{
|
|
393
|
+
height: '480px',
|
|
394
|
+
width: '100%',
|
|
395
|
+
'max-width': '900px',
|
|
396
|
+
border: '1px solid var(--color-border, #e4e4e7)',
|
|
397
|
+
'border-radius': '8px',
|
|
398
|
+
overflow: 'hidden',
|
|
399
|
+
}}
|
|
400
|
+
>
|
|
401
|
+
<Resizable
|
|
402
|
+
maximizedIndex={maximizedIndex()}
|
|
403
|
+
onMaximizeChange={setMaximizedIndex}
|
|
404
|
+
>
|
|
405
|
+
<ResizablePanel defaultSize="20%" minSize="120px">
|
|
406
|
+
<Pane label="List" />
|
|
407
|
+
</ResizablePanel>
|
|
408
|
+
<ResizablePanel minSize="140px">
|
|
409
|
+
<Pane label="Chat" tone="plain" />
|
|
410
|
+
</ResizablePanel>
|
|
411
|
+
<ResizablePanel defaultSize="35%" minSize="200px">
|
|
412
|
+
<Artifact
|
|
413
|
+
src={`${BASE}/index.html`}
|
|
414
|
+
iframeTitle="Starboard artifact preview"
|
|
415
|
+
expandable
|
|
416
|
+
maximized={maximizedIndex() === 2}
|
|
417
|
+
onMaximizeChange={(m) => setMaximizedIndex(m ? 2 : null)}
|
|
418
|
+
/>
|
|
419
|
+
</ResizablePanel>
|
|
420
|
+
</Resizable>
|
|
421
|
+
</div>
|
|
422
|
+
);
|
|
423
|
+
},
|
|
424
|
+
parameters: { docs: { source: { code: SOLID_PARITY_SNIPPET, language: 'tsx' } } },
|
|
425
|
+
};
|