@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,399 @@
|
|
|
1
|
+
import { type JSX, createContext, useContext, splitProps, Show } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
import { Button } from '../ui/button';
|
|
4
|
+
import { HoverCardRoot, HoverCardTrigger, HoverCardContent } from '../ui/hover-card';
|
|
5
|
+
import {
|
|
6
|
+
FileText,
|
|
7
|
+
Globe,
|
|
8
|
+
Image as ImageIcon,
|
|
9
|
+
Music2,
|
|
10
|
+
Paperclip,
|
|
11
|
+
Video,
|
|
12
|
+
X,
|
|
13
|
+
} from 'lucide-solid';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Types
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
export interface AttachmentData {
|
|
20
|
+
id: string;
|
|
21
|
+
type: 'file' | 'source-document';
|
|
22
|
+
filename?: string;
|
|
23
|
+
mediaType?: string;
|
|
24
|
+
url?: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type AttachmentMediaCategory = 'image' | 'video' | 'audio' | 'document' | 'source' | 'unknown';
|
|
29
|
+
export type AttachmentVariant = 'grid' | 'inline' | 'list';
|
|
30
|
+
|
|
31
|
+
const mediaCategoryIcons: Record<AttachmentMediaCategory, typeof ImageIcon> = {
|
|
32
|
+
audio: Music2,
|
|
33
|
+
document: FileText,
|
|
34
|
+
image: ImageIcon,
|
|
35
|
+
source: Globe,
|
|
36
|
+
unknown: Paperclip,
|
|
37
|
+
video: Video,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Utility Functions
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
export const getMediaCategory = (data: AttachmentData): AttachmentMediaCategory => {
|
|
45
|
+
if (data.type === 'source-document') {
|
|
46
|
+
return 'source';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const mediaType = data.mediaType ?? '';
|
|
50
|
+
|
|
51
|
+
if (mediaType.startsWith('image/')) return 'image';
|
|
52
|
+
if (mediaType.startsWith('video/')) return 'video';
|
|
53
|
+
if (mediaType.startsWith('audio/')) return 'audio';
|
|
54
|
+
if (mediaType.startsWith('application/') || mediaType.startsWith('text/')) return 'document';
|
|
55
|
+
|
|
56
|
+
return 'unknown';
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const getAttachmentLabel = (data: AttachmentData): string => {
|
|
60
|
+
if (data.type === 'source-document') {
|
|
61
|
+
return data.title || data.filename || 'Source';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const category = getMediaCategory(data);
|
|
65
|
+
return data.filename || (category === 'image' ? 'Image' : 'Attachment');
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ============================================================================
|
|
69
|
+
// Contexts
|
|
70
|
+
// ============================================================================
|
|
71
|
+
|
|
72
|
+
interface AttachmentsContextValue {
|
|
73
|
+
variant: AttachmentVariant;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const AttachmentsContext = createContext<AttachmentsContextValue>();
|
|
77
|
+
|
|
78
|
+
interface AttachmentContextValue {
|
|
79
|
+
data: AttachmentData;
|
|
80
|
+
mediaCategory: AttachmentMediaCategory;
|
|
81
|
+
onRemove?: () => void;
|
|
82
|
+
variant: AttachmentVariant;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const AttachmentContext = createContext<AttachmentContextValue>();
|
|
86
|
+
|
|
87
|
+
// ============================================================================
|
|
88
|
+
// Hooks
|
|
89
|
+
// ============================================================================
|
|
90
|
+
|
|
91
|
+
export const useAttachmentsContext = () =>
|
|
92
|
+
useContext(AttachmentsContext) ?? { variant: 'grid' as const };
|
|
93
|
+
|
|
94
|
+
export const useAttachmentContext = () => {
|
|
95
|
+
const ctx = useContext(AttachmentContext);
|
|
96
|
+
if (!ctx) {
|
|
97
|
+
throw new Error('Attachment components must be used within <Attachment>');
|
|
98
|
+
}
|
|
99
|
+
return ctx;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// Attachments - Container
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
export interface AttachmentsProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
107
|
+
variant?: AttachmentVariant;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function Attachments(props: AttachmentsProps) {
|
|
111
|
+
const [local, rest] = splitProps(props, ['variant', 'class', 'children']);
|
|
112
|
+
const variant = () => local.variant ?? 'grid';
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<AttachmentsContext.Provider value={{ get variant() { return variant(); } }}>
|
|
116
|
+
<div
|
|
117
|
+
class={cn(
|
|
118
|
+
'flex items-start',
|
|
119
|
+
variant() === 'list' ? 'flex-col gap-2' : 'flex-wrap gap-2',
|
|
120
|
+
variant() === 'grid' && 'w-fit',
|
|
121
|
+
local.class,
|
|
122
|
+
)}
|
|
123
|
+
{...rest}
|
|
124
|
+
>
|
|
125
|
+
{local.children}
|
|
126
|
+
</div>
|
|
127
|
+
</AttachmentsContext.Provider>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================================================
|
|
132
|
+
// Attachment - Item
|
|
133
|
+
// ============================================================================
|
|
134
|
+
|
|
135
|
+
export interface AttachmentProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
136
|
+
data: AttachmentData;
|
|
137
|
+
onRemove?: () => void;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function Attachment(props: AttachmentProps) {
|
|
141
|
+
const [local, rest] = splitProps(props, ['data', 'onRemove', 'class', 'children']);
|
|
142
|
+
// Read the getter reactively — DON'T destructure, or the variant is captured
|
|
143
|
+
// once and the item never re-lays-out when the container variant changes.
|
|
144
|
+
const ctx = useAttachmentsContext();
|
|
145
|
+
const mediaCategory = () => getMediaCategory(local.data);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<AttachmentContext.Provider
|
|
149
|
+
value={{
|
|
150
|
+
get data() { return local.data; },
|
|
151
|
+
get mediaCategory() { return mediaCategory(); },
|
|
152
|
+
get onRemove() { return local.onRemove; },
|
|
153
|
+
get variant() { return ctx.variant; },
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
<div
|
|
157
|
+
class={cn(
|
|
158
|
+
'group relative',
|
|
159
|
+
ctx.variant === 'grid' && 'size-24 overflow-hidden rounded-lg',
|
|
160
|
+
ctx.variant === 'inline' && [
|
|
161
|
+
'flex h-8 cursor-pointer select-none items-center gap-1.5',
|
|
162
|
+
'rounded-md bg-muted/50 px-1.5 text-foreground',
|
|
163
|
+
'font-medium text-sm transition-all',
|
|
164
|
+
'hover:bg-muted',
|
|
165
|
+
],
|
|
166
|
+
ctx.variant === 'list' && [
|
|
167
|
+
'flex w-full items-center gap-3 rounded-lg bg-muted/30 p-3 text-foreground',
|
|
168
|
+
'hover:bg-muted/50',
|
|
169
|
+
],
|
|
170
|
+
local.class,
|
|
171
|
+
)}
|
|
172
|
+
{...rest}
|
|
173
|
+
>
|
|
174
|
+
{local.children}
|
|
175
|
+
</div>
|
|
176
|
+
</AttachmentContext.Provider>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// AttachmentPreview - Media preview
|
|
182
|
+
// ============================================================================
|
|
183
|
+
|
|
184
|
+
export interface AttachmentPreviewProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
185
|
+
fallbackIcon?: JSX.Element;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function AttachmentPreview(props: AttachmentPreviewProps) {
|
|
189
|
+
const [local, rest] = splitProps(props, ['fallbackIcon', 'class']);
|
|
190
|
+
const ctx = useAttachmentContext();
|
|
191
|
+
|
|
192
|
+
const iconSize = () => ctx.variant === 'inline' ? 'size-3' : 'size-4';
|
|
193
|
+
|
|
194
|
+
const renderIcon = (Icon: typeof ImageIcon) => (
|
|
195
|
+
<Icon class={cn(iconSize(), 'text-muted-foreground')} />
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const renderContent = () => {
|
|
199
|
+
if (ctx.mediaCategory === 'image' && ctx.data.type === 'file' && ctx.data.url) {
|
|
200
|
+
return ctx.variant === 'grid' ? (
|
|
201
|
+
<img
|
|
202
|
+
alt={ctx.data.filename || 'Image'}
|
|
203
|
+
class="size-full object-cover"
|
|
204
|
+
height={96}
|
|
205
|
+
src={ctx.data.url}
|
|
206
|
+
width={96}
|
|
207
|
+
/>
|
|
208
|
+
) : (
|
|
209
|
+
<img
|
|
210
|
+
alt={ctx.data.filename || 'Image'}
|
|
211
|
+
class="size-full rounded object-cover"
|
|
212
|
+
height={20}
|
|
213
|
+
src={ctx.data.url}
|
|
214
|
+
width={20}
|
|
215
|
+
/>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (ctx.mediaCategory === 'video' && ctx.data.type === 'file' && ctx.data.url) {
|
|
220
|
+
return <video class="size-full object-cover" muted src={ctx.data.url} />;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const Icon = mediaCategoryIcons[ctx.mediaCategory];
|
|
224
|
+
return local.fallbackIcon ?? renderIcon(Icon);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div
|
|
229
|
+
class={cn(
|
|
230
|
+
'flex shrink-0 items-center justify-center overflow-hidden',
|
|
231
|
+
ctx.variant === 'grid' && 'size-full bg-muted',
|
|
232
|
+
ctx.variant === 'inline' && 'size-5 rounded bg-background',
|
|
233
|
+
ctx.variant === 'list' && 'size-12 rounded bg-muted',
|
|
234
|
+
local.class,
|
|
235
|
+
)}
|
|
236
|
+
{...rest}
|
|
237
|
+
>
|
|
238
|
+
{renderContent()}
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ============================================================================
|
|
244
|
+
// AttachmentInfo - Name and type display
|
|
245
|
+
// ============================================================================
|
|
246
|
+
|
|
247
|
+
export interface AttachmentInfoProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
248
|
+
showMediaType?: boolean;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function AttachmentInfo(props: AttachmentInfoProps) {
|
|
252
|
+
const [local, rest] = splitProps(props, ['showMediaType', 'class']);
|
|
253
|
+
const ctx = useAttachmentContext();
|
|
254
|
+
const label = () => getAttachmentLabel(ctx.data);
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<Show when={ctx.variant !== 'grid'}>
|
|
258
|
+
<div class={cn('min-w-0 flex-1', local.class)} {...rest}>
|
|
259
|
+
<span class="block truncate">{label()}</span>
|
|
260
|
+
{/* The media-type subtitle is a two-line affordance — only the `list`
|
|
261
|
+
variant has room for it; `inline` chips are a fixed single-line height. */}
|
|
262
|
+
<Show when={local.showMediaType && ctx.variant === 'list' && ctx.data.mediaType}>
|
|
263
|
+
<span class="text-muted-foreground text-caption block truncate">
|
|
264
|
+
{ctx.data.mediaType}
|
|
265
|
+
</span>
|
|
266
|
+
</Show>
|
|
267
|
+
</div>
|
|
268
|
+
</Show>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// AttachmentRemove - Remove button
|
|
274
|
+
// ============================================================================
|
|
275
|
+
|
|
276
|
+
export interface AttachmentRemoveProps {
|
|
277
|
+
label?: string;
|
|
278
|
+
class?: string;
|
|
279
|
+
children?: JSX.Element;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function AttachmentRemove(props: AttachmentRemoveProps) {
|
|
283
|
+
const ctx = useAttachmentContext();
|
|
284
|
+
|
|
285
|
+
const handleClick = (e: MouseEvent) => {
|
|
286
|
+
e.stopPropagation();
|
|
287
|
+
ctx.onRemove?.();
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
return (
|
|
291
|
+
<Show when={ctx.onRemove}>
|
|
292
|
+
<Button
|
|
293
|
+
aria-label={props.label ?? 'Remove'}
|
|
294
|
+
class={cn(
|
|
295
|
+
ctx.variant === 'grid' && [
|
|
296
|
+
'absolute top-2 right-2 size-6 rounded-full p-0',
|
|
297
|
+
'bg-background/80 backdrop-blur-sm',
|
|
298
|
+
'opacity-0 transition-opacity group-hover:opacity-100',
|
|
299
|
+
'hover:bg-background',
|
|
300
|
+
'[&>svg]:size-3',
|
|
301
|
+
],
|
|
302
|
+
ctx.variant === 'inline' && [
|
|
303
|
+
'size-5 rounded p-0',
|
|
304
|
+
'opacity-0 transition-opacity group-hover:opacity-100',
|
|
305
|
+
'[&>svg]:size-2.5',
|
|
306
|
+
],
|
|
307
|
+
ctx.variant === 'list' && ['size-8 shrink-0 rounded p-0', '[&>svg]:size-4'],
|
|
308
|
+
props.class,
|
|
309
|
+
)}
|
|
310
|
+
onClick={handleClick}
|
|
311
|
+
type="button"
|
|
312
|
+
variant="ghost"
|
|
313
|
+
>
|
|
314
|
+
{props.children ?? <X />}
|
|
315
|
+
<span class="sr-only">{props.label ?? 'Remove'}</span>
|
|
316
|
+
</Button>
|
|
317
|
+
</Show>
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ============================================================================
|
|
322
|
+
// AttachmentHoverCard - Hover preview
|
|
323
|
+
// ============================================================================
|
|
324
|
+
|
|
325
|
+
export interface AttachmentHoverCardProps {
|
|
326
|
+
children: JSX.Element;
|
|
327
|
+
openDelay?: number;
|
|
328
|
+
closeDelay?: number;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function AttachmentHoverCard(props: AttachmentHoverCardProps) {
|
|
332
|
+
return (
|
|
333
|
+
<HoverCardRoot
|
|
334
|
+
openDelay={props.openDelay ?? 0}
|
|
335
|
+
closeDelay={props.closeDelay ?? 0}
|
|
336
|
+
>
|
|
337
|
+
{props.children}
|
|
338
|
+
</HoverCardRoot>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export interface AttachmentHoverCardTriggerProps {
|
|
343
|
+
children: JSX.Element;
|
|
344
|
+
class?: string;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function AttachmentHoverCardTrigger(props: AttachmentHoverCardTriggerProps) {
|
|
348
|
+
return <HoverCardTrigger class={props.class}>{props.children}</HoverCardTrigger>;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export interface AttachmentHoverCardContentProps {
|
|
352
|
+
children: JSX.Element;
|
|
353
|
+
class?: string;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function AttachmentHoverCardContent(props: AttachmentHoverCardContentProps) {
|
|
357
|
+
return (
|
|
358
|
+
<HoverCardContent class={cn('w-auto p-2', props.class)}>
|
|
359
|
+
{props.children}
|
|
360
|
+
</HoverCardContent>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ============================================================================
|
|
365
|
+
// AttachmentEmpty - Empty state
|
|
366
|
+
// ============================================================================
|
|
367
|
+
|
|
368
|
+
export interface AttachmentEmptyProps extends JSX.HTMLAttributes<HTMLDivElement> {}
|
|
369
|
+
|
|
370
|
+
function AttachmentEmpty(props: AttachmentEmptyProps) {
|
|
371
|
+
const [local, rest] = splitProps(props, ['class', 'children']);
|
|
372
|
+
return (
|
|
373
|
+
<div
|
|
374
|
+
class={cn(
|
|
375
|
+
'flex items-center justify-center p-4 text-muted-foreground text-sm',
|
|
376
|
+
local.class,
|
|
377
|
+
)}
|
|
378
|
+
{...rest}
|
|
379
|
+
>
|
|
380
|
+
{local.children ?? 'No attachments'}
|
|
381
|
+
</div>
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ============================================================================
|
|
386
|
+
// Exports
|
|
387
|
+
// ============================================================================
|
|
388
|
+
|
|
389
|
+
export {
|
|
390
|
+
Attachments,
|
|
391
|
+
Attachment,
|
|
392
|
+
AttachmentPreview,
|
|
393
|
+
AttachmentInfo,
|
|
394
|
+
AttachmentRemove,
|
|
395
|
+
AttachmentHoverCard,
|
|
396
|
+
AttachmentHoverCardTrigger,
|
|
397
|
+
AttachmentHoverCardContent,
|
|
398
|
+
AttachmentEmpty,
|
|
399
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/components/card-fallback.tsx
|
|
2
|
+
// Rendered by the dispatcher when an envelope's `type` has no registered card.
|
|
3
|
+
// Visual: reuses the Card chrome so it sits naturally in a card stream.
|
|
4
|
+
import type { JSX } from 'solid-js';
|
|
5
|
+
import { Card } from './card';
|
|
6
|
+
import { AlertTriangle } from 'lucide-solid';
|
|
7
|
+
|
|
8
|
+
export interface CardFallbackProps {
|
|
9
|
+
/** The unrecognized envelope type, shown to aid debugging. */
|
|
10
|
+
type: string;
|
|
11
|
+
/** The envelope id (for parity with cards; not displayed). */
|
|
12
|
+
cardId?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Inert, themed fallback for an unsupported card type. Emits nothing itself —
|
|
16
|
+
* the dispatcher emits the contract `error` event alongside rendering this. */
|
|
17
|
+
export function CardFallback(props: CardFallbackProps): JSX.Element {
|
|
18
|
+
return (
|
|
19
|
+
<Card>
|
|
20
|
+
<div role="alert" class="flex items-center gap-2 p-4 text-sm text-muted-foreground">
|
|
21
|
+
<AlertTriangle class="size-4 shrink-0 text-destructive dark:text-red-400" aria-hidden="true" />
|
|
22
|
+
<span>
|
|
23
|
+
Unsupported card type: <code class="font-mono">{props.type}</code>
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
26
|
+
</Card>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// src/components/card-renderer.tsx
|
|
2
|
+
// The Solid single-envelope dispatcher: pick the card for envelope.type and render it
|
|
3
|
+
// with the envelope spread onto its props. Routing uses the ambient CardProvider
|
|
4
|
+
// (useCardHost). Unknown type → CardFallback + a one-shot contract `error` emit.
|
|
5
|
+
import { createMemo, untrack, Show, type JSX } from 'solid-js';
|
|
6
|
+
import { Dynamic } from 'solid-js/web';
|
|
7
|
+
import type { CardEnvelope } from '../primitives/card-contract';
|
|
8
|
+
import { useCardHost } from '../primitives/card-host';
|
|
9
|
+
import { mergeCardComponents, type CardComponentMap } from '../primitives/card-registry';
|
|
10
|
+
import { CardFallback } from './card-fallback';
|
|
11
|
+
|
|
12
|
+
export interface CardRendererProps {
|
|
13
|
+
envelope: CardEnvelope;
|
|
14
|
+
/** Add/override type→component entries (merged over the built-ins). */
|
|
15
|
+
types?: CardComponentMap;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function CardRenderer(props: CardRendererProps): JSX.Element {
|
|
19
|
+
const host = useCardHost();
|
|
20
|
+
const map = createMemo(() => mergeCardComponents(props.types));
|
|
21
|
+
const entry = createMemo(() => map()[props.envelope.type]);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Show
|
|
25
|
+
when={entry()}
|
|
26
|
+
fallback={<UnknownCard envelope={props.envelope} />}
|
|
27
|
+
>
|
|
28
|
+
{(comp) => <Dynamic component={comp()} envelope={props.envelope} host={host} />}
|
|
29
|
+
</Show>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Renders the fallback and emits exactly one `error` (untracked, on first render). */
|
|
34
|
+
function UnknownCard(props: { envelope: CardEnvelope }): JSX.Element {
|
|
35
|
+
const host = useCardHost();
|
|
36
|
+
untrack(() =>
|
|
37
|
+
host?.emit({
|
|
38
|
+
kind: 'error',
|
|
39
|
+
cardId: props.envelope.id,
|
|
40
|
+
message: `Unsupported card type: ${props.envelope.type}`,
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
return <CardFallback type={props.envelope.type} cardId={props.envelope.id} />;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Function sugar: renderCard(env) ≡ <CardRenderer envelope={env} />. */
|
|
47
|
+
export function renderCard(
|
|
48
|
+
envelope: CardEnvelope,
|
|
49
|
+
opts?: { types?: CardComponentMap },
|
|
50
|
+
): JSX.Element {
|
|
51
|
+
return <CardRenderer envelope={envelope} types={opts?.types} />;
|
|
52
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type JSX, Show, splitProps, mergeProps, createUniqueId } from 'solid-js';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
import { AlertTriangle } from 'lucide-solid';
|
|
4
|
+
|
|
5
|
+
export interface CardProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
|
6
|
+
/** Heading rendered in the card chrome (the contract's CardEnvelope.title).
|
|
7
|
+
* NB: NOT `title` — `title` is a reserved IDL attr (see define.tsx RESERVED). */
|
|
8
|
+
heading?: string;
|
|
9
|
+
/** Optional supporting text under the heading. */
|
|
10
|
+
description?: string;
|
|
11
|
+
/** Media region (image/icon) rendered above the heading. */
|
|
12
|
+
media?: JSX.Element;
|
|
13
|
+
/** Footer action region (buttons). */
|
|
14
|
+
actions?: JSX.Element;
|
|
15
|
+
/** When set, the card renders its standard inline error state INSTEAD of body. */
|
|
16
|
+
errorMessage?: string;
|
|
17
|
+
/** Compact spacing for dense lists of cards. */
|
|
18
|
+
dense?: boolean;
|
|
19
|
+
/** Stable id for the heading (so composing cards can `aria-labelledby` it).
|
|
20
|
+
* Auto-generated when omitted. */
|
|
21
|
+
headingId?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* `Card` — the shared presentational chrome every native card composes from:
|
|
26
|
+
* an optional media region, a heading + description, a body (default slot), an
|
|
27
|
+
* actions footer, and one consistent inline **error** state (the contract's
|
|
28
|
+
* "never a broken/partial card" rule). It is intentionally chrome-only: it reads
|
|
29
|
+
* no `CardContext` and emits no `CardEvent` — the cards that compose it (e.g.
|
|
30
|
+
* `kai-form`) own the contract interaction.
|
|
31
|
+
*/
|
|
32
|
+
export function Card(props: CardProps): JSX.Element {
|
|
33
|
+
const merged = mergeProps({ dense: false }, props);
|
|
34
|
+
const [local, rest] = splitProps(merged, [
|
|
35
|
+
'heading',
|
|
36
|
+
'description',
|
|
37
|
+
'media',
|
|
38
|
+
'actions',
|
|
39
|
+
'errorMessage',
|
|
40
|
+
'dense',
|
|
41
|
+
'headingId',
|
|
42
|
+
'class',
|
|
43
|
+
'children',
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const autoId = createUniqueId();
|
|
47
|
+
const headingId = () => local.headingId ?? `kai-card-heading-${autoId}`;
|
|
48
|
+
const hasError = () => local.errorMessage !== undefined && local.errorMessage !== '';
|
|
49
|
+
const hasHeader = () => Boolean(local.heading) || Boolean(local.description);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
class={cn(
|
|
54
|
+
'flex flex-col rounded-xl border border-border bg-card text-card-foreground kai-elevation-sm',
|
|
55
|
+
local.dense ? 'gap-2.5 p-3.5' : 'gap-4 p-5',
|
|
56
|
+
local.class,
|
|
57
|
+
)}
|
|
58
|
+
{...rest}
|
|
59
|
+
>
|
|
60
|
+
<Show when={local.media}>
|
|
61
|
+
<div class="overflow-hidden rounded-lg">{local.media}</div>
|
|
62
|
+
</Show>
|
|
63
|
+
|
|
64
|
+
<Show when={hasHeader()}>
|
|
65
|
+
<div
|
|
66
|
+
class={cn(
|
|
67
|
+
'flex flex-col border-b border-border',
|
|
68
|
+
local.dense ? 'gap-0.5 pb-2.5' : 'gap-1 pb-4',
|
|
69
|
+
)}
|
|
70
|
+
>
|
|
71
|
+
<Show when={local.heading}>
|
|
72
|
+
<h3
|
|
73
|
+
id={headingId()}
|
|
74
|
+
class="text-[1.0625rem] font-semibold leading-snug tracking-tight text-foreground"
|
|
75
|
+
>
|
|
76
|
+
{local.heading}
|
|
77
|
+
</h3>
|
|
78
|
+
</Show>
|
|
79
|
+
<Show when={local.description}>
|
|
80
|
+
<p class="text-[0.8125rem] leading-relaxed text-muted-foreground">{local.description}</p>
|
|
81
|
+
</Show>
|
|
82
|
+
</div>
|
|
83
|
+
</Show>
|
|
84
|
+
|
|
85
|
+
<Show
|
|
86
|
+
when={hasError()}
|
|
87
|
+
fallback={
|
|
88
|
+
<>
|
|
89
|
+
<Show when={local.children}>
|
|
90
|
+
<div class="text-sm text-foreground">{local.children}</div>
|
|
91
|
+
</Show>
|
|
92
|
+
<Show when={local.actions}>
|
|
93
|
+
<div class="flex flex-wrap items-center justify-end gap-2 pt-1">
|
|
94
|
+
{local.actions}
|
|
95
|
+
</div>
|
|
96
|
+
</Show>
|
|
97
|
+
</>
|
|
98
|
+
}
|
|
99
|
+
>
|
|
100
|
+
<div
|
|
101
|
+
role="alert"
|
|
102
|
+
class="flex items-start gap-2 rounded-lg border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive dark:bg-destructive/15 dark:text-red-400"
|
|
103
|
+
>
|
|
104
|
+
<AlertTriangle size={16} class="mt-0.5 shrink-0" aria-hidden="true" />
|
|
105
|
+
<span>{local.errorMessage}</span>
|
|
106
|
+
</div>
|
|
107
|
+
</Show>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|