@kitnai/chat 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -9
- package/dist/custom-elements.json +1676 -881
- package/dist/kitn-chat.es.js +36 -36
- package/dist/llms/llms-full.txt +316 -155
- package/dist/llms/llms.txt +18 -18
- package/dist/schemas/card-envelope.schema.json +14 -0
- package/dist/schemas/card-event.schema.json +12 -0
- package/dist/schemas/confirm.schema.json +65 -0
- package/dist/schemas/embed.schema.json +65 -0
- package/dist/schemas/form.result.schema.json +7 -0
- package/dist/schemas/form.schema.json +33 -0
- package/dist/schemas/link.schema.json +56 -0
- package/dist/schemas/task-list.result.schema.json +16 -0
- package/dist/schemas/task-list.schema.json +78 -0
- package/dist/theme.tokens.css +65 -65
- package/dist/tsx-B8rCNbgL.js +1 -0
- package/dist/typescript-RycA9KXf.js +1 -0
- package/frameworks/react/index.tsx +382 -193
- package/frameworks/react/runtime.tsx +2 -2
- package/llms-full.txt +316 -155
- package/llms.txt +18 -18
- package/package.json +5 -2
- package/src/components/artifact.stories.tsx +138 -0
- package/src/components/artifact.tsx +581 -0
- package/src/components/attachments.stories.tsx +7 -8
- package/src/components/attachments.tsx +2 -2
- package/src/components/card.tsx +110 -0
- package/src/components/chain-of-thought.stories.tsx +7 -8
- package/src/components/chat-container.stories.tsx +7 -8
- package/src/components/chat-container.tsx +4 -0
- package/src/components/checkpoint.stories.tsx +7 -8
- package/src/components/code-block.stories.tsx +8 -9
- package/src/components/component-meta.json +3411 -0
- package/src/components/confirm-card.stories.tsx +74 -0
- package/src/components/confirm-card.tsx +299 -0
- package/src/components/context.stories.tsx +7 -8
- package/src/components/conversation-item.stories.tsx +7 -8
- package/src/components/conversation-item.tsx +2 -2
- package/src/components/conversation-list.stories.tsx +7 -8
- package/src/components/conversation-list.tsx +1 -1
- package/src/components/embed.tsx +196 -0
- package/src/components/empty.stories.tsx +8 -9
- package/src/components/feedback-bar.stories.tsx +7 -8
- package/src/components/file-tree.stories.tsx +73 -0
- package/src/components/file-tree.tsx +383 -0
- package/src/components/file-upload.stories.tsx +7 -8
- package/src/components/form-widgets.tsx +461 -0
- package/src/components/form.tsx +796 -0
- package/src/components/image.stories.tsx +7 -8
- package/src/components/link-card.tsx +194 -0
- package/src/components/loader.stories.tsx +7 -8
- package/src/components/markdown.stories.tsx +7 -8
- package/src/components/message-narrow.stories.tsx +12 -13
- package/src/components/message-skills.stories.tsx +16 -17
- package/src/components/message.stories.tsx +17 -18
- package/src/components/model-switcher.stories.tsx +7 -8
- package/src/components/prompt-input.stories.tsx +8 -9
- package/src/components/prompt-suggestion.stories.tsx +7 -8
- package/src/components/prompt-suggestion.tsx +3 -3
- package/src/components/reasoning.stories.tsx +7 -8
- package/src/components/scroll-button.stories.tsx +7 -8
- package/src/components/slash-command.stories.tsx +8 -9
- package/src/components/slash-command.tsx +2 -2
- package/src/components/source.stories.tsx +7 -8
- package/src/components/source.tsx +1 -1
- package/src/components/task-list-card.stories.tsx +78 -0
- package/src/components/task-list-card.tsx +388 -0
- package/src/components/text-shimmer.stories.tsx +7 -8
- package/src/components/thinking-bar.stories.tsx +7 -8
- package/src/components/tool.stories.tsx +7 -8
- package/src/components/tool.tsx +2 -2
- package/src/components/voice-input.stories.tsx +7 -8
- package/src/elements/artifact.stories.tsx +291 -0
- package/src/elements/artifact.tsx +72 -0
- package/src/elements/{kitn-attachments.stories.tsx → attachments.stories.tsx} +11 -11
- package/src/elements/attachments.tsx +4 -4
- package/src/elements/card.stories.tsx +118 -0
- package/src/elements/card.tsx +40 -0
- package/src/elements/catalog.stories.tsx +491 -0
- package/src/elements/{kitn-chain-of-thought.stories.tsx → chain-of-thought.stories.tsx} +13 -13
- package/src/elements/chain-of-thought.tsx +3 -3
- package/src/elements/{kitn-chat-scope-picker.stories.tsx → chat-scope-picker.stories.tsx} +10 -10
- package/src/elements/chat-scope-picker.tsx +4 -4
- package/src/elements/{kitn-chat-workspace.stories.tsx → chat-workspace.stories.tsx} +71 -29
- package/src/elements/chat-workspace.tsx +29 -3
- package/src/elements/{kitn-chat.stories.tsx → chat.stories.tsx} +61 -16
- package/src/elements/chat.tsx +23 -2
- package/src/elements/{kitn-checkpoint.stories.tsx → checkpoint.stories.tsx} +11 -11
- package/src/elements/checkpoint.tsx +4 -4
- package/src/elements/{kitn-code-block.stories.tsx → code-block.stories.tsx} +10 -10
- package/src/elements/code-block.tsx +3 -3
- package/src/elements/compiled.css +1 -1
- package/src/elements/composed-shell.stories.tsx +316 -0
- package/src/elements/confirm-card.stories.tsx +186 -0
- package/src/elements/confirm-card.tsx +45 -0
- package/src/elements/{kitn-context-meter.stories.tsx → context-meter.stories.tsx} +10 -10
- package/src/elements/context-meter.tsx +3 -3
- package/src/elements/{kitn-conversation-list.stories.tsx → conversation-list.stories.tsx} +35 -22
- package/src/elements/conversation-list.tsx +11 -2
- package/src/elements/css.ts +1 -1
- package/src/elements/define.tsx +10 -10
- package/src/elements/element-meta.json +2649 -0
- package/src/elements/element-types.d.ts +251 -125
- package/src/elements/embed.stories.tsx +197 -0
- package/src/elements/embed.tsx +35 -0
- package/src/elements/{kitn-empty.stories.tsx → empty.stories.tsx} +12 -12
- package/src/elements/empty.tsx +3 -3
- package/src/elements/{kitn-feedback-bar.stories.tsx → feedback-bar.stories.tsx} +11 -11
- package/src/elements/feedback-bar.tsx +4 -4
- package/src/elements/file-tree.stories.tsx +133 -0
- package/src/elements/file-tree.tsx +52 -0
- package/src/elements/{kitn-file-upload.stories.tsx → file-upload.stories.tsx} +12 -12
- package/src/elements/file-upload.tsx +4 -4
- package/src/elements/form.stories.tsx +204 -0
- package/src/elements/form.tsx +37 -0
- package/src/elements/{kitn-image.stories.tsx → image.stories.tsx} +10 -10
- package/src/elements/image.tsx +3 -3
- package/src/elements/link-card.stories.tsx +193 -0
- package/src/elements/link-card.tsx +34 -0
- package/src/elements/{kitn-loader.stories.tsx → loader.stories.tsx} +11 -11
- package/src/elements/loader.tsx +3 -3
- package/src/elements/{kitn-markdown.stories.tsx → markdown.stories.tsx} +10 -10
- package/src/elements/markdown.tsx +3 -3
- package/src/elements/{kitn-message-skills.stories.tsx → message-skills.stories.tsx} +10 -10
- package/src/elements/message-skills.tsx +3 -3
- package/src/elements/{kitn-message.stories.tsx → message.stories.tsx} +12 -12
- package/src/elements/message.tsx +5 -5
- package/src/elements/{kitn-model-switcher.stories.tsx → model-switcher.stories.tsx} +10 -10
- package/src/elements/model-switcher.tsx +5 -5
- package/src/elements/{kitn-prompt-input.stories.tsx → prompt-input.stories.tsx} +41 -19
- package/src/elements/prompt-input.tsx +5 -5
- package/src/elements/{kitn-prompt-suggestions.stories.tsx → prompt-suggestions.stories.tsx} +13 -13
- package/src/elements/prompt-suggestions.tsx +4 -4
- package/src/elements/{kitn-reasoning.stories.tsx → reasoning.stories.tsx} +10 -10
- package/src/elements/reasoning.tsx +4 -4
- package/src/elements/register.ts +11 -1
- package/src/elements/resizable.stories.tsx +200 -0
- package/src/elements/resizable.tsx +264 -0
- package/src/elements/{kitn-response-stream.stories.tsx → response-stream.stories.tsx} +10 -10
- package/src/elements/response-stream.tsx +4 -4
- package/src/elements/{kitn-source-list.stories.tsx → source-list.stories.tsx} +11 -11
- package/src/elements/{kitn-source.stories.tsx → source.stories.tsx} +12 -12
- package/src/elements/source.tsx +5 -5
- package/src/elements/styles.css +140 -1
- package/src/elements/task-list-card.stories.tsx +194 -0
- package/src/elements/task-list-card.tsx +40 -0
- package/src/elements/{kitn-text-shimmer.stories.tsx → text-shimmer.stories.tsx} +10 -10
- package/src/elements/text-shimmer.tsx +3 -3
- package/src/elements/{kitn-thinking-bar.stories.tsx → thinking-bar.stories.tsx} +11 -11
- package/src/elements/thinking-bar.tsx +5 -5
- package/src/elements/{kitn-tool.stories.tsx → tool.stories.tsx} +10 -10
- package/src/elements/tool.tsx +3 -3
- package/src/elements/{kitn-voice-input.stories.tsx → voice-input.stories.tsx} +10 -10
- package/src/elements/voice-input.tsx +4 -4
- package/src/index.ts +94 -2
- package/src/primitives/card-contract.ts +60 -0
- package/src/primitives/card-host.tsx +35 -0
- package/src/primitives/card-routing.ts +79 -0
- package/src/primitives/card-schemas/card-envelope.schema.json +14 -0
- package/src/primitives/card-schemas/card-event.schema.json +12 -0
- package/src/primitives/card-schemas/confirm.schema.json +65 -0
- package/src/primitives/card-schemas/embed.schema.json +65 -0
- package/src/primitives/card-schemas/form.result.schema.json +7 -0
- package/src/primitives/card-schemas/form.schema.json +33 -0
- package/src/primitives/card-schemas/link.schema.json +56 -0
- package/src/primitives/card-schemas/task-list.result.schema.json +16 -0
- package/src/primitives/card-schemas/task-list.schema.json +78 -0
- package/src/primitives/card-validate.ts +95 -0
- package/src/primitives/embed-providers.ts +254 -0
- package/src/primitives/highlighter.ts +4 -0
- package/src/primitives/link-preview.ts +87 -0
- package/src/primitives/pdf-preview.ts +121 -0
- package/src/stories/chat-panel-layout.stories.tsx +2 -1
- package/src/stories/chat-scene.tsx +22 -21
- package/src/stories/checkpoint-restore.stories.tsx +10 -10
- package/src/stories/conversation-with-reasoning.stories.tsx +4 -4
- package/src/stories/conversation-with-sources.stories.tsx +7 -7
- package/src/stories/docs/Accessibility.mdx +2 -2
- package/src/stories/docs/ForAIAgents.mdx +3 -3
- package/src/stories/docs/GettingStarted.mdx +2 -2
- package/src/stories/docs/Installation.mdx +2 -2
- package/src/stories/docs/Integrations.mdx +29 -29
- package/src/stories/docs/Introduction.mdx +3 -3
- package/src/stories/docs/Theming.mdx +2 -2
- package/src/stories/docs/element-controls.ts +60 -0
- package/src/stories/docs/theme-editor/theme-editor.tsx +1 -0
- package/src/stories/examples/ChoosingComponents.mdx +94 -0
- package/src/stories/examples/sample-data.ts +79 -0
- package/src/stories/message-actions.stories.tsx +13 -13
- package/src/stories/pattern-centered-conversation.stories.tsx +3 -3
- package/src/stories/pattern-docked-widget.stories.tsx +1 -1
- package/src/stories/pattern-empty-state.stories.tsx +3 -3
- package/src/stories/prompt-input-variants.stories.tsx +13 -13
- package/src/stories/streaming-response.stories.tsx +3 -3
- package/src/stories/typography.stories.tsx +4 -4
- package/src/ui/avatar.stories.tsx +7 -8
- package/src/ui/badge.stories.tsx +7 -8
- package/src/ui/button.stories.tsx +8 -9
- package/src/ui/button.tsx +1 -0
- package/src/ui/collapsible.stories.tsx +6 -7
- package/src/ui/dropdown.stories.tsx +6 -7
- package/src/ui/hover-card.stories.tsx +6 -7
- package/src/ui/resizable.stories.tsx +74 -9
- package/src/ui/resizable.tsx +351 -71
- package/src/ui/scroll-area.stories.tsx +6 -7
- package/src/ui/scroll-area.tsx +3 -1
- package/src/ui/separator.stories.tsx +7 -8
- package/src/ui/skeleton.stories.tsx +7 -8
- package/src/ui/textarea.stories.tsx +6 -7
- package/src/ui/tooltip.stories.tsx +8 -9
- package/theme.css +65 -65
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
2
|
import { TextShimmer } from './text-shimmer';
|
|
3
|
+
import { componentDescription } from '../stories/docs/element-controls';
|
|
3
4
|
|
|
4
5
|
const meta = {
|
|
5
6
|
title: 'Components/TextShimmer',
|
|
@@ -9,14 +10,12 @@ const meta = {
|
|
|
9
10
|
layout: 'padded',
|
|
10
11
|
docs: {
|
|
11
12
|
controls: { exclude: ['use:eventListener'] },
|
|
12
|
-
description:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
].join('\n\n'),
|
|
19
|
-
},
|
|
13
|
+
description: componentDescription([
|
|
14
|
+
'An animated text effect that sweeps a light gradient across its content, rendered into any element tag via `as`.',
|
|
15
|
+
'**When to use:** to signal an in-progress / loading state with a label — e.g. "Thinking", "Generating", "Searching" — or to draw subtle attention to a transient status line.',
|
|
16
|
+
'**How to use:** wrap text in `<TextShimmer>`; tune `duration` (seconds per sweep) and `spread` (gradient width, clamped 5–45). Use `as` to change the wrapping tag (e.g. `"h2"`) and pass any standard HTML attributes.',
|
|
17
|
+
'**Placement:** loading indicators, streaming status lines, thinking bars, and placeholder labels.',
|
|
18
|
+
]),
|
|
20
19
|
},
|
|
21
20
|
},
|
|
22
21
|
argTypes: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
2
|
import { fn } from 'storybook/test';
|
|
3
3
|
import { ThinkingBar } from './thinking-bar';
|
|
4
|
+
import { componentDescription } from '../stories/docs/element-controls';
|
|
4
5
|
|
|
5
6
|
const meta = {
|
|
6
7
|
title: 'Components/ThinkingBar',
|
|
@@ -10,14 +11,12 @@ const meta = {
|
|
|
10
11
|
layout: 'padded',
|
|
11
12
|
docs: {
|
|
12
13
|
controls: { exclude: ['use:eventListener'] },
|
|
13
|
-
description:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
].join('\n\n'),
|
|
20
|
-
},
|
|
14
|
+
description: componentDescription([
|
|
15
|
+
'A status row that shows a shimmering "thinking" label, optionally clickable to expand reasoning, with an optional stop/cancel action.',
|
|
16
|
+
'**When to use:** while the assistant is generating or reasoning, to show live activity and give the user a way to interrupt or to open the reasoning trace.',
|
|
17
|
+
'**How to use:** set `text` for the label. Pass `onClick` to make the label a button (adds a chevron) — typically to toggle a reasoning panel. Pass `onStop` (with optional `stopLabel`) to render the interrupt action.',
|
|
18
|
+
'**Placement:** above or in place of a streaming message, or at the top of a reasoning/chain-of-thought block.',
|
|
19
|
+
]),
|
|
21
20
|
},
|
|
22
21
|
},
|
|
23
22
|
argTypes: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
2
|
import { Tool } from './tool';
|
|
3
3
|
import type { ToolPart } from './tool';
|
|
4
|
+
import { componentDescription } from '../stories/docs/element-controls';
|
|
4
5
|
|
|
5
6
|
const streamingPart: ToolPart = {
|
|
6
7
|
type: 'search_documents',
|
|
@@ -40,14 +41,12 @@ const meta = {
|
|
|
40
41
|
layout: 'padded',
|
|
41
42
|
docs: {
|
|
42
43
|
controls: { exclude: ['use:eventListener'] },
|
|
43
|
-
description:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
].join('\n\n'),
|
|
50
|
-
},
|
|
44
|
+
description: componentDescription([
|
|
45
|
+
'A collapsible panel that visualizes a single tool call — its name, state (processing / ready / completed / error), input, output, error, and call ID.',
|
|
46
|
+
'**When to use:** to surface assistant tool/function calls in the conversation, so users can inspect what was run and what came back.',
|
|
47
|
+
'**How to use:** pass a `toolPart` describing the call (`type`, `state`, optional `input`, `output`, `errorText`, `toolCallId`). State drives the icon and badge automatically. Set `defaultOpen` to start expanded.',
|
|
48
|
+
'**Placement:** inline within an assistant message, typically between text segments where the tool was invoked.',
|
|
49
|
+
]),
|
|
51
50
|
},
|
|
52
51
|
},
|
|
53
52
|
argTypes: {
|
package/src/components/tool.tsx
CHANGED
|
@@ -150,7 +150,7 @@ function Tool(props: ToolProps) {
|
|
|
150
150
|
<Show when={output()}>
|
|
151
151
|
<div>
|
|
152
152
|
<h4 class="text-muted-foreground mb-2 text-sm font-medium">Output</h4>
|
|
153
|
-
<div class="max-h-60 overflow-auto rounded bg-muted/50 p-2 font-mono text-sm">
|
|
153
|
+
<div tabindex={0} class="max-h-60 overflow-auto rounded bg-muted/50 p-2 font-mono text-sm">
|
|
154
154
|
<pre class="whitespace-pre-wrap">{formatValue(output())}</pre>
|
|
155
155
|
</div>
|
|
156
156
|
</div>
|
|
@@ -158,7 +158,7 @@ function Tool(props: ToolProps) {
|
|
|
158
158
|
|
|
159
159
|
<Show when={state() === 'output-error' && local.toolPart.errorText}>
|
|
160
160
|
<div>
|
|
161
|
-
<h4 class="mb-2 text-sm font-medium text-red-
|
|
161
|
+
<h4 class="mb-2 text-sm font-medium text-red-600 dark:text-red-400">Error</h4>
|
|
162
162
|
<div class="rounded bg-red-500/10 p-2 text-sm">
|
|
163
163
|
{local.toolPart.errorText}
|
|
164
164
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
2
|
import { fn } from 'storybook/test';
|
|
3
3
|
import { VoiceInput } from './voice-input';
|
|
4
|
+
import { componentDescription } from '../stories/docs/element-controls';
|
|
4
5
|
|
|
5
6
|
/** Sample transcription handler — resolves the recorded audio to text. */
|
|
6
7
|
const transcribe = async (_audio: Blob): Promise<string> => {
|
|
@@ -16,14 +17,12 @@ const meta = {
|
|
|
16
17
|
layout: 'padded',
|
|
17
18
|
docs: {
|
|
18
19
|
controls: { exclude: ['use:eventListener'] },
|
|
19
|
-
description:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
].join('\n\n'),
|
|
26
|
-
},
|
|
20
|
+
description: componentDescription([
|
|
21
|
+
'A microphone button that records audio, shows recording (pulse rings) and processing (spinner) states, then hands the audio off for transcription.',
|
|
22
|
+
'**When to use:** to let users dictate input by voice instead of typing — speech-to-text for the prompt field.',
|
|
23
|
+
'**How to use:** provide `onTranscribe(audio)` returning a `Promise<string>` (your STT call) and `onTranscription(text)` to receive the result. Click toggles recording; transcription runs automatically on stop. Set `disabled` to block input.',
|
|
24
|
+
'**Placement:** inside the prompt input action bar, next to send and other input actions.',
|
|
25
|
+
]),
|
|
27
26
|
},
|
|
28
27
|
},
|
|
29
28
|
argTypes: {
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { createSignal, onMount, type JSX } from 'solid-js';
|
|
3
|
+
import './register'; // side effect: registers the custom elements
|
|
4
|
+
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
5
|
+
import type { ArtifactFile } from '../components/artifact';
|
|
6
|
+
|
|
7
|
+
// Custom DOM elements — declare the tags for JSX.
|
|
8
|
+
declare module 'solid-js' {
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
10
|
+
namespace JSX {
|
|
11
|
+
interface IntrinsicElements {
|
|
12
|
+
'kc-artifact': JSX.HTMLAttributes<HTMLElement> & {
|
|
13
|
+
src?: string;
|
|
14
|
+
tab?: string;
|
|
15
|
+
'active-file'?: string;
|
|
16
|
+
sandbox?: string;
|
|
17
|
+
'iframe-title'?: string;
|
|
18
|
+
ref?: (el: HTMLElement) => void;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Storybook serves examples/artifact-fixtures at /artifact-fixtures (see
|
|
25
|
+
// .storybook/main.ts staticDirs). These are real, cross-linked pages so
|
|
26
|
+
// relative-link nav, back/forward and multi-format all work in the iframe.
|
|
27
|
+
const BASE = '/artifact-fixtures';
|
|
28
|
+
|
|
29
|
+
const FILES: ArtifactFile[] = [
|
|
30
|
+
{
|
|
31
|
+
path: 'index.html',
|
|
32
|
+
url: `${BASE}/index.html`,
|
|
33
|
+
type: 'html',
|
|
34
|
+
language: 'html',
|
|
35
|
+
code: `<!DOCTYPE html>
|
|
36
|
+
<html lang="en">
|
|
37
|
+
<head>
|
|
38
|
+
<link rel="stylesheet" href="css/site.css" />
|
|
39
|
+
<title>Starboard — Home</title>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<h1>Starboard</h1>
|
|
43
|
+
<a href="about.html">About</a>
|
|
44
|
+
</body>
|
|
45
|
+
</html>`,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: 'about.html',
|
|
49
|
+
url: `${BASE}/about.html`,
|
|
50
|
+
type: 'html',
|
|
51
|
+
language: 'html',
|
|
52
|
+
code: `<!DOCTYPE html>
|
|
53
|
+
<html lang="en">
|
|
54
|
+
<head>
|
|
55
|
+
<link rel="stylesheet" href="css/site.css" />
|
|
56
|
+
<title>Starboard — About</title>
|
|
57
|
+
</head>
|
|
58
|
+
<body data-page="about">
|
|
59
|
+
<h1>About Starboard</h1>
|
|
60
|
+
<a href="index.html">← Home</a>
|
|
61
|
+
</body>
|
|
62
|
+
</html>`,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
path: 'css/site.css',
|
|
66
|
+
url: `${BASE}/css/site.css`,
|
|
67
|
+
type: 'other',
|
|
68
|
+
language: 'css',
|
|
69
|
+
code: `:root { --accent: #6ea8fe; }
|
|
70
|
+
body { font-family: system-ui, sans-serif; }
|
|
71
|
+
.card { border: 1px solid var(--accent); border-radius: 14px; }`,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
path: 'assets/logo.svg',
|
|
75
|
+
url: `${BASE}/assets/logo.svg`,
|
|
76
|
+
type: 'image',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
path: 'assets/report.pdf',
|
|
80
|
+
url: `${BASE}/assets/report.pdf`,
|
|
81
|
+
type: 'pdf',
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
/** A bordered, sized box the artifact fills. */
|
|
86
|
+
function Frame(props: { children: JSX.Element; height?: string }) {
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
style={{
|
|
90
|
+
height: props.height ?? '520px',
|
|
91
|
+
width: '100%',
|
|
92
|
+
'max-width': '900px',
|
|
93
|
+
border: '1px solid var(--color-border, #e4e4e7)',
|
|
94
|
+
'border-radius': '12px',
|
|
95
|
+
overflow: 'hidden',
|
|
96
|
+
}}
|
|
97
|
+
>
|
|
98
|
+
{props.children}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
104
|
+
<kc-artifact
|
|
105
|
+
src="https://your-backend.example/artifacts/abc/index.html"
|
|
106
|
+
style="display:block;height:520px"
|
|
107
|
+
></kc-artifact>
|
|
108
|
+
|
|
109
|
+
<script type="module">
|
|
110
|
+
import '@kitnai/chat/elements'; // registers the custom elements
|
|
111
|
+
|
|
112
|
+
const el = document.querySelector('kc-artifact');
|
|
113
|
+
// \`files\` is a JS property (array): tree labels (folders from \`/\`), a preview
|
|
114
|
+
// \`url\` per file, and \`code\` for the Code tab.
|
|
115
|
+
el.files = [
|
|
116
|
+
{ path: 'index.html', url: '…/index.html', type: 'html', code: '<!DOCTYPE …' },
|
|
117
|
+
{ path: 'about.html', url: '…/about.html', type: 'html', code: '<!DOCTYPE …' },
|
|
118
|
+
{ path: 'assets/logo.svg', url: '…/logo.svg', type: 'image' },
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
el.addEventListener('navigate', (e) => console.log('navigate', e.detail.url));
|
|
122
|
+
el.addEventListener('tabchange', (e) => console.log('tab', e.detail.tab));
|
|
123
|
+
el.addEventListener('fileselect', (e) => console.log('file', e.detail.path));
|
|
124
|
+
</script>`;
|
|
125
|
+
|
|
126
|
+
const meta = {
|
|
127
|
+
title: 'Web Components/kc-artifact',
|
|
128
|
+
tags: ['autodocs'],
|
|
129
|
+
argTypes: argTypesFor('kc-artifact'),
|
|
130
|
+
parameters: {
|
|
131
|
+
layout: 'padded',
|
|
132
|
+
docs: {
|
|
133
|
+
description: specDescription('kc-artifact', [
|
|
134
|
+
'`<kc-artifact>` is the framework-agnostic **web component** for a framed, switchable **generated-artifact viewer** — the "canvas / artifacts" pattern (Claude Artifacts, ChatGPT Canvas, V0, bolt). It frames a consumer-served URL in a **sandboxed `<iframe>`** with a functional nav toolbar, and a **Preview | Code** toggle whose Code tab shows a file tree (`<kc-file-tree>`) + the active file source (`<kc-code-block>`). Isolated in **Shadow DOM**.',
|
|
135
|
+
"**When to use:** to show an AI-generated artifact (a web page, doc, image, or PDF your backend hosts) beside the conversation — typically inside a `<kc-resizable>` panel. The component **frames** content; your backend serves it, so relative links, back/forward, reload and multi-format work natively.",
|
|
136
|
+
"**How to use:** register once with `import '@kitnai/chat/elements'`, set `src` to the hosted URL, and set the `files` **property** (a JS array of `{ path, url?, code?, language?, type? }`) for the Code tab tree. The iframe `sandbox` defaults to `allow-scripts allow-forms` (**not** `allow-same-origin`); widen it via the `sandbox` attribute only if you trust the artifact. Listen for **`navigate`** (`detail.url`), **`tabchange`** (`detail.tab`), and **`fileselect`** (`detail.path`).",
|
|
137
|
+
'**Placement:** the preview/canvas panel of a compose-your-own-chat shell — e.g. `list | chat | artifact`. It **fills** its container, so give the parent (or the element) a height.',
|
|
138
|
+
'See the **Code** tab for HTML usage.',
|
|
139
|
+
]),
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
args: { tab: 'preview', sandbox: 'allow-scripts allow-forms' },
|
|
143
|
+
} satisfies Meta;
|
|
144
|
+
|
|
145
|
+
export default meta;
|
|
146
|
+
type Story = StoryObj;
|
|
147
|
+
|
|
148
|
+
/** Interactive playground — frames a live, cross-linked fixture. Flip Preview/Code. */
|
|
149
|
+
export const Playground: Story = {
|
|
150
|
+
render: (args: { src?: string; tab?: string; sandbox?: string }) => {
|
|
151
|
+
let el: HTMLElement & { files?: ArtifactFile[] };
|
|
152
|
+
onMount(() => {
|
|
153
|
+
if (el) el.files = FILES;
|
|
154
|
+
});
|
|
155
|
+
return (
|
|
156
|
+
<Frame>
|
|
157
|
+
<kc-artifact
|
|
158
|
+
ref={(e) => (el = e as HTMLElement & { files?: ArtifactFile[] })}
|
|
159
|
+
src={args.src ?? `${BASE}/index.html`}
|
|
160
|
+
tab={args.tab ?? 'preview'}
|
|
161
|
+
sandbox={args.sandbox ?? 'allow-scripts allow-forms'}
|
|
162
|
+
iframe-title="Starboard artifact preview"
|
|
163
|
+
/>
|
|
164
|
+
</Frame>
|
|
165
|
+
);
|
|
166
|
+
},
|
|
167
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/** Opens on the Code tab — file tree + syntax-highlighted source. */
|
|
171
|
+
export const CodeTab: Story = {
|
|
172
|
+
name: 'Code tab',
|
|
173
|
+
render: () => {
|
|
174
|
+
let el: HTMLElement & { files?: ArtifactFile[] };
|
|
175
|
+
onMount(() => {
|
|
176
|
+
if (el) el.files = FILES;
|
|
177
|
+
});
|
|
178
|
+
return (
|
|
179
|
+
<Frame>
|
|
180
|
+
<kc-artifact
|
|
181
|
+
ref={(e) => (el = e as HTMLElement & { files?: ArtifactFile[] })}
|
|
182
|
+
src={`${BASE}/index.html`}
|
|
183
|
+
tab="code"
|
|
184
|
+
active-file="index.html"
|
|
185
|
+
iframe-title="Starboard artifact preview"
|
|
186
|
+
/>
|
|
187
|
+
</Frame>
|
|
188
|
+
);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/** Multi-format: the preview frames an image fixture natively. */
|
|
193
|
+
export const ImagePreview: Story = {
|
|
194
|
+
name: 'Image (multi-format)',
|
|
195
|
+
render: () => {
|
|
196
|
+
let el: HTMLElement & { files?: ArtifactFile[] };
|
|
197
|
+
onMount(() => {
|
|
198
|
+
if (el) el.files = FILES;
|
|
199
|
+
});
|
|
200
|
+
return (
|
|
201
|
+
<Frame>
|
|
202
|
+
<kc-artifact
|
|
203
|
+
ref={(e) => (el = e as HTMLElement & { files?: ArtifactFile[] })}
|
|
204
|
+
src={`${BASE}/assets/logo.svg`}
|
|
205
|
+
iframe-title="Logo image"
|
|
206
|
+
/>
|
|
207
|
+
</Frame>
|
|
208
|
+
);
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/** Multi-format: PDFs render inline via pdf.js (loaded on demand from a CDN). */
|
|
213
|
+
export const PdfPreview: Story = {
|
|
214
|
+
name: 'PDF (multi-format)',
|
|
215
|
+
render: () => {
|
|
216
|
+
let el: HTMLElement & { files?: ArtifactFile[] };
|
|
217
|
+
onMount(() => {
|
|
218
|
+
if (el) el.files = FILES;
|
|
219
|
+
});
|
|
220
|
+
return (
|
|
221
|
+
<Frame>
|
|
222
|
+
<kc-artifact
|
|
223
|
+
ref={(e) => (el = e as HTMLElement & { files?: ArtifactFile[] })}
|
|
224
|
+
src={`${BASE}/assets/report.pdf`}
|
|
225
|
+
iframe-title="Report PDF"
|
|
226
|
+
/>
|
|
227
|
+
</Frame>
|
|
228
|
+
);
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/** When inline rendering can't work (CORS / 404 / blocked CDN) the viewer shows
|
|
233
|
+
* an "Open in new tab / Download" card. Here the src 404s to force that path. */
|
|
234
|
+
export const PdfFallback: Story = {
|
|
235
|
+
name: 'PDF (fallback card)',
|
|
236
|
+
render: () => (
|
|
237
|
+
<Frame>
|
|
238
|
+
<kc-artifact
|
|
239
|
+
src={`${BASE}/assets/does-not-exist.pdf`}
|
|
240
|
+
iframe-title="Missing PDF"
|
|
241
|
+
/>
|
|
242
|
+
</Frame>
|
|
243
|
+
),
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/** Logs the emitted events so you can see the controlled nav model in action. */
|
|
247
|
+
export const WithEvents: Story = {
|
|
248
|
+
name: 'Events',
|
|
249
|
+
render: () => {
|
|
250
|
+
const [log, setLog] = createSignal<string[]>([]);
|
|
251
|
+
let el: HTMLElement & { files?: ArtifactFile[] };
|
|
252
|
+
onMount(() => {
|
|
253
|
+
if (!el) return;
|
|
254
|
+
el.files = FILES;
|
|
255
|
+
el.addEventListener('navigate', (e) =>
|
|
256
|
+
setLog((l) => [`navigate → ${(e as CustomEvent).detail.url}`, ...l].slice(0, 6)),
|
|
257
|
+
);
|
|
258
|
+
el.addEventListener('tabchange', (e) =>
|
|
259
|
+
setLog((l) => [`tabchange → ${(e as CustomEvent).detail.tab}`, ...l].slice(0, 6)),
|
|
260
|
+
);
|
|
261
|
+
el.addEventListener('fileselect', (e) =>
|
|
262
|
+
setLog((l) => [`fileselect → ${(e as CustomEvent).detail.path}`, ...l].slice(0, 6)),
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
return (
|
|
266
|
+
<div style={{ display: 'flex', 'flex-direction': 'column', gap: '8px' }}>
|
|
267
|
+
<Frame height="420px">
|
|
268
|
+
<kc-artifact
|
|
269
|
+
ref={(e) => (el = e as HTMLElement & { files?: ArtifactFile[] })}
|
|
270
|
+
src={`${BASE}/index.html`}
|
|
271
|
+
iframe-title="Starboard artifact preview"
|
|
272
|
+
/>
|
|
273
|
+
</Frame>
|
|
274
|
+
<pre
|
|
275
|
+
style={{
|
|
276
|
+
'font-size': '12px',
|
|
277
|
+
'max-width': '900px',
|
|
278
|
+
padding: '10px 12px',
|
|
279
|
+
'border-radius': '8px',
|
|
280
|
+
background: 'var(--color-muted, #f4f4f5)',
|
|
281
|
+
color: 'var(--color-muted-foreground, #71717a)',
|
|
282
|
+
'min-height': '96px',
|
|
283
|
+
margin: '0',
|
|
284
|
+
}}
|
|
285
|
+
>
|
|
286
|
+
{log().length ? log().join('\n') : '(navigate the preview / switch tabs / pick a file…)'}
|
|
287
|
+
</pre>
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
290
|
+
},
|
|
291
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { defineWebComponent } from './define';
|
|
2
|
+
import { Artifact, type ArtifactFile, type ArtifactTab } from '../components/artifact';
|
|
3
|
+
|
|
4
|
+
interface Props extends Record<string, unknown> {
|
|
5
|
+
/** URL the preview iframe frames. Consumer-controlled. */
|
|
6
|
+
src?: string;
|
|
7
|
+
/** Files for the Code tab tree + each file's preview `url`. Set as a JS property (array). */
|
|
8
|
+
files: ArtifactFile[];
|
|
9
|
+
/** Active tab: `preview` (default) or `code`. */
|
|
10
|
+
tab?: ArtifactTab;
|
|
11
|
+
/** Selected file path — syncs the tree highlight, Code source, and preview. */
|
|
12
|
+
activeFile?: string;
|
|
13
|
+
/** iframe `sandbox` override. Secure default `allow-scripts allow-forms` (NOT `allow-same-origin`). */
|
|
14
|
+
sandbox?: string;
|
|
15
|
+
/** Accessible title for the preview iframe. */
|
|
16
|
+
iframeTitle?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Events extends Record<string, unknown> {
|
|
20
|
+
/** Fired when the preview navigates. `detail.url` = the new location. */
|
|
21
|
+
navigate: { url: string };
|
|
22
|
+
/** Fired when the Preview|Code tab changes. `detail.tab`. */
|
|
23
|
+
tabchange: { tab: ArtifactTab };
|
|
24
|
+
/** Fired when a file is selected. `detail.path`. */
|
|
25
|
+
fileselect: { path: string };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* `<kc-artifact>` — a framed, switchable generated-artifact viewer: a sandboxed
|
|
30
|
+
* preview iframe with a functional nav toolbar (back · forward · reload · home +
|
|
31
|
+
* editable path field) and a Preview|Code toggle; the Code tab shows a file tree
|
|
32
|
+
* (`<kc-file-tree>`) + the active file's source via `<kc-code-block>`. The
|
|
33
|
+
* component self-navigates the iframe and emits `navigate` / `tabchange` /
|
|
34
|
+
* `fileselect`. Designed to FILL its container (e.g. a `<kc-resizable>` panel).
|
|
35
|
+
*/
|
|
36
|
+
defineWebComponent<Props, Events>('kc-artifact', {
|
|
37
|
+
src: undefined,
|
|
38
|
+
files: [],
|
|
39
|
+
tab: 'preview',
|
|
40
|
+
activeFile: undefined,
|
|
41
|
+
sandbox: 'allow-scripts allow-forms',
|
|
42
|
+
iframeTitle: undefined,
|
|
43
|
+
}, (props, { dispatch }) => (
|
|
44
|
+
<>
|
|
45
|
+
{/* The artifact fills its container; the internal column flex (toolbar
|
|
46
|
+
flex-shrink:0, body flex:1/min-height:0) is in the Solid component. Wrap
|
|
47
|
+
in a definite `1fr` grid cell (NOT :host) so the facade's sibling
|
|
48
|
+
portal-mount div can't steal a grid track — see resizable.tsx. */}
|
|
49
|
+
<style>{':host{display:block;height:100%;min-height:0}'}</style>
|
|
50
|
+
<div
|
|
51
|
+
style={{
|
|
52
|
+
display: 'grid',
|
|
53
|
+
'grid-template-rows': 'minmax(0, 1fr)',
|
|
54
|
+
'grid-template-columns': 'minmax(0, 1fr)',
|
|
55
|
+
height: '100%',
|
|
56
|
+
'min-height': '0',
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<Artifact
|
|
60
|
+
src={props.src}
|
|
61
|
+
files={props.files}
|
|
62
|
+
tab={props.tab}
|
|
63
|
+
activeFile={props.activeFile}
|
|
64
|
+
sandbox={props.sandbox}
|
|
65
|
+
iframeTitle={props.iframeTitle}
|
|
66
|
+
onNavigate={(url) => dispatch('navigate', { url })}
|
|
67
|
+
onTabChange={(tab) => dispatch('tabchange', { tab })}
|
|
68
|
+
onFileSelect={(path) => dispatch('fileselect', { path })}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
</>
|
|
72
|
+
));
|
|
@@ -2,13 +2,14 @@ import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
|
2
2
|
import { onMount } from 'solid-js';
|
|
3
3
|
import './register'; // side effect: registers the custom elements
|
|
4
4
|
import type { AttachmentData } from '../components/attachments';
|
|
5
|
+
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
5
6
|
|
|
6
7
|
// The web components are custom DOM elements, so declare the tags for JSX.
|
|
7
8
|
declare module 'solid-js' {
|
|
8
9
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
9
10
|
namespace JSX {
|
|
10
11
|
interface IntrinsicElements {
|
|
11
|
-
'
|
|
12
|
+
'kc-attachments': JSX.HTMLAttributes<HTMLElement> & {
|
|
12
13
|
variant?: string;
|
|
13
14
|
'hover-card'?: boolean | string;
|
|
14
15
|
removable?: boolean | string;
|
|
@@ -58,7 +59,7 @@ const imageItems: AttachmentData[] = [
|
|
|
58
59
|
},
|
|
59
60
|
];
|
|
60
61
|
|
|
61
|
-
/** Render `<
|
|
62
|
+
/** Render `<kc-attachments>` with an `items` property and the given flags. */
|
|
62
63
|
function AttachmentsElement(props: {
|
|
63
64
|
items: AttachmentData[];
|
|
64
65
|
variant?: string;
|
|
@@ -81,7 +82,7 @@ function AttachmentsElement(props: {
|
|
|
81
82
|
}
|
|
82
83
|
});
|
|
83
84
|
return (
|
|
84
|
-
<
|
|
85
|
+
<kc-attachments
|
|
85
86
|
ref={(e) => (el = e as HTMLElement)}
|
|
86
87
|
style={{ display: 'block', padding: '24px', 'max-width': '720px' }}
|
|
87
88
|
/>
|
|
@@ -89,7 +90,7 @@ function AttachmentsElement(props: {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
92
|
-
<
|
|
93
|
+
<kc-attachments id="att" variant="grid" removable></kc-attachments>
|
|
93
94
|
|
|
94
95
|
<script type="module">
|
|
95
96
|
import '@kitnai/chat/elements'; // registers the custom elements
|
|
@@ -108,7 +109,7 @@ const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
|
108
109
|
</script>`;
|
|
109
110
|
|
|
110
111
|
const HOVER_SNIPPET = `<!-- inline/list chips with a hover-card image preview -->
|
|
111
|
-
<
|
|
112
|
+
<kc-attachments id="att" variant="inline" hover-card></kc-attachments>
|
|
112
113
|
|
|
113
114
|
<script type="module">
|
|
114
115
|
import '@kitnai/chat/elements';
|
|
@@ -123,19 +124,18 @@ const HOVER_SNIPPET = `<!-- inline/list chips with a hover-card image preview --
|
|
|
123
124
|
</script>`;
|
|
124
125
|
|
|
125
126
|
const meta = {
|
|
126
|
-
title: 'Web Components/
|
|
127
|
+
title: 'Web Components/kc-attachments',
|
|
127
128
|
tags: ['autodocs'],
|
|
129
|
+
argTypes: argTypesFor('kc-attachments'),
|
|
128
130
|
parameters: {
|
|
129
131
|
layout: 'fullscreen',
|
|
130
132
|
docs: {
|
|
131
|
-
description:
|
|
132
|
-
|
|
133
|
-
'`<kitn-attachments>` is the framework-agnostic **web component** for a set of file/source attachments, and the exemplar for the "collapse a compound primitive to ONE configurable element" pattern: the sub-parts the SolidJS layer composes become attributes here. Isolated in **Shadow DOM**.',
|
|
133
|
+
description: specDescription('kc-attachments', [
|
|
134
|
+
'`<kc-attachments>` is the framework-agnostic **web component** for a set of file/source attachments, and the exemplar for the "collapse a compound primitive to ONE configurable element" pattern: the sub-parts the SolidJS layer composes become attributes here. Isolated in **Shadow DOM**.',
|
|
134
135
|
'**When to use:** rendering attachment chips/tiles in a non-Solid app. In SolidJS, compose the `Attachment*` primitives for fully custom layouts.',
|
|
135
136
|
"**How to use:** register once with `import '@kitnai/chat/elements'`, set the data via the `items` **property**, pick a layout with `variant` (`grid` | `inline` | `list`), add `removable` to get per-item remove buttons (emits a `remove` **CustomEvent** with `{ id }`), and `hover-card` for inline/list previews (image attachments preview their thumbnail).",
|
|
136
137
|
'See the **Code** tab for HTML usage.',
|
|
137
|
-
]
|
|
138
|
-
},
|
|
138
|
+
]),
|
|
139
139
|
},
|
|
140
140
|
},
|
|
141
141
|
} satisfies Meta;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { For, Show } from 'solid-js';
|
|
2
|
-
import {
|
|
2
|
+
import { defineWebComponent } from './define';
|
|
3
3
|
import {
|
|
4
4
|
Attachments,
|
|
5
5
|
Attachment,
|
|
@@ -31,14 +31,14 @@ interface Props extends Record<string, unknown> {
|
|
|
31
31
|
emptyText?: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
/** Events fired by `<
|
|
34
|
+
/** Events fired by `<kc-attachments>`. */
|
|
35
35
|
interface Events {
|
|
36
36
|
/** A remove button was clicked. */
|
|
37
37
|
remove: { id: string };
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* `<
|
|
41
|
+
* `<kc-attachments>` — the exemplar for the "collapse a compound primitive to
|
|
42
42
|
* ONE configurable element" pattern (Route 1). The presentation knobs that the
|
|
43
43
|
* SolidJS layer expresses by composing sub-parts (`<AttachmentPreview>`,
|
|
44
44
|
* `<AttachmentInfo>`, `<AttachmentHoverCard>`, `<AttachmentRemove>`) become
|
|
@@ -52,7 +52,7 @@ interface Events {
|
|
|
52
52
|
* as an event. For fully-custom hover content, the SolidJS primitives remain the
|
|
53
53
|
* escape hatch (a templated slot — "Route 2" — is a deliberate future add).
|
|
54
54
|
*/
|
|
55
|
-
|
|
55
|
+
defineWebComponent<Props, Events>('kc-attachments', {
|
|
56
56
|
items: [],
|
|
57
57
|
variant: 'grid',
|
|
58
58
|
hoverCard: false,
|