UNPKG

@kitn.ai/chat

Version:

Framework-agnostic, Shadow-DOM web components for building AI chat interfaces — works in React, Vue, Angular, Svelte, or plain HTML. Authored in SolidJS.

270 lines (190 loc) 9.23 kB
import { Meta } from '@storybook/addon-docs/blocks'; <Meta title="Docs/Composition & Child Elements" /> # Composition & Child Elements Seven collection elements in the kit accept two input paths — **data** (a JS property array) and **composition** (declarative child markup). Both produce identical rendered output and can be mixed on the same element. ## Two ways to populate a collection ### Data — the JS property array Set the element's array property from JavaScript. The parent re-renders reactively whenever the reference changes. ```html <kc-suggestions id="s"></kc-suggestions> <script type="module"> import '@kitn.ai/chat/elements'; document.getElementById('s').suggestions = [ { label: 'Summarise this', value: 'summarise' }, { label: 'Translate to French', value: 'translate' }, ]; </script> ``` ### Composition — declarative child elements Place `<kc-*>` child elements directly in markup. They live in the light DOM but are invisible — the parent's Shadow DOM has no `<slot>` for them, so it reads them via `querySelectorAll` and a `MutationObserver` instead. No JavaScript required. ```html <kc-suggestions> <kc-suggestion value="summarise">Summarise this</kc-suggestion> <kc-suggestion value="translate">Translate to French</kc-suggestion> </kc-suggestions> <script type="module"> import '@kitn.ai/chat/elements'; </script> ``` ### Merge order When both are provided, **property items render first** and child elements are appended after. There is no conflict — they simply concatenate. --- ## Choosing the right path | Situation | Reach for | |-----------|-----------| | Data arrives from a fetch, a stream, or application state | **Data (property)** | | Content is model-driven, user-specific, or changes at runtime | **Data (property)** | | You're working in React, Vue, Svelte, or Angular with a component model | **Data (property)** | | Content is static and known at build time | **Composition (children)** | | You're authoring a CMS template, server-rendered HTML, or a no-build page | **Composition (children)** | | You want zero JavaScript for the initial population | **Composition (children)** | --- ## Child element reference Each row below is verified against the element's source in `src/elements/`. ### `<kc-action>` **Parents:** `<kc-message>` (action bar → `kc-message-action`) and `<kc-prompt-input>` (custom toolbar buttons → `kc-toolbar-action`) | Attribute | Maps to | Notes | |-----------|---------|-------| | `id` | `CustomAction.id` | Falls back to the `action` attribute when `id` is absent | | `action` | `CustomAction.id` | Secondary fallback when `id` is also absent | | `icon` | `CustomAction.icon` | Optional icon name | | `tooltip` | `CustomAction.tooltip` | Optional tooltip string | | *(text content)* | `CustomAction.label` | Falls back to the `label` attribute, then `id` | **Event fired by parent:** `kc-message-action``{ messageId: string, action: string }` ```html <kc-message> <kc-action id="copy" icon="copy" tooltip="Copy to clipboard">Copy</kc-action> <kc-action id="retry" icon="refresh">Retry</kc-action> </kc-message> ``` --- ### `<kc-suggestion>` **Parent:** `<kc-suggestions>` | Attribute | Maps to | Notes | |-----------|---------|-------| | `value` | `Item.value` | Falls back to text content when absent | | *(text content)* | `Item.label` | The chip label | **Event fired by parent:** `kc-select``{ value: string }` ```html <kc-suggestions> <kc-suggestion value="summarise">Summarise this</kc-suggestion> <kc-suggestion value="translate">Translate to French</kc-suggestion> </kc-suggestions> ``` --- ### `<kc-source>` **Parent:** `<kc-sources>` | Attribute | Maps to | Notes | |-----------|---------|-------| | `href` | `SourceItem.href` | Required — the citation URL | | `label` | `SourceItem.label` | Trigger label (defaults to the domain) | | `headline` | `SourceItem.title` | Hover-card headline. Named `headline` (not `title`) because `title` is a reserved HTML global attribute | | `description` | `SourceItem.description` | Hover-card body text | | `show-favicon` | `SourceItem.showFavicon` | Boolean — bare attribute or `show-favicon="true"` | | *(text content)* | *(not read by `kc-sources`)* | Text content on a child `<kc-source>` is not mapped by the parent; use `label`/`headline`/`description` attributes instead | **No interaction event** — sources open their `href` as links. ```html <kc-sources> <kc-source href="https://example.com/page" label="Example" headline="Example Domain" description="An illustrative domain used in documentation." show-favicon ></kc-source> </kc-sources> ``` > **Note:** `<kc-source>` also works as a **standalone** element — its own `href`, `label`, `headline`, `description`, and `show-favicon` props drive a single citation chip with a hover-card. The child-element role above is its use *inside* `<kc-sources>`. --- ### `<kc-conversation>` **Parent:** `<kc-conversations>` | Attribute | Maps to | Notes | |-----------|---------|-------| | `id` | `ConversationSummary.id` | Required — stable conversation identifier | | `group-id` | `ConversationSummary.groupId` | Optional group bucket (e.g. "today", "yesterday") | | *(text content)* | `ConversationSummary.title` | The conversation title shown in the list | Required fields not expressible as HTML attributes (`scope`, `messageCount`, `lastMessageAt`, `updatedAt`) receive safe defaults so the list item is fully functional with just `id` and text. **Events fired by parent:** `kc-conversation-select``{ id: string }`, `kc-new-chat``{}`, `kc-toggle-sidebar``{}` ```html <kc-conversations> <kc-conversation id="conv-1" group-id="today">Morning standup recap</kc-conversation> <kc-conversation id="conv-2" group-id="yesterday">API design review</kc-conversation> </kc-conversations> ``` --- ### `<kc-step>` **Parent:** `<kc-chain-of-thought>` | Attribute | Maps to | Notes | |-----------|---------|-------| | `label` | `Step.label` | The always-visible step heading (required) | | *(text content)* | `Step.content` | Optional expandable detail; empty string is treated as `undefined` (no expand affordance) | **No interaction event** — steps expand/collapse internally. ```html <kc-chain-of-thought> <kc-step label="Understand the request">The user wants a concise summary.</kc-step> <kc-step label="Draft a response">Condense the key points into three sentences.</kc-step> <kc-step label="Review"></kc-step> </kc-chain-of-thought> ``` --- ### `<kc-model>` **Parent:** `<kc-model-switcher>` | Attribute | Maps to | Notes | |-----------|---------|-------| | `id` | `ModelOption.id` | Required — the model identifier passed in `kc-model-change` | | `provider` | `ModelOption.provider` | Optional provider name shown in the switcher UI | | *(text content)* | `ModelOption.name` | Human-readable model name | **Event fired by parent:** `kc-model-change``{ modelId: string }` The switcher only renders when more than one model is provided. ```html <kc-model-switcher> <kc-model id="gpt-4o" provider="OpenAI">GPT-4o</kc-model> <kc-model id="gpt-4o-mini" provider="OpenAI">GPT-4o mini</kc-model> <kc-model id="claude-3-7-sonnet" provider="Anthropic">Claude 3.7 Sonnet</kc-model> </kc-model-switcher> ``` --- ### `<kc-skill>` **Parent:** `<kc-skills>` | Attribute | Maps to | Notes | |-----------|---------|-------| | `id` | `Skill.id` | Falls back to text content when absent | | *(text content)* | `Skill.name` | The badge label | **No interaction event** — skills badges are display-only. ```html <kc-skills> <kc-skill id="web-search">Web Search</kc-skill> <kc-skill id="code">Code</kc-skill> </kc-skills> ``` --- ## Side-by-side comparison The same set of suggestions rendered both ways — identical output, different authoring model. **Data path:** ```html <kc-suggestions id="s1"></kc-suggestions> <script type="module"> import '@kitn.ai/chat/elements'; document.getElementById('s1').suggestions = [ { label: 'Explain quantum computing', value: 'explain-quantum' }, { label: 'Write a haiku', value: 'haiku' }, { label: 'Summarise in bullet points', value: 'summarise' }, ]; </script> ``` **Composition path:** ```html <kc-suggestions> <kc-suggestion value="explain-quantum">Explain quantum computing</kc-suggestion> <kc-suggestion value="haiku">Write a haiku</kc-suggestion> <kc-suggestion value="summarise">Summarise in bullet points</kc-suggestion> </kc-suggestions> <script type="module"> import '@kitn.ai/chat/elements'; </script> ``` Both fire `kc-select` with the same `value` string. The only difference is where the data lives — in application state or in the HTML. --- ## Customisation Child elements are **attribute- and text-driven data carriers**. Their visual appearance is entirely controlled by the parent element's props and the kit's design tokens (`--kc-*`, `--color-*`). There is no CSS to pierce through Shadow DOM boundaries and no per-child styling surface — reach for the parent's documented attributes and tokens instead. See **[Theming](?path=/docs/theming-overview--docs)** for the full token reference.