@kitn.ai/ui 0.14.1
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/LICENSE +21 -0
- package/README.md +394 -0
- package/dist/bash-InADTalH.js +1 -0
- package/dist/core-AYMC6_lb.js +12 -0
- package/dist/css-M7EaDHN_.js +1 -0
- package/dist/custom-elements.json +4739 -0
- package/dist/engine-javascript-vq0WuIJl.js +141 -0
- package/dist/github-dark-dimmed-DUshB20C.js +1 -0
- package/dist/github-light-JYsPkUQd.js +1 -0
- package/dist/host-embed.d.ts +33 -0
- package/dist/html-CPZ3oZQ7.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/javascript-C25yR2R2.js +1 -0
- package/dist/json-DxJze_jm.js +1 -0
- package/dist/kitn-chat-provider.d.ts +2 -0
- package/dist/kitn-chat-provider.es.js +240 -0
- package/dist/kitn-chat.es.js +77 -0
- package/dist/llms/llms-full.txt +1018 -0
- package/dist/llms/llms.txt +104 -0
- package/dist/origin.d.ts +5 -0
- package/dist/provider-runtime.d.ts +27 -0
- package/dist/schemas/card-envelope.schema.json +14 -0
- package/dist/schemas/card-event.schema.json +12 -0
- package/dist/schemas/choice.schema.json +70 -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/tasks.result.schema.json +16 -0
- package/dist/schemas/tasks.schema.json +78 -0
- package/dist/svelte--5p79yCD.js +1 -0
- package/dist/theme.tokens.css +176 -0
- package/dist/tsx-B8rCNbgL.js +1 -0
- package/dist/typescript-RycA9KXf.js +1 -0
- package/dist/validate.d.ts +4 -0
- package/dist/version.d.ts +4 -0
- package/dist/vue-BmIZj4XD.js +1 -0
- package/dist/wire.d.ts +53 -0
- package/frameworks/react/index.tsx +948 -0
- package/frameworks/react/runtime.tsx +94 -0
- package/llms-full.txt +1018 -0
- package/llms.txt +104 -0
- package/package.json +141 -0
- package/src/components/artifact.stories.tsx +138 -0
- package/src/components/artifact.tsx +743 -0
- package/src/components/attachments.stories.tsx +303 -0
- package/src/components/attachments.test.tsx +64 -0
- package/src/components/attachments.tsx +399 -0
- package/src/components/card-fallback.tsx +28 -0
- package/src/components/card-renderer.tsx +52 -0
- package/src/components/card.tsx +110 -0
- package/src/components/chain-of-thought.stories.tsx +211 -0
- package/src/components/chain-of-thought.tsx +139 -0
- package/src/components/chat-container.stories.tsx +187 -0
- package/src/components/chat-container.tsx +82 -0
- package/src/components/chat-scope-picker.tsx +47 -0
- package/src/components/chat-thread.test.tsx +58 -0
- package/src/components/chat-thread.tsx +249 -0
- package/src/components/checkpoint.stories.tsx +102 -0
- package/src/components/checkpoint.tsx +88 -0
- package/src/components/choice-card.tsx +569 -0
- package/src/components/code-block.stories.tsx +150 -0
- package/src/components/code-block.tsx +105 -0
- package/src/components/component-meta.json +3735 -0
- package/src/components/confirm-card.stories.tsx +74 -0
- package/src/components/confirm-card.tsx +315 -0
- package/src/components/context.stories.tsx +229 -0
- package/src/components/context.test.tsx +133 -0
- package/src/components/context.tsx +363 -0
- package/src/components/conversation-item.stories.tsx +125 -0
- package/src/components/conversation-item.tsx +16 -0
- package/src/components/conversation-list.stories.tsx +133 -0
- package/src/components/conversation-list.tsx +101 -0
- package/src/components/default-input.test.tsx +111 -0
- package/src/components/embed.tsx +196 -0
- package/src/components/empty.stories.tsx +434 -0
- package/src/components/empty.tsx +166 -0
- package/src/components/feedback-bar.stories.tsx +138 -0
- package/src/components/feedback-bar.test.tsx +103 -0
- package/src/components/feedback-bar.tsx +166 -0
- package/src/components/file-tree.stories.tsx +73 -0
- package/src/components/file-tree.tsx +383 -0
- package/src/components/file-upload.stories.tsx +156 -0
- package/src/components/file-upload.tsx +161 -0
- package/src/components/form-widgets.tsx +461 -0
- package/src/components/form.tsx +873 -0
- package/src/components/image.stories.tsx +89 -0
- package/src/components/image.tsx +67 -0
- package/src/components/link-preview.tsx +194 -0
- package/src/components/loader.stories.tsx +181 -0
- package/src/components/loader.tsx +333 -0
- package/src/components/markdown.stories.tsx +180 -0
- package/src/components/markdown.tsx +81 -0
- package/src/components/message-action-bar.test.tsx +128 -0
- package/src/components/message-narrow.stories.tsx +329 -0
- package/src/components/message-skills.stories.tsx +211 -0
- package/src/components/message-skills.tsx +36 -0
- package/src/components/message.stories.tsx +281 -0
- package/src/components/message.tsx +227 -0
- package/src/components/model-switcher.stories.tsx +123 -0
- package/src/components/model-switcher.test.tsx +65 -0
- package/src/components/model-switcher.tsx +87 -0
- package/src/components/prompt-input.stories.tsx +222 -0
- package/src/components/prompt-input.tsx +211 -0
- package/src/components/prompt-suggestion.stories.tsx +142 -0
- package/src/components/prompt-suggestion.tsx +115 -0
- package/src/components/reasoning.stories.tsx +140 -0
- package/src/components/reasoning.tsx +156 -0
- package/src/components/resizable.test.tsx +226 -0
- package/src/components/response-stream.tsx +103 -0
- package/src/components/scroll-button.stories.tsx +100 -0
- package/src/components/scroll-button.tsx +34 -0
- package/src/components/slash-command.stories.tsx +163 -0
- package/src/components/slash-command.tsx +232 -0
- package/src/components/source-list.test.tsx +152 -0
- package/src/components/source.stories.tsx +124 -0
- package/src/components/source.tsx +129 -0
- package/src/components/tasks-card.stories.tsx +78 -0
- package/src/components/tasks-card.tsx +432 -0
- package/src/components/text-shimmer.stories.tsx +87 -0
- package/src/components/text-shimmer.tsx +37 -0
- package/src/components/thinking-bar.stories.tsx +87 -0
- package/src/components/thinking-bar.tsx +50 -0
- package/src/components/tool.stories.tsx +153 -0
- package/src/components/tool.tsx +184 -0
- package/src/components/use-card-resolution.ts +28 -0
- package/src/components/voice-input.stories.tsx +83 -0
- package/src/components/voice-input.tsx +107 -0
- package/src/elements/artifact.stories.tsx +506 -0
- package/src/elements/artifact.tsx +137 -0
- package/src/elements/attachments.stories.tsx +190 -0
- package/src/elements/attachments.tsx +136 -0
- package/src/elements/card.stories.tsx +113 -0
- package/src/elements/card.tsx +40 -0
- package/src/elements/cards.stories.tsx +59 -0
- package/src/elements/cards.tsx +97 -0
- package/src/elements/catalog.stories.tsx +491 -0
- package/src/elements/chain-of-thought.declarative.test.tsx +153 -0
- package/src/elements/chain-of-thought.stories.tsx +113 -0
- package/src/elements/chain-of-thought.tsx +92 -0
- package/src/elements/chat-scope-picker.stories.tsx +73 -0
- package/src/elements/chat-scope-picker.tsx +36 -0
- package/src/elements/chat-types.ts +42 -0
- package/src/elements/chat-workspace.stories.tsx +288 -0
- package/src/elements/chat-workspace.tsx +161 -0
- package/src/elements/chat.stories.tsx +151 -0
- package/src/elements/chat.tsx +80 -0
- package/src/elements/checkpoint.stories.tsx +72 -0
- package/src/elements/checkpoint.tsx +47 -0
- package/src/elements/choice.stories.tsx +240 -0
- package/src/elements/choice.tsx +48 -0
- package/src/elements/code-block.stories.tsx +83 -0
- package/src/elements/code-block.tsx +42 -0
- package/src/elements/compiled.css +2 -0
- package/src/elements/composed-shell.stories.tsx +316 -0
- package/src/elements/confirm-card.stories.tsx +227 -0
- package/src/elements/confirm-card.tsx +51 -0
- package/src/elements/context-meter.stories.tsx +151 -0
- package/src/elements/context-meter.tsx +121 -0
- package/src/elements/conversation-list.declarative.test.tsx +137 -0
- package/src/elements/conversation-list.stories.tsx +243 -0
- package/src/elements/conversation-list.tsx +80 -0
- package/src/elements/css.ts +5 -0
- package/src/elements/default-input.tsx +247 -0
- package/src/elements/define.tsx +171 -0
- package/src/elements/element-meta.json +3314 -0
- package/src/elements/element-types.d.ts +710 -0
- package/src/elements/embed.stories.tsx +213 -0
- package/src/elements/embed.tsx +35 -0
- package/src/elements/empty.stories.tsx +111 -0
- package/src/elements/empty.tsx +29 -0
- package/src/elements/feedback-bar.stories.tsx +114 -0
- package/src/elements/feedback-bar.tsx +61 -0
- package/src/elements/file-tree.stories.tsx +134 -0
- package/src/elements/file-tree.tsx +52 -0
- package/src/elements/file-upload.stories.tsx +82 -0
- package/src/elements/file-upload.tsx +44 -0
- package/src/elements/form.stories.tsx +248 -0
- package/src/elements/form.tsx +43 -0
- package/src/elements/framework-usage.json +563 -0
- package/src/elements/image.stories.tsx +71 -0
- package/src/elements/image.tsx +32 -0
- package/src/elements/link-preview.stories.tsx +209 -0
- package/src/elements/link-preview.tsx +34 -0
- package/src/elements/loader.stories.tsx +88 -0
- package/src/elements/loader.tsx +25 -0
- package/src/elements/markdown.stories.tsx +76 -0
- package/src/elements/markdown.tsx +38 -0
- package/src/elements/message-skills.declarative.test.tsx +104 -0
- package/src/elements/message-skills.stories.tsx +114 -0
- package/src/elements/message-skills.tsx +85 -0
- package/src/elements/message.stories.tsx +218 -0
- package/src/elements/message.tsx +162 -0
- package/src/elements/model-switcher.declarative.test.tsx +130 -0
- package/src/elements/model-switcher.stories.tsx +198 -0
- package/src/elements/model-switcher.tsx +93 -0
- package/src/elements/popover.stories.tsx +80 -0
- package/src/elements/popover.tsx +59 -0
- package/src/elements/prompt-input-slash-command.test.tsx +128 -0
- package/src/elements/prompt-input-stoppable.test.tsx +88 -0
- package/src/elements/prompt-input-toolbar-actions.test.tsx +120 -0
- package/src/elements/prompt-input.stories.tsx +331 -0
- package/src/elements/prompt-input.tsx +185 -0
- package/src/elements/prompt-suggestions.declarative.test.tsx +164 -0
- package/src/elements/prompt-suggestions.stories.tsx +213 -0
- package/src/elements/prompt-suggestions.tsx +94 -0
- package/src/elements/reasoning.stories.tsx +77 -0
- package/src/elements/reasoning.tsx +50 -0
- package/src/elements/register.ts +56 -0
- package/src/elements/remote.stories.tsx +314 -0
- package/src/elements/remote.tsx +221 -0
- package/src/elements/resizable.d.ts +27 -0
- package/src/elements/resizable.stories.tsx +426 -0
- package/src/elements/resizable.tsx +553 -0
- package/src/elements/response-stream.stories.tsx +79 -0
- package/src/elements/response-stream.tsx +40 -0
- package/src/elements/scroll-button.stories.tsx +202 -0
- package/src/elements/scroll-button.test.tsx +148 -0
- package/src/elements/scroll-button.tsx +126 -0
- package/src/elements/source-list.stories.tsx +143 -0
- package/src/elements/source.stories.tsx +88 -0
- package/src/elements/source.tsx +120 -0
- package/src/elements/styles.css +199 -0
- package/src/elements/switch.stories.tsx +56 -0
- package/src/elements/switch.tsx +43 -0
- package/src/elements/tasks.stories.tsx +237 -0
- package/src/elements/tasks.tsx +46 -0
- package/src/elements/text-shimmer.stories.tsx +64 -0
- package/src/elements/text-shimmer.tsx +28 -0
- package/src/elements/thinking-bar.stories.tsx +72 -0
- package/src/elements/thinking-bar.tsx +34 -0
- package/src/elements/tool.stories.tsx +89 -0
- package/src/elements/tool.tsx +23 -0
- package/src/elements/voice-input.stories.tsx +88 -0
- package/src/elements/voice-input.tsx +41 -0
- package/src/index.ts +253 -0
- package/src/primitives/card-contract.ts +71 -0
- package/src/primitives/card-host.tsx +35 -0
- package/src/primitives/card-registry.tsx +67 -0
- package/src/primitives/card-resolution.ts +36 -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/choice.schema.json +70 -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/tasks.result.schema.json +16 -0
- package/src/primitives/card-schemas/tasks.schema.json +78 -0
- package/src/primitives/card-validate.ts +95 -0
- package/src/primitives/chat-config.tsx +76 -0
- package/src/primitives/controllable.test.ts +47 -0
- package/src/primitives/controllable.ts +21 -0
- package/src/primitives/embed-providers.ts +254 -0
- package/src/primitives/highlighter.ts +157 -0
- package/src/primitives/link-preview.ts +87 -0
- package/src/primitives/pdf-preview.ts +121 -0
- package/src/primitives/use-auto-resize.ts +31 -0
- package/src/primitives/use-resize-observer.ts +12 -0
- package/src/primitives/use-stick-to-bottom.ts +43 -0
- package/src/primitives/use-text-stream.ts +112 -0
- package/src/primitives/use-voice-recorder.ts +50 -0
- package/src/remote/host-embed.ts +345 -0
- package/src/remote/index.ts +2 -0
- package/src/remote/origin.ts +30 -0
- package/src/remote/provider-runtime.ts +259 -0
- package/src/remote/provider.ts +2 -0
- package/src/remote/validate.ts +22 -0
- package/src/remote/version.ts +12 -0
- package/src/remote/wire.ts +48 -0
- package/src/stories/chat-panel-layout.stories.tsx +145 -0
- package/src/stories/chat-scene.tsx +571 -0
- package/src/stories/checkpoint-restore.stories.tsx +256 -0
- package/src/stories/context-usage.stories.tsx +212 -0
- package/src/stories/conversation-with-reasoning.stories.tsx +182 -0
- package/src/stories/conversation-with-sources.stories.tsx +471 -0
- package/src/stories/docs/Accessibility.mdx +119 -0
- package/src/stories/docs/ChildElements.mdx +269 -0
- package/src/stories/docs/ForAIAgents.mdx +162 -0
- package/src/stories/docs/GettingStarted.mdx +65 -0
- package/src/stories/docs/Installation.mdx +83 -0
- package/src/stories/docs/Introduction.mdx +50 -0
- package/src/stories/docs/SolidJsAdvanced.mdx +17 -0
- package/src/stories/docs/Theming.mdx +85 -0
- package/src/stories/docs/element-controls.ts +121 -0
- package/src/stories/docs/frameworks/Angular.mdx +245 -0
- package/src/stories/docs/frameworks/Html.mdx +227 -0
- package/src/stories/docs/frameworks/Overview.mdx +47 -0
- package/src/stories/docs/frameworks/React.mdx +227 -0
- package/src/stories/docs/frameworks/Solid.mdx +173 -0
- package/src/stories/docs/frameworks/Svelte.mdx +194 -0
- package/src/stories/docs/frameworks/Vue.mdx +238 -0
- package/src/stories/docs/generative-ui-overview.mdx +186 -0
- package/src/stories/docs/recipes/SpeechToText.mdx +7 -0
- package/src/stories/docs/recipes/Streaming.mdx +68 -0
- package/src/stories/docs/recipes/TextToSpeech.mdx +35 -0
- package/src/stories/docs/theme-editor/canvas.tsx +32 -0
- package/src/stories/docs/theme-editor/inspector.tsx +66 -0
- package/src/stories/docs/theme-editor/presets.test.ts +32 -0
- package/src/stories/docs/theme-editor/presets.ts +64 -0
- package/src/stories/docs/theme-editor/theme-css.test.ts +19 -0
- package/src/stories/docs/theme-editor/theme-css.ts +15 -0
- package/src/stories/docs/theme-editor/theme-editor.tsx +146 -0
- package/src/stories/docs/theme-tokens.tsx +174 -0
- package/src/stories/examples/ChoosingComponents.mdx +106 -0
- package/src/stories/examples/sample-data.ts +79 -0
- package/src/stories/examples/usage/checkpoint-restore.ts +156 -0
- package/src/stories/examples/usage/context-usage.ts +591 -0
- package/src/stories/examples/usage/conversation-with-reasoning.ts +224 -0
- package/src/stories/examples/usage/conversation-with-sources.ts +589 -0
- package/src/stories/examples/usage/empty-state.ts +144 -0
- package/src/stories/examples/usage/full-chat-app.ts +277 -0
- package/src/stories/examples/usage/index.ts +63 -0
- package/src/stories/examples/usage/message-actions.ts +704 -0
- package/src/stories/examples/usage/pattern-centered-conversation.ts +446 -0
- package/src/stories/examples/usage/pattern-chat-panel-layout.ts +442 -0
- package/src/stories/examples/usage/pattern-docked-widget.ts +479 -0
- package/src/stories/examples/usage/prompt-input-variants.ts +1356 -0
- package/src/stories/examples/usage/streaming-response.ts +687 -0
- package/src/stories/examples/usage/types.ts +33 -0
- package/src/stories/full-chat.stories.tsx +18 -0
- package/src/stories/message-actions.stories.tsx +230 -0
- package/src/stories/pattern-centered-conversation.stories.tsx +93 -0
- package/src/stories/pattern-docked-widget.stories.tsx +93 -0
- package/src/stories/pattern-empty-state.stories.tsx +76 -0
- package/src/stories/prompt-input-variants.stories.tsx +456 -0
- package/src/stories/streaming-response.stories.tsx +438 -0
- package/src/stories/theme-editor.stories.tsx +16 -0
- package/src/stories/token-reference.stories.tsx +18 -0
- package/src/stories/typography.stories.tsx +78 -0
- package/src/types.ts +47 -0
- package/src/ui/action-icons.ts +52 -0
- package/src/ui/avatar.stories.tsx +103 -0
- package/src/ui/avatar.tsx +23 -0
- package/src/ui/badge.stories.tsx +86 -0
- package/src/ui/badge.tsx +21 -0
- package/src/ui/button.stories.tsx +145 -0
- package/src/ui/button.tsx +38 -0
- package/src/ui/collapsible.stories.tsx +69 -0
- package/src/ui/collapsible.tsx +125 -0
- package/src/ui/dropdown.stories.tsx +59 -0
- package/src/ui/dropdown.tsx +196 -0
- package/src/ui/hover-card.stories.tsx +77 -0
- package/src/ui/hover-card.test.tsx +30 -0
- package/src/ui/hover-card.tsx +172 -0
- package/src/ui/overlay.stories.tsx +115 -0
- package/src/ui/overlay.tsx +158 -0
- package/src/ui/popover.stories.tsx +81 -0
- package/src/ui/popover.test.tsx +99 -0
- package/src/ui/popover.tsx +92 -0
- package/src/ui/resizable.stories.tsx +236 -0
- package/src/ui/resizable.tsx +576 -0
- package/src/ui/scroll-area.stories.tsx +50 -0
- package/src/ui/scroll-area.tsx +15 -0
- package/src/ui/separator.stories.tsx +81 -0
- package/src/ui/separator.tsx +10 -0
- package/src/ui/skeleton.stories.tsx +337 -0
- package/src/ui/skeleton.tsx +16 -0
- package/src/ui/switch.stories.tsx +51 -0
- package/src/ui/switch.test.tsx +59 -0
- package/src/ui/switch.tsx +62 -0
- package/src/ui/textarea.stories.tsx +76 -0
- package/src/ui/textarea.tsx +21 -0
- package/src/ui/tooltip.stories.tsx +74 -0
- package/src/ui/tooltip.tsx +68 -0
- package/src/utils/cn.ts +24 -0
- package/theme.css +187 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { createSignal, onMount, onCleanup } from 'solid-js';
|
|
2
|
+
import { defineWebComponent } from './define';
|
|
3
|
+
import { Artifact, type ArtifactFile, type ArtifactTab } from '../components/artifact';
|
|
4
|
+
|
|
5
|
+
interface Props extends Record<string, unknown> {
|
|
6
|
+
/** URL the preview iframe frames. Consumer-controlled. */
|
|
7
|
+
src?: string;
|
|
8
|
+
/** Files for the Code tab tree + each file's preview `url`. Set as a JS property (array). */
|
|
9
|
+
files: ArtifactFile[];
|
|
10
|
+
/** Active tab: `preview` (default) or `code`. */
|
|
11
|
+
tab?: ArtifactTab;
|
|
12
|
+
/** Selected file path — syncs the tree highlight, Code source, and preview. */
|
|
13
|
+
activeFile?: string;
|
|
14
|
+
/** iframe `sandbox` override. Secure default `allow-scripts allow-forms` (NOT `allow-same-origin`). */
|
|
15
|
+
sandbox?: string;
|
|
16
|
+
/** Accessible title for the preview iframe. */
|
|
17
|
+
iframeTitle?: string;
|
|
18
|
+
/** Reflects the artifact's own maximized view-state (usually driven by the protocol). */
|
|
19
|
+
maximized?: boolean;
|
|
20
|
+
/** Show the expand-to-fill button (OPT-IN). */
|
|
21
|
+
expandable?: boolean;
|
|
22
|
+
/** Show the open-in-new-tab button (OPT-IN). */
|
|
23
|
+
openInTab?: boolean;
|
|
24
|
+
/** Hide back/forward. */
|
|
25
|
+
noNav?: boolean;
|
|
26
|
+
/** Hide reload. */
|
|
27
|
+
noReload?: boolean;
|
|
28
|
+
/** Hide home. */
|
|
29
|
+
noHome?: boolean;
|
|
30
|
+
/** Hide the address field. */
|
|
31
|
+
noPathField?: boolean;
|
|
32
|
+
/** Hide the Preview|Code toggle. */
|
|
33
|
+
noTabs?: boolean;
|
|
34
|
+
/** Standalone chrome: rounded corners + border (else square, borderless in-panel). */
|
|
35
|
+
standalone?: boolean;
|
|
36
|
+
/** Show the address but make it read-only (visible, nav-tracking, non-editable). */
|
|
37
|
+
readonlyPath?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface Events extends Record<string, unknown> {
|
|
41
|
+
/** Fired when the preview navigates. `detail.url` = the new location. */
|
|
42
|
+
'kai-navigate': { url: string };
|
|
43
|
+
/** Fired when the Preview|Code tab changes. `detail.tab`. */
|
|
44
|
+
'kai-tab-change': { tab: ArtifactTab };
|
|
45
|
+
/** Fired when a file is selected. `detail.path`. */
|
|
46
|
+
'kai-file-select': { path: string };
|
|
47
|
+
/** Artifact's own maximize button toggled (consumer-observable; non-bubbling). */
|
|
48
|
+
'kai-maximize-change': { maximized: boolean };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `<kai-artifact>` — a framed, switchable generated-artifact viewer: a sandboxed
|
|
53
|
+
* preview iframe with a functional nav toolbar (back · forward · reload · home +
|
|
54
|
+
* editable path field) and a Preview|Code toggle; the Code tab shows a file tree
|
|
55
|
+
* (`<kai-file-tree>`) + the active file's source via `<kai-code-block>`. The
|
|
56
|
+
* component self-navigates the iframe and emits `kai-navigate` / `kai-tab-change` /
|
|
57
|
+
* `kai-file-select`. Designed to FILL its container (e.g. a `<kai-resizable>` panel).
|
|
58
|
+
*/
|
|
59
|
+
defineWebComponent<Props, Events>('kai-artifact', {
|
|
60
|
+
src: undefined,
|
|
61
|
+
files: [],
|
|
62
|
+
tab: 'preview',
|
|
63
|
+
activeFile: undefined,
|
|
64
|
+
sandbox: 'allow-scripts allow-forms',
|
|
65
|
+
iframeTitle: undefined,
|
|
66
|
+
maximized: false,
|
|
67
|
+
expandable: false,
|
|
68
|
+
openInTab: false,
|
|
69
|
+
noNav: false,
|
|
70
|
+
noReload: false,
|
|
71
|
+
noHome: false,
|
|
72
|
+
noPathField: false,
|
|
73
|
+
noTabs: false,
|
|
74
|
+
standalone: false,
|
|
75
|
+
readonlyPath: false,
|
|
76
|
+
}, (props, { element, dispatch, flag }) => {
|
|
77
|
+
const [maximized, setMaximized] = createSignal(flag('maximized'));
|
|
78
|
+
|
|
79
|
+
const onMaximizeChange = (next: boolean) => {
|
|
80
|
+
setMaximized(next);
|
|
81
|
+
// 1) The PROTOCOL intent — raw, bubbling + composed (NOT via dispatch()).
|
|
82
|
+
element.dispatchEvent(
|
|
83
|
+
new CustomEvent('kai-maximize-intent', { detail: { requested: next }, bubbles: true, composed: true }),
|
|
84
|
+
);
|
|
85
|
+
// 2) The PUBLIC observable event (non-bubbling, on the host).
|
|
86
|
+
dispatch('kai-maximize-change', { maximized: next });
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Authoritative reconcile: the resizable tells us the effective state.
|
|
90
|
+
onMount(() => {
|
|
91
|
+
const onState = (e: Event) => setMaximized((e as CustomEvent<{ maximized: boolean }>).detail.maximized);
|
|
92
|
+
element.addEventListener('kai-maximize-state', onState);
|
|
93
|
+
onCleanup(() => element.removeEventListener('kai-maximize-state', onState));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<>
|
|
98
|
+
{/* The artifact fills its container; the internal column flex (toolbar
|
|
99
|
+
flex-shrink:0, body flex:1/min-height:0) is in the Solid component. Wrap
|
|
100
|
+
in a definite `1fr` grid cell (NOT :host) so the facade's sibling
|
|
101
|
+
portal-mount div can't steal a grid track — see resizable.tsx. */}
|
|
102
|
+
<style>{':host{display:block;height:100%;min-height:0}'}</style>
|
|
103
|
+
<div
|
|
104
|
+
style={{
|
|
105
|
+
display: 'grid',
|
|
106
|
+
'grid-template-rows': 'minmax(0, 1fr)',
|
|
107
|
+
'grid-template-columns': 'minmax(0, 1fr)',
|
|
108
|
+
height: '100%',
|
|
109
|
+
'min-height': '0',
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
<Artifact
|
|
113
|
+
src={props.src}
|
|
114
|
+
files={props.files}
|
|
115
|
+
tab={props.tab}
|
|
116
|
+
activeFile={props.activeFile}
|
|
117
|
+
sandbox={props.sandbox}
|
|
118
|
+
iframeTitle={props.iframeTitle}
|
|
119
|
+
maximized={maximized()}
|
|
120
|
+
expandable={flag('expandable')}
|
|
121
|
+
openInTab={flag('openInTab')}
|
|
122
|
+
showNav={!flag('noNav')}
|
|
123
|
+
showReload={!flag('noReload')}
|
|
124
|
+
showHome={!flag('noHome')}
|
|
125
|
+
showPathField={!flag('noPathField')}
|
|
126
|
+
showTabs={!flag('noTabs')}
|
|
127
|
+
standalone={flag('standalone')}
|
|
128
|
+
readonlyPath={flag('readonlyPath')}
|
|
129
|
+
onMaximizeChange={onMaximizeChange}
|
|
130
|
+
onNavigate={(url) => dispatch('kai-navigate', { url })}
|
|
131
|
+
onTabChange={(tab) => dispatch('kai-tab-change', { tab })}
|
|
132
|
+
onFileSelect={(path) => dispatch('kai-file-select', { path })}
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
</>
|
|
136
|
+
);
|
|
137
|
+
});
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import { onMount } from 'solid-js';
|
|
3
|
+
import './register'; // side effect: registers the custom elements
|
|
4
|
+
import type { AttachmentData } from '../components/attachments';
|
|
5
|
+
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
6
|
+
|
|
7
|
+
// The web components are custom DOM elements, so 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
|
+
'kai-attachments': JSX.HTMLAttributes<HTMLElement> & {
|
|
13
|
+
variant?: string;
|
|
14
|
+
'hover-card'?: boolean | string;
|
|
15
|
+
removable?: boolean | string;
|
|
16
|
+
'show-media-type'?: boolean | string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Mixed file/source items (icons + labels) — mirrors Components/Attachments.
|
|
23
|
+
const sampleItems: AttachmentData[] = [
|
|
24
|
+
{
|
|
25
|
+
id: '1',
|
|
26
|
+
type: 'file',
|
|
27
|
+
filename: 'mountain-landscape.jpg',
|
|
28
|
+
mediaType: 'image/jpeg',
|
|
29
|
+
url: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=400&fit=crop',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: '2',
|
|
33
|
+
type: 'file',
|
|
34
|
+
filename: 'sunset-beach.png',
|
|
35
|
+
mediaType: 'image/png',
|
|
36
|
+
url: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=400&h=400&fit=crop',
|
|
37
|
+
},
|
|
38
|
+
{ id: '3', type: 'file', filename: 'architecture-report.pdf', mediaType: 'application/pdf' },
|
|
39
|
+
{ id: '4', type: 'file', filename: 'demo-recording.mp4', mediaType: 'video/mp4' },
|
|
40
|
+
{ id: '5', type: 'file', filename: 'podcast-episode.mp3', mediaType: 'audio/mpeg' },
|
|
41
|
+
{ id: '6', type: 'source-document', filename: 'SolidJS Documentation', title: 'SolidJS Reactivity Guide', url: 'https://solidjs.com/docs' },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Image-only items — used for the grid thumbnails + hover-card preview stories.
|
|
45
|
+
const imageItems: AttachmentData[] = [
|
|
46
|
+
{
|
|
47
|
+
id: 'img-1',
|
|
48
|
+
type: 'file',
|
|
49
|
+
filename: 'mountain-landscape.jpg',
|
|
50
|
+
mediaType: 'image/jpeg',
|
|
51
|
+
url: 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=400&fit=crop',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'img-2',
|
|
55
|
+
type: 'file',
|
|
56
|
+
filename: 'sunset-beach.png',
|
|
57
|
+
mediaType: 'image/png',
|
|
58
|
+
url: 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=400&h=400&fit=crop',
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/** Render `<kai-attachments>` with an `items` property and the given flags. */
|
|
63
|
+
function AttachmentsElement(props: {
|
|
64
|
+
items: AttachmentData[];
|
|
65
|
+
variant?: string;
|
|
66
|
+
removable?: boolean;
|
|
67
|
+
hoverCard?: boolean;
|
|
68
|
+
showMediaType?: boolean;
|
|
69
|
+
}) {
|
|
70
|
+
let el: (HTMLElement & { items?: AttachmentData[] }) | undefined;
|
|
71
|
+
onMount(() => {
|
|
72
|
+
if (!el) return;
|
|
73
|
+
el.items = props.items;
|
|
74
|
+
if (props.variant) el.setAttribute('variant', props.variant);
|
|
75
|
+
if (props.hoverCard) el.setAttribute('hover-card', '');
|
|
76
|
+
if (props.showMediaType) el.setAttribute('show-media-type', '');
|
|
77
|
+
if (props.removable) {
|
|
78
|
+
el.setAttribute('removable', '');
|
|
79
|
+
el.addEventListener('kai-remove', ((e: CustomEvent<{ id: string }>) => {
|
|
80
|
+
el!.items = (el!.items ?? []).filter((x) => x.id !== e.detail.id);
|
|
81
|
+
}) as EventListener);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
return (
|
|
85
|
+
<kai-attachments
|
|
86
|
+
ref={(e) => (el = e as HTMLElement)}
|
|
87
|
+
style={{ display: 'block', padding: '24px', 'max-width': '720px' }}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const HTML_SNIPPET = `<!-- Works in any framework or plain HTML -->
|
|
93
|
+
<kai-attachments id="att" variant="grid" removable></kai-attachments>
|
|
94
|
+
|
|
95
|
+
<script type="module">
|
|
96
|
+
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
97
|
+
|
|
98
|
+
const att = document.getElementById('att');
|
|
99
|
+
att.items = [
|
|
100
|
+
{ id: '1', type: 'file', filename: 'mountain-landscape.jpg',
|
|
101
|
+
mediaType: 'image/jpeg', url: 'https://.../mountain.jpg' },
|
|
102
|
+
{ id: '2', type: 'file', filename: 'spec.pdf', mediaType: 'application/pdf' },
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
// events are CustomEvents on the element (they do not bubble)
|
|
106
|
+
att.addEventListener('kai-remove', (e) => {
|
|
107
|
+
att.items = att.items.filter((x) => x.id !== e.detail.id);
|
|
108
|
+
});
|
|
109
|
+
</script>`;
|
|
110
|
+
|
|
111
|
+
const HOVER_SNIPPET = `<!-- inline/list chips with a hover-card image preview -->
|
|
112
|
+
<kai-attachments id="att" variant="inline" hover-card></kai-attachments>
|
|
113
|
+
|
|
114
|
+
<script type="module">
|
|
115
|
+
import '@kitn.ai/ui/elements';
|
|
116
|
+
|
|
117
|
+
const att = document.getElementById('att');
|
|
118
|
+
// image attachments show their thumbnail in the hover card on hover;
|
|
119
|
+
// non-image attachments show their label + media type instead.
|
|
120
|
+
att.items = [
|
|
121
|
+
{ id: '1', type: 'file', filename: 'mountain-landscape.jpg',
|
|
122
|
+
mediaType: 'image/jpeg', url: 'https://.../mountain.jpg' },
|
|
123
|
+
];
|
|
124
|
+
</script>`;
|
|
125
|
+
|
|
126
|
+
const meta = {
|
|
127
|
+
title: 'Components/Attachments',
|
|
128
|
+
tags: ['autodocs'],
|
|
129
|
+
argTypes: argTypesFor('kai-attachments'),
|
|
130
|
+
parameters: {
|
|
131
|
+
layout: 'fullscreen',
|
|
132
|
+
docs: {
|
|
133
|
+
description: specDescription('kai-attachments', [
|
|
134
|
+
'`<kai-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**.',
|
|
135
|
+
'**When to use:** rendering attachment chips/tiles in a non-Solid app. In SolidJS, compose the `Attachment*` primitives for fully custom layouts.',
|
|
136
|
+
'**Placement:** beneath message content (for received/sent files) or above the prompt input (for staged uploads); fills its container width.',
|
|
137
|
+
"**How to use:** register once with `import '@kitn.ai/ui/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 `kai-remove` **CustomEvent** with `{ id }`), and `hover-card` for inline/list previews (image attachments preview their thumbnail).",
|
|
138
|
+
'See the **Code** tab for HTML usage.',
|
|
139
|
+
]),
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
} satisfies Meta;
|
|
143
|
+
|
|
144
|
+
export default meta;
|
|
145
|
+
type Story = StoryObj;
|
|
146
|
+
|
|
147
|
+
/** Visual tiles (the default `grid` variant) — image attachments render as real
|
|
148
|
+
* `<img>` thumbnails; non-image files fall back to a media-type icon. */
|
|
149
|
+
export const Grid: Story = {
|
|
150
|
+
render: () => <AttachmentsElement items={sampleItems} variant="grid" />,
|
|
151
|
+
parameters: { docs: { source: { code: HTML_SNIPPET, language: 'html' } } },
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** Icon + label chips (`inline`) — files shown as a small preview + filename. */
|
|
155
|
+
export const Inline: Story = {
|
|
156
|
+
render: () => <AttachmentsElement items={sampleItems} variant="inline" />,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/** Detailed rows (`list`) with the media type beneath each filename. */
|
|
160
|
+
export const List: Story = {
|
|
161
|
+
render: () => <AttachmentsElement items={sampleItems} variant="list" showMediaType />,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** Removable rows — clicking the remove button fires a `remove` event. */
|
|
165
|
+
export const RemovableList: Story = {
|
|
166
|
+
name: 'Removable List',
|
|
167
|
+
render: () => <AttachmentsElement items={sampleItems} variant="list" removable />,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/** Image thumbnails in a grid (real `<img>` previews). */
|
|
171
|
+
export const ImageGrid: Story = {
|
|
172
|
+
name: 'Image Grid',
|
|
173
|
+
render: () => <AttachmentsElement items={imageItems} variant="grid" />,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/** Inline chips with a hover-card preview — hover an image chip to see its
|
|
177
|
+
* thumbnail enlarge in the hover card. */
|
|
178
|
+
export const WithHoverCard: Story = {
|
|
179
|
+
name: 'With Hover Card',
|
|
180
|
+
render: () => <AttachmentsElement items={imageItems} variant="inline" hoverCard />,
|
|
181
|
+
parameters: { docs: { source: { code: HOVER_SNIPPET, language: 'html' } } },
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** Grid tiles with a hover-card preview — the tile shows only the image; the
|
|
185
|
+
* filename + media type surface in the hover card. (Grid previously skipped
|
|
186
|
+
* the hover card entirely.) */
|
|
187
|
+
export const GridWithHoverCard: Story = {
|
|
188
|
+
name: 'Grid With Hover Card',
|
|
189
|
+
render: () => <AttachmentsElement items={imageItems} variant="grid" hoverCard />,
|
|
190
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { For, Show } from 'solid-js';
|
|
2
|
+
import { defineWebComponent } from './define';
|
|
3
|
+
import {
|
|
4
|
+
Attachments,
|
|
5
|
+
Attachment,
|
|
6
|
+
AttachmentPreview,
|
|
7
|
+
AttachmentInfo,
|
|
8
|
+
AttachmentRemove,
|
|
9
|
+
AttachmentHoverCard,
|
|
10
|
+
AttachmentHoverCardTrigger,
|
|
11
|
+
AttachmentHoverCardContent,
|
|
12
|
+
AttachmentEmpty,
|
|
13
|
+
getAttachmentLabel,
|
|
14
|
+
getMediaCategory,
|
|
15
|
+
type AttachmentData,
|
|
16
|
+
type AttachmentVariant,
|
|
17
|
+
} from '../components/attachments';
|
|
18
|
+
|
|
19
|
+
interface Props extends Record<string, unknown> {
|
|
20
|
+
/** The attachments to render. Set as a JS property (array). */
|
|
21
|
+
items: AttachmentData[];
|
|
22
|
+
/** Layout: `grid` = visual tiles, `inline` = icon + label chips, `list` = rows. */
|
|
23
|
+
variant?: AttachmentVariant;
|
|
24
|
+
/** Wrap each item in a hover card that previews its details. */
|
|
25
|
+
hoverCard?: boolean;
|
|
26
|
+
/** Show a remove button per item; clicking it fires a `kai-remove` event. */
|
|
27
|
+
removable?: boolean;
|
|
28
|
+
/** Also show the media type beneath the filename (non-grid variants). */
|
|
29
|
+
showMediaType?: boolean;
|
|
30
|
+
/** Text shown when `items` is empty. */
|
|
31
|
+
emptyText?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Events fired by `<kai-attachments>`. */
|
|
35
|
+
interface Events {
|
|
36
|
+
/** A remove button was clicked. */
|
|
37
|
+
'kai-remove': { id: string };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `<kai-attachments>` — the exemplar for the "collapse a compound primitive to
|
|
42
|
+
* ONE configurable element" pattern (Route 1). The presentation knobs that the
|
|
43
|
+
* SolidJS layer expresses by composing sub-parts (`<AttachmentPreview>`,
|
|
44
|
+
* `<AttachmentInfo>`, `<AttachmentHoverCard>`, `<AttachmentRemove>`) become
|
|
45
|
+
* attributes/flags here:
|
|
46
|
+
*
|
|
47
|
+
* - icon + label .......... `variant="inline"`
|
|
48
|
+
* - visual + hover card .... `variant="grid" hover-card`
|
|
49
|
+
* - removable chips ........ add `removable` (emits `kai-remove` → { id })
|
|
50
|
+
*
|
|
51
|
+
* Data in via the `items` property; the only interaction (`remove`) comes back
|
|
52
|
+
* as an event. For fully-custom hover content, the SolidJS primitives remain the
|
|
53
|
+
* escape hatch (a templated slot — "Route 2" — is a deliberate future add).
|
|
54
|
+
*/
|
|
55
|
+
defineWebComponent<Props, Events>('kai-attachments', {
|
|
56
|
+
items: [],
|
|
57
|
+
variant: 'grid',
|
|
58
|
+
hoverCard: false,
|
|
59
|
+
removable: false,
|
|
60
|
+
showMediaType: false,
|
|
61
|
+
emptyText: undefined,
|
|
62
|
+
}, (props, { dispatch, flag }) => {
|
|
63
|
+
const variant = () => props.variant ?? 'grid';
|
|
64
|
+
const hoverCard = () => flag('hoverCard');
|
|
65
|
+
const removable = () => flag('removable');
|
|
66
|
+
const showMediaType = () => flag('showMediaType');
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Show
|
|
70
|
+
when={props.items.length}
|
|
71
|
+
fallback={<Show when={props.emptyText}><AttachmentEmpty>{props.emptyText}</AttachmentEmpty></Show>}
|
|
72
|
+
>
|
|
73
|
+
<Attachments variant={variant()}>
|
|
74
|
+
<For each={props.items}>
|
|
75
|
+
{(item) => (
|
|
76
|
+
<Attachment
|
|
77
|
+
data={item}
|
|
78
|
+
onRemove={removable() ? () => dispatch('kai-remove', { id: item.id }) : undefined}
|
|
79
|
+
>
|
|
80
|
+
<Show
|
|
81
|
+
when={hoverCard()}
|
|
82
|
+
fallback={
|
|
83
|
+
<>
|
|
84
|
+
<AttachmentPreview />
|
|
85
|
+
{/* Info only for non-grid; grid is a self-contained visual tile. */}
|
|
86
|
+
<Show when={variant() !== 'grid'}>
|
|
87
|
+
<AttachmentInfo showMediaType={showMediaType()} />
|
|
88
|
+
</Show>
|
|
89
|
+
</>
|
|
90
|
+
}
|
|
91
|
+
>
|
|
92
|
+
{/* The trigger carries the layout itself — a bare inline <span>
|
|
93
|
+
collapses inline/list rows. Grid wraps just the tile (the
|
|
94
|
+
label/details surface in the hover card instead). */}
|
|
95
|
+
<AttachmentHoverCard>
|
|
96
|
+
<AttachmentHoverCardTrigger
|
|
97
|
+
class={variant() === 'grid'
|
|
98
|
+
? 'block size-full'
|
|
99
|
+
: `flex items-center gap-1.5${variant() === 'list' ? ' w-full' : ''}`}
|
|
100
|
+
>
|
|
101
|
+
<AttachmentPreview />
|
|
102
|
+
<Show when={variant() !== 'grid'}>
|
|
103
|
+
<AttachmentInfo showMediaType={showMediaType()} />
|
|
104
|
+
</Show>
|
|
105
|
+
</AttachmentHoverCardTrigger>
|
|
106
|
+
<AttachmentHoverCardContent>
|
|
107
|
+
{/* For image attachments, preview the actual thumbnail;
|
|
108
|
+
otherwise fall back to the label + media-type details. */}
|
|
109
|
+
<Show
|
|
110
|
+
when={getMediaCategory(item) === 'image' && item.type === 'file' && item.url}
|
|
111
|
+
fallback={
|
|
112
|
+
<>
|
|
113
|
+
<div class="text-body font-medium">{getAttachmentLabel(item)}</div>
|
|
114
|
+
<Show when={item.mediaType}>
|
|
115
|
+
<div class="text-muted-foreground text-caption">{item.mediaType}</div>
|
|
116
|
+
</Show>
|
|
117
|
+
</>
|
|
118
|
+
}
|
|
119
|
+
>
|
|
120
|
+
<img
|
|
121
|
+
src={item.url}
|
|
122
|
+
alt={getAttachmentLabel(item)}
|
|
123
|
+
class="block max-h-64 max-w-xs rounded object-contain"
|
|
124
|
+
/>
|
|
125
|
+
</Show>
|
|
126
|
+
</AttachmentHoverCardContent>
|
|
127
|
+
</AttachmentHoverCard>
|
|
128
|
+
</Show>
|
|
129
|
+
<AttachmentRemove />
|
|
130
|
+
</Attachment>
|
|
131
|
+
)}
|
|
132
|
+
</For>
|
|
133
|
+
</Attachments>
|
|
134
|
+
</Show>
|
|
135
|
+
);
|
|
136
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
2
|
+
import type { JSX } from 'solid-js';
|
|
3
|
+
import './card';
|
|
4
|
+
import { argTypesFor, specDescription } from '../stories/docs/element-controls';
|
|
5
|
+
|
|
6
|
+
declare module 'solid-js' {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
8
|
+
namespace JSX {
|
|
9
|
+
interface IntrinsicElements {
|
|
10
|
+
'kai-card': JSX.HTMLAttributes<HTMLElement> & {
|
|
11
|
+
heading?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
'error-message'?: string;
|
|
14
|
+
dense?: boolean | string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** A bordered box the card sits inside (gives it a sensible width). */
|
|
21
|
+
function Frame(props: { children: JSX.Element }) {
|
|
22
|
+
return <div style={{ 'max-width': '420px' }}>{props.children}</div>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const CONTENT_SNIPPET = `<!-- kai-card is the chrome/container. Slot in plain content — not interactive controls.
|
|
26
|
+
For data-driven interactive cards use the typed cards instead (see below). -->
|
|
27
|
+
<kai-card heading="Workspace summary" description="Generated just now.">
|
|
28
|
+
<p>Your repository has 3 open pull requests and 12 passing checks.</p>
|
|
29
|
+
</kai-card>
|
|
30
|
+
|
|
31
|
+
<script type="module">
|
|
32
|
+
import '@kitn.ai/ui/elements'; // registers the custom elements
|
|
33
|
+
</script>`;
|
|
34
|
+
|
|
35
|
+
const MEDIA_SNIPPET = `<kai-card heading="Quarterly report" description="Generated just now.">
|
|
36
|
+
<img slot="media" src="/preview.png" alt="Report preview" />
|
|
37
|
+
<p>Your Q2 numbers are ready to review.</p>
|
|
38
|
+
</kai-card>`;
|
|
39
|
+
|
|
40
|
+
const ERROR_SNIPPET = `<!-- The one consistent inline error every card uses. -->
|
|
41
|
+
<kai-card heading="Share your feedback" error-message="This card couldn't be displayed."></kai-card>`;
|
|
42
|
+
|
|
43
|
+
const meta = {
|
|
44
|
+
title: 'Generative UI/Cards/kai-card',
|
|
45
|
+
tags: ['autodocs'],
|
|
46
|
+
argTypes: argTypesFor('kai-card'),
|
|
47
|
+
parameters: {
|
|
48
|
+
layout: 'padded',
|
|
49
|
+
docs: {
|
|
50
|
+
description: specDescription('kai-card', [
|
|
51
|
+
'`<kai-card>` is the shared, presentational **card chrome** every native generative-UI card composes from: an optional **media** region (`slot="media"`), a **heading** + **description**, a **body** (default slot), an **actions** footer (`slot="actions"`), and one consistent inline **error** state (`error-message`).',
|
|
52
|
+
'**Chrome only — it emits no events and reads no context.** Raw light-DOM content slotted into `kai-card` (such as plain `<button>` elements) is unstyled because light DOM cannot reach shadow CSS. The cards that compose it own the Card-contract interaction.',
|
|
53
|
+
'**For interactive, data-driven cards use the typed cards:** `<kai-confirm>` (approval actions — see that story for the canonical data-in → event-out example), `<kai-form>` (input/submit), `<kai-choice>` (single-select), `<kai-tasks>` (progress), `<kai-link-preview>` (rich link), `<kai-embed>` (media). Use `kai-card` only as a **plain themed surface** for presentational content.',
|
|
54
|
+
"**How to use as a surface:** register once with `import '@kitn.ai/ui/elements'`, set `heading`/`description` attributes, drop body markup in the default slot, and optionally a media region in `slot=\"media\"`. Set `error-message` to render the standard inline error (it replaces the body/actions).",
|
|
55
|
+
'See the **Code** tab for HTML usage.',
|
|
56
|
+
]),
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
} satisfies Meta;
|
|
60
|
+
|
|
61
|
+
export default meta;
|
|
62
|
+
type Story = StoryObj;
|
|
63
|
+
|
|
64
|
+
/** `kai-card` as a plain themed surface: heading + description + slotted text content. This is the correct use of `kai-card` — presentational chrome around passive content. For interactive cards (actions, forms, choices) use the typed cards such as `kai-confirm`. */
|
|
65
|
+
export const ContentSurface: Story = {
|
|
66
|
+
name: 'Content surface',
|
|
67
|
+
render: () => (
|
|
68
|
+
<Frame>
|
|
69
|
+
<kai-card heading="Workspace summary" description="Generated just now.">
|
|
70
|
+
<p style={{ margin: 0 }}>Your repository has 3 open pull requests and 12 passing checks.</p>
|
|
71
|
+
</kai-card>
|
|
72
|
+
</Frame>
|
|
73
|
+
),
|
|
74
|
+
parameters: { docs: { source: { code: CONTENT_SNIPPET, language: 'html' } } },
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** An image media region above the heading. */
|
|
78
|
+
export const WithMedia: Story = {
|
|
79
|
+
render: () => (
|
|
80
|
+
<Frame>
|
|
81
|
+
<kai-card heading="Quarterly report" description="Generated just now.">
|
|
82
|
+
<div
|
|
83
|
+
slot="media"
|
|
84
|
+
style={{
|
|
85
|
+
height: '120px',
|
|
86
|
+
display: 'grid',
|
|
87
|
+
'place-items': 'center',
|
|
88
|
+
background: 'var(--color-muted, #f4f4f5)',
|
|
89
|
+
color: 'var(--color-muted-foreground, #71717a)',
|
|
90
|
+
'font-size': '13px',
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
media slot
|
|
94
|
+
</div>
|
|
95
|
+
<p style={{ margin: 0 }}>Your Q2 numbers are ready to review.</p>
|
|
96
|
+
</kai-card>
|
|
97
|
+
</Frame>
|
|
98
|
+
),
|
|
99
|
+
parameters: { docs: { source: { code: MEDIA_SNIPPET, language: 'html' } } },
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** The standard inline error state (replaces the body/actions). */
|
|
103
|
+
export const ErrorState: Story = {
|
|
104
|
+
render: () => (
|
|
105
|
+
<Frame>
|
|
106
|
+
<kai-card
|
|
107
|
+
heading="Share your feedback"
|
|
108
|
+
error-message="This card couldn't be displayed."
|
|
109
|
+
/>
|
|
110
|
+
</Frame>
|
|
111
|
+
),
|
|
112
|
+
parameters: { docs: { source: { code: ERROR_SNIPPET, language: 'html' } } },
|
|
113
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defineWebComponent } from './define';
|
|
2
|
+
import { Card } from '../components/card';
|
|
3
|
+
|
|
4
|
+
interface Props extends Record<string, unknown> {
|
|
5
|
+
/** Heading rendered in the card chrome (= CardEnvelope.title). Attribute: `heading`. */
|
|
6
|
+
heading?: string;
|
|
7
|
+
/** Supporting text under the heading. Attribute: `description`. */
|
|
8
|
+
description?: string;
|
|
9
|
+
/** When set, the card renders its inline error state instead of the body.
|
|
10
|
+
* Attribute: `error-message`. */
|
|
11
|
+
errorMessage?: string;
|
|
12
|
+
/** Compact spacing for dense lists. Attribute: `dense`. */
|
|
13
|
+
dense?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `<kai-card>` — the shared, presentational card chrome every native card composes
|
|
18
|
+
* from: an optional media region (`slot="media"`), a heading + description, a body
|
|
19
|
+
* (default slot), an actions footer (`slot="actions"`), and one consistent inline
|
|
20
|
+
* **error** state (`error-message`). It emits **no** events and reads no context —
|
|
21
|
+
* it is chrome only; the cards that compose it (e.g. `<kai-form>`) own the contract
|
|
22
|
+
* interaction. Isolated in Shadow DOM; theme-aware via the shared kit tokens.
|
|
23
|
+
*/
|
|
24
|
+
defineWebComponent<Props>('kai-card', {
|
|
25
|
+
heading: undefined,
|
|
26
|
+
description: undefined,
|
|
27
|
+
errorMessage: undefined,
|
|
28
|
+
dense: false,
|
|
29
|
+
}, (props, { flag }) => (
|
|
30
|
+
<Card
|
|
31
|
+
heading={props.heading}
|
|
32
|
+
description={props.description}
|
|
33
|
+
errorMessage={props.errorMessage}
|
|
34
|
+
dense={flag('dense')}
|
|
35
|
+
media={<slot name="media" />}
|
|
36
|
+
actions={<slot name="actions" />}
|
|
37
|
+
>
|
|
38
|
+
<slot />
|
|
39
|
+
</Card>
|
|
40
|
+
));
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/elements/cards.stories.tsx
|
|
2
|
+
import type { Meta, StoryObj } from 'storybook-solidjs-vite';
|
|
3
|
+
import { createSignal, onMount } from 'solid-js';
|
|
4
|
+
import './register'; // registers all kai-* incl. kai-cards
|
|
5
|
+
import type { CardEnvelope, CardEvent } from '../primitives/card-contract';
|
|
6
|
+
|
|
7
|
+
declare module 'solid-js' {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
9
|
+
namespace JSX {
|
|
10
|
+
interface IntrinsicElements { 'kai-cards': JSX.HTMLAttributes<HTMLElement>; }
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const CARDS: CardEnvelope[] = [
|
|
15
|
+
{ type: 'confirm', id: 'deploy', title: 'Deploy to production?',
|
|
16
|
+
data: { body: 'Apply 3 migrations and deploy?', tone: 'warning',
|
|
17
|
+
actions: [{ id: 'go', label: 'Deploy', style: 'primary', default: true }, { id: 'no', label: 'Cancel' }] } },
|
|
18
|
+
{ type: 'tasks', id: 'plan', title: 'Pick the steps',
|
|
19
|
+
data: { tasks: [{ id: 'a', label: 'Run tests', checked: true }, { id: 'b', label: 'Tag release' }], confirmLabel: 'Run selected' } },
|
|
20
|
+
{ type: 'choice', id: 'plan-pick', title: 'Choose a plan',
|
|
21
|
+
data: { prompt: 'Which plan fits your team?', options: [
|
|
22
|
+
{ id: 'free', label: 'Free', meta: '$0' },
|
|
23
|
+
{ id: 'pro', label: 'Pro', meta: '$12/seat', recommended: true, payload: { plan: 'pro' } },
|
|
24
|
+
] } },
|
|
25
|
+
{ type: 'link', id: 'doc', data: { url: 'https://kitn.dev', title: 'kitn.dev', description: 'Generative UI cards', domain: 'kitn.dev' } },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** A host renders a stream of envelopes via <kai-cards> and handles events via `.policy`. */
|
|
29
|
+
function Demo() {
|
|
30
|
+
let el: HTMLElement | undefined;
|
|
31
|
+
const [log, setLog] = createSignal<string[]>([]);
|
|
32
|
+
onMount(() => {
|
|
33
|
+
if (!el) return;
|
|
34
|
+
(el as unknown as { cards: CardEnvelope[] }).cards = CARDS;
|
|
35
|
+
(el as unknown as { policy: unknown }).policy = {
|
|
36
|
+
onAction: (id: string, action: string) => setLog((l) => [`action: ${id} → ${action}`, ...l]),
|
|
37
|
+
onSubmit: (id: string, data: unknown) => setLog((l) => [`submit: ${id} → ${JSON.stringify(data)}`, ...l]),
|
|
38
|
+
onOpen: (url: string) => setLog((l) => [`open: ${url}`, ...l]),
|
|
39
|
+
onError: (id: string, msg: string) => setLog((l) => [`error: ${id} → ${msg}`, ...l]),
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
return (
|
|
43
|
+
<div style={{ display: 'grid', 'grid-template-columns': '1fr 18rem', gap: '1rem', padding: '1rem' }}>
|
|
44
|
+
<kai-cards ref={(e) => (el = e as HTMLElement)} />
|
|
45
|
+
<pre style={{ 'font-size': '12px', background: '#f4f4f5', padding: '8px', 'border-radius': '6px', 'min-height': '6rem' }}>
|
|
46
|
+
{log().join('\n') || 'events appear here…'}
|
|
47
|
+
</pre>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const meta = {
|
|
53
|
+
title: 'Generative UI/SDK/kai-cards',
|
|
54
|
+
parameters: { layout: 'fullscreen' },
|
|
55
|
+
} satisfies Meta;
|
|
56
|
+
export default meta;
|
|
57
|
+
type Story = StoryObj;
|
|
58
|
+
|
|
59
|
+
export const CardStream: Story = { render: () => <Demo /> };
|