From 731778cb89aebc199bba30c6bd15b036ea648e37 Mon Sep 17 00:00:00 2001
From: Hirotaka Mizutani <52546+hirotaka@users.noreply.github.com>
Date: Wed, 24 Jun 2026 20:00:39 +0900
Subject: [PATCH] feat: Replace Lucide icon demo with chat message actions in
components
---
README.md | 2 +-
app/components/demo/ChatMessageDemo.vue | 7 ++
app/components/demo/ChatMessagesDemo.vue | 7 ++
app/components/demo/LucideIconComponent.vue | 13 ----
app/components/demo/LucideIconDemo.vue | 28 --------
app/components/demo/LucideIconSize.vue | 24 -------
app/components/ui/lucide-icon/index.ts | 1 -
.../blocks/chat-message/ChatMessage.vue | 9 ++-
app/registry/ui/lucide-icon/LucideIcon.vue | 51 --------------
app/registry/ui/lucide-icon/index.ts | 1 -
.../docs/1.getting-started/2.installation.md | 8 +--
content/docs/2.components/1.chat.md | 66 +++++++++++++++++++
content/docs/2.components/1.lucide-icon.md | 65 ------------------
content/docs/2.components/2.chat-message.md | 12 +++-
content/docs/2.components/3.chat-messages.md | 11 +++-
scripts/registry-build.ts | 10 +++
scripts/registry-verify.ts | 8 +--
17 files changed, 118 insertions(+), 205 deletions(-)
delete mode 100644 app/components/demo/LucideIconComponent.vue
delete mode 100644 app/components/demo/LucideIconDemo.vue
delete mode 100644 app/components/demo/LucideIconSize.vue
delete mode 100644 app/components/ui/lucide-icon/index.ts
delete mode 100644 app/registry/ui/lucide-icon/LucideIcon.vue
delete mode 100644 app/registry/ui/lucide-icon/index.ts
create mode 100644 content/docs/2.components/1.chat.md
delete mode 100644 content/docs/2.components/1.lucide-icon.md
diff --git a/README.md b/README.md
index 346e66f..a9c9402 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ This project aims to make shadcn-vue easier to use in Nuxt by providing:
Install an item with the shadcn-vue CLI:
```bash
-npx shadcn-vue@latest add "https://ui.stackhacker.io/r/lucide-icon.json"
+npx shadcn-vue@latest add "https://ui.stackhacker.io/r/chat-messages.json"
```
## Local development
diff --git a/app/components/demo/ChatMessageDemo.vue b/app/components/demo/ChatMessageDemo.vue
index 0645d36..8779ac0 100644
--- a/app/components/demo/ChatMessageDemo.vue
+++ b/app/components/demo/ChatMessageDemo.vue
@@ -1,5 +1,11 @@
@@ -9,6 +15,7 @@ import { ChatMessage } from "~/registry/blocks/chat-message";
role="assistant"
:parts="[{ type: 'text', text: 'Hello! How can I help you today?' }]"
side="left"
+ :actions="assistantActions"
/>
+import { Copy, RotateCcw } from "@lucide/vue";
import { ChatMessages } from "~/registry/blocks/chat-messages";
+const assistantActions = [
+ { label: "Copy message", icon: Copy },
+ { label: "Retry message", icon: RotateCcw },
+];
+
const messages = ref([
{
id: "1",
@@ -34,6 +40,7 @@ const messages = ref([
diff --git a/app/components/demo/LucideIconComponent.vue b/app/components/demo/LucideIconComponent.vue
deleted file mode 100644
index db4ba89..0000000
--- a/app/components/demo/LucideIconComponent.vue
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
diff --git a/app/components/demo/LucideIconDemo.vue b/app/components/demo/LucideIconDemo.vue
deleted file mode 100644
index 11f9889..0000000
--- a/app/components/demo/LucideIconDemo.vue
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/components/demo/LucideIconSize.vue b/app/components/demo/LucideIconSize.vue
deleted file mode 100644
index 9422883..0000000
--- a/app/components/demo/LucideIconSize.vue
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/app/components/ui/lucide-icon/index.ts b/app/components/ui/lucide-icon/index.ts
deleted file mode 100644
index b8639b3..0000000
--- a/app/components/ui/lucide-icon/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { LucideIcon, type LucideIconProps } from "~/registry/ui/lucide-icon";
diff --git a/app/registry/blocks/chat-message/ChatMessage.vue b/app/registry/blocks/chat-message/ChatMessage.vue
index 0038de6..75d8b84 100644
--- a/app/registry/blocks/chat-message/ChatMessage.vue
+++ b/app/registry/blocks/chat-message/ChatMessage.vue
@@ -1,14 +1,13 @@
-
-
-
-
diff --git a/app/registry/ui/lucide-icon/index.ts b/app/registry/ui/lucide-icon/index.ts
deleted file mode 100644
index 9652990..0000000
--- a/app/registry/ui/lucide-icon/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as LucideIcon, type LucideIconProps } from "./LucideIcon.vue";
diff --git a/content/docs/1.getting-started/2.installation.md b/content/docs/1.getting-started/2.installation.md
index e8e55b7..9957b54 100644
--- a/content/docs/1.getting-started/2.installation.md
+++ b/content/docs/1.getting-started/2.installation.md
@@ -26,10 +26,10 @@ A Nuxt project with shadcn-vue already configured. If you haven't set up shadcn-
Use the shadcn-vue CLI to add components from this registry:
```bash
-npx shadcn-vue@latest add "https://ui.stackhacker.io/r/lucide-icon.json"
+npx shadcn-vue@latest add "https://ui.stackhacker.io/r/chat-messages.json"
```
-This will install the component source into your `components/ui/` directory.
+This installs the message list component and its registry dependencies into your project.
#### Usage
@@ -37,11 +37,11 @@ Import and use the component in your app:
```vue
-
+
```
diff --git a/content/docs/2.components/1.chat.md b/content/docs/2.components/1.chat.md
new file mode 100644
index 0000000..76bfbd7
--- /dev/null
+++ b/content/docs/2.components/1.chat.md
@@ -0,0 +1,66 @@
+---
+title: Chat
+description: Build Nuxt chat interfaces with installable shadcn-vue components you own and compose.
+category: chat
+---
+
+Stackhacker UI Chat is a starter kit for adding chat UI pieces to a shadcn-vue Nuxt project.
+
+Unlike an integrated UI module, these registry items are installed as source code in your app. You can read them, change them, and compose them with your own AI SDK state, server endpoints, markdown rendering, files, tools, and auth.
+
+## Components
+
+| Component | Description |
+|-----------|-------------|
+| [ChatMessages](/docs/components/chat-messages) | Message list component with loading state, optional auto-scroll, and assistant actions. |
+| [ChatMessage](/docs/components/chat-message) | Individual message bubble component with content slots, leading content, and action buttons. |
+| [ChatPrompt](/docs/components/chat-prompt) | Prompt input wrapper for composing text areas, errors, and footer controls. |
+| [ChatPromptSubmit](/docs/components/chat-prompt-submit) | Status-aware submit, stop, and retry button for prompt workflows. |
+
+## Install
+
+Install the message list when you want the primary display surface. It also installs `ChatMessage` because the list composes individual messages.
+
+```bash
+npx shadcn-vue@latest add "https://ui.stackhacker.io/r/chat-messages.json"
+```
+
+Install prompt pieces when you want the input surface.
+
+```bash
+npx shadcn-vue@latest add "https://ui.stackhacker.io/r/chat-prompt.json"
+npx shadcn-vue@latest add "https://ui.stackhacker.io/r/chat-prompt-submit.json"
+```
+
+## Basic Composition
+
+```vue
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## App Responsibilities
+
+The registry items handle the UI boundaries. Your Nuxt app still owns the chat runtime:
+
+- AI SDK client state and message sending.
+- Server API endpoints and model provider setup.
+- Markdown, reasoning, tool, and file rendering.
+- Auth, persistence, layout, and deployment behavior.
+
+Use the component slots to connect those app-level concerns without turning the registry components into a full chat framework.
diff --git a/content/docs/2.components/1.lucide-icon.md b/content/docs/2.components/1.lucide-icon.md
deleted file mode 100644
index 05925af..0000000
--- a/content/docs/2.components/1.lucide-icon.md
+++ /dev/null
@@ -1,65 +0,0 @@
----
-title: LucideIcon
-description: Use Lucide icons in Nuxt projects with shadcn-vue-friendly naming and component support.
-category: element
----
-
-::component-preview
----
-name: LucideIconDemo
----
-::
-
-## Installation
-
-```bash
-npx shadcn-vue@latest add "https://ui.stackhacker.io/r/lucide-icon.json"
-```
-
-## Usage
-
-```vue
-
-
-
-
-
-```
-
-## Examples
-
-### Default
-
-::component-preview
----
-name: LucideIconDemo
----
-::
-
-### Size
-
-::component-preview
----
-name: LucideIconSize
----
-::
-
-### With Vue component
-
-::component-preview
----
-name: LucideIconComponent
----
-::
-
-## API Reference
-
-### Props
-
-| Prop | Type | Default | Description |
-|------|------|---------|-------------|
-| `name` | `string \| Component` | Required | Icon identifier. Accepts `i-lucide-*` format strings or Vue components. |
-| `size` | `string \| number` | — | Icon dimensions. Numbers are treated as pixels. |
-| `class` | `string` | — | CSS classes to apply to the icon. |
diff --git a/content/docs/2.components/2.chat-message.md b/content/docs/2.components/2.chat-message.md
index d95aff3..e2b925a 100644
--- a/content/docs/2.components/2.chat-message.md
+++ b/content/docs/2.components/2.chat-message.md
@@ -1,6 +1,6 @@
---
title: ChatMessage
-description: Render a single chat message with AI SDK parts, actions, and layout control for Nuxt chat interfaces.
+description: Render an individual chat message bubble with AI SDK parts, actions, and layout control.
category: chat
---
@@ -22,7 +22,12 @@ npx shadcn-vue@latest add "https://ui.stackhacker.io/r/chat-message.json"
```vue
@@ -31,6 +36,7 @@ import { ChatMessage } from '@/components/chat-message'
role="assistant"
:parts="[{ type: 'text', text: 'Hello!' }]"
side="left"
+ :actions="actions"
/>
```
@@ -57,7 +63,7 @@ name: ChatMessageDemo
| `role` | `'user' \| 'assistant' \| 'system'` | — | Message role. |
| `parts` | `UIMessage['parts']` | — | Message parts from AI SDK. |
| `side` | `'left' \| 'right'` | `'left'` | Which side to render the message. |
-| `actions` | `ChatMessageAction[]` | `[]` | Action buttons shown on hover. Each `label` is used for the tooltip and accessible button name. |
+| `actions` | `ChatMessageAction[]` | `[]` | Action buttons shown on hover. Each `icon` is a Vue component and each `label` is used for the tooltip and accessible button name. |
| `class` | `string` | — | Additional CSS classes. |
### Slots
@@ -72,7 +78,7 @@ name: ChatMessageDemo
```ts
interface ChatMessageAction {
label: string
- icon: string
+ icon: Component
onClick?: (e: MouseEvent, message: UIMessage) => void
}
```
diff --git a/content/docs/2.components/3.chat-messages.md b/content/docs/2.components/3.chat-messages.md
index 3d91241..06f41cd 100644
--- a/content/docs/2.components/3.chat-messages.md
+++ b/content/docs/2.components/3.chat-messages.md
@@ -1,6 +1,6 @@
---
title: ChatMessages
-description: Manage chat message lists with scrolling behavior, loading states, and action-ready assistant output.
+description: Compose chat message lists with scrolling behavior, loading states, and action-ready assistant output.
category: chat
---
@@ -20,11 +20,16 @@ npx shadcn-vue@latest add "https://ui.stackhacker.io/r/chat-messages.json"
```vue
-
+
```
@@ -47,7 +52,7 @@ name: ChatMessagesDemo
| `messages` | `UIMessage[]` | `[]` | Array of messages to display. |
| `status` | `'ready' \| 'streaming' \| 'submitted' \| 'error'` | `'ready'` | Current chat status. Shows loading indicator when `submitted`. |
| `shouldAutoScroll` | `boolean` | `false` | Enable auto-scroll to bottom on new messages. |
-| `assistant` | `{ actions?: ChatMessageAction[] }` | — | Assistant-specific configuration (e.g., action buttons). |
+| `assistant` | `{ actions?: ChatMessageAction[] }` | — | Assistant-specific configuration. Action icons are Vue components passed through to `ChatMessage`. |
| `class` | `string` | — | Additional CSS classes. |
### Slots
diff --git a/scripts/registry-build.ts b/scripts/registry-build.ts
index e277d1b..dbc3d54 100644
--- a/scripts/registry-build.ts
+++ b/scripts/registry-build.ts
@@ -25,9 +25,18 @@ import { x } from "tinyexec";
const registryJson = await generateShadcnRegistry(config);
const customItemNames = new Set(registryJson.items.map(item => item.name));
+ const itemPackageDependencies: Record = {
+ "chat-message": ["@lucide/vue"],
+ };
+
for (const item of registryJson.items) {
+ const dependencies = new Set(item.dependencies ?? []);
const registryDependencies = new Set(item.registryDependencies ?? []);
+ for (const dependency of itemPackageDependencies[item.name] ?? []) {
+ dependencies.add(dependency);
+ }
+
for (const file of item.files) {
const filePath = resolve(registryPath, file.path);
const content = await readFile(filePath, "utf8").catch(() => "");
@@ -43,6 +52,7 @@ import { x } from "tinyexec";
}
}
+ item.dependencies = [...dependencies];
item.registryDependencies = [...registryDependencies].map((dependency) => {
if (!customItemNames.has(dependency)) return dependency;
return `${config.homepage}/r/${dependency}.json`;
diff --git a/scripts/registry-verify.ts b/scripts/registry-verify.ts
index 6ce10ef..2b89fa6 100644
--- a/scripts/registry-verify.ts
+++ b/scripts/registry-verify.ts
@@ -75,10 +75,6 @@ const stackhackerRegistryUrl = `${expectedHomepage}/r`;
const stackhackerDependency = (name: string) => `${stackhackerRegistryUrl}/${name}.json`;
const expectedItems: Record = {
- "lucide-icon": {
- dependencies: ["@lucide/vue"],
- registryDependencies: [],
- },
"chat-prompt-submit": {
dependencies: ["@lucide/vue"],
registryDependencies: ["button"],
@@ -92,8 +88,8 @@ const expectedItems: Record = {
registryDependencies: ["button", stackhackerDependency("chat-message")],
},
"chat-message": {
- dependencies: ["ai"],
- registryDependencies: ["button", stackhackerDependency("lucide-icon"), "tooltip"],
+ dependencies: ["@lucide/vue", "ai"],
+ registryDependencies: ["button", "tooltip"],
},
};