Components
Source
A collapsible list of citation links backing an AI response. Purely presentational — no Morphos equivalent.
Installation
npx kosmesis add sourcepnpm dlx kosmesis add sourceyarn dlx kosmesis add sourcebunx kosmesis add sourceInstall the following dependencies:
npm install @morphos/iconspnpm add @morphos/iconsyarn add @morphos/iconsbun add @morphos/iconsCopy and paste the following code into your project.
import { StatefulComponent, StatelessComponent } from "@praxisjs/core";import { Component, Prop, State } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { Icon } from "@morphos/icons";import { cn } from "@/lib/utils";export interface SourcesProps { defaultOpen?: boolean; class?: string; children?: Children;}@Component()export class Sources extends StatefulComponent { @Prop() defaultOpen = false; @Prop() class?: string; @Prop() children?: Children; @State() _open = false; onBeforeMount() { this._open = this.defaultOpen; } toggle(): void { this._open = !this._open; } render() { return ( <div data-slot="sources" class={cn("text-sm", this.class)}> <button type="button" class="flex items-center gap-1.5 text-muted-foreground hover:text-foreground" onClick={() => { this.toggle(); }} > <span class={() => cn("inline-block transition-transform", this._open && "rotate-90")}> <Icon name="ChevronRight" size={14} /> </span> Sources </button> <div data-state={() => (this._open ? "open" : "closed")} class="mt-2 flex flex-col gap-1 data-[state=closed]:hidden" > {this.children} </div> </div> ); }}export interface SourceProps { href: string; class?: string; children?: Children;}@Component()export class Source extends StatelessComponent<SourceProps> { render() { const { href, class: cls, children } = this.props; return ( <a href={href} target="_blank" rel="noopener noreferrer" class={cn( "flex items-center gap-2 truncate rounded-md border px-2 py-1.5 text-xs text-muted-foreground hover:bg-accent hover:text-accent-foreground", cls, )} > <span class="truncate">{children ?? href}</span> </a> ); }}Install the following dependencies:
npm install @praxisjs/css @morphos/iconspnpm add @praxisjs/css @morphos/iconsyarn add @praxisjs/css @morphos/iconsbun add @praxisjs/css @morphos/iconsCopy and paste the following code into your project.
import { StatefulComponent, StatelessComponent } from "@praxisjs/core";import { cx, Stylesheet, Styled, tokenVars } from "@praxisjs/css";import { Component, Prop, State } from "@praxisjs/decorators";import type { Children } from "@praxisjs/shared";import { Icon } from "@morphos/icons";import { KosmesisTokens } from "@/lib/kosmesis-theme";const t = tokenVars(KosmesisTokens);class SourcesStyles extends Stylesheet { $root = this.css({ fontSize: "0.875rem" }); $trigger = this.css({ display: "flex", alignItems: "center", gap: "0.375rem", color: t.mutedForeground, cursor: "pointer", }).on("&:hover", { color: t.foreground }); $chevron = this.css({ display: "inline-block", transition: "transform 150ms ease" }).on("&[data-open]", { transform: "rotate(90deg)", }); $content = this.css({ marginTop: "0.5rem", display: "flex", flexDirection: "column", gap: "0.25rem" }).on( '&[data-state="closed"]', { display: "none" }, ); $source = this.css({ display: "flex", alignItems: "center", gap: "0.5rem", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", borderRadius: "0.375rem", border: `1px solid ${t.border}`, padding: "0.375rem 0.5rem", fontSize: "0.75rem", color: t.mutedForeground, }).on("&:hover", { backgroundColor: t.accent, color: t.accentForeground });}export interface SourcesProps { defaultOpen?: boolean; class?: string; children?: Children;}@Component()export class Sources extends StatefulComponent { @Styled(SourcesStyles) $s!: SourcesStyles; @Prop() defaultOpen = false; @Prop() class?: string; @Prop() children?: Children; @State() _open = false; onBeforeMount() { this._open = this.defaultOpen; } toggle(): void { this._open = !this._open; } render() { return ( <div data-slot="sources" class={cx(this.$s.$root, this.class)}> <button type="button" class={this.$s.$trigger} onClick={() => { this.toggle(); }}> <span data-open={() => (this._open ? "" : undefined)} class={this.$s.$chevron}> <Icon name="ChevronRight" size={14} /> </span> Sources </button> <div data-state={() => (this._open ? "open" : "closed")} class={this.$s.$content}> {this.children} </div> </div> ); }}export interface SourceProps { href: string; class?: string; children?: Children;}@Component()export class Source extends StatelessComponent<SourceProps> { @Styled(SourcesStyles) $s!: SourcesStyles; render() { const { href, class: cls, children } = this.props; return ( <a href={href} target="_blank" rel="noopener noreferrer" class={cx(this.$s.$source, cls)}> {children ?? href} </a> ); }}Examples
About
Purely presentational — no Morphos equivalent. Sources is the collapsible wrapper; each Source
is a plain external link styled as a small chip, defaulting its label to the href if no children
are given.
Usage
import { Source, Sources } from "@/components/ui/source";
<Sources defaultOpen>
<Source href="https://example.com/docs">Example Docs</Source>
<Source href="https://example.com" />
</Sources>Props
Sources
| Prop | Type | Default |
|---|---|---|
defaultOpen | boolean | false |
class | string | — |
Source
| Prop | Type | Default |
|---|---|---|
href | string | — |
class | string | — |