Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dist

# Generated shadcn registry output
public/r/
app/registry/registry.json

# Node dependencies
node_modules
Expand Down
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Nuxt 4 documentation site with a built-in shadcn-vue component registry for dist
- `pnpm lint:fix` — auto-fix
- `pnpm typecheck` — `nuxt typecheck`
- `pnpm registry:build` — generate registry JSON files into `public/r/`
- `pnpm registry:verify` — verify generated registry dependency metadata after `registry:build`

CI order: **lint → typecheck** (no tests)

Expand All @@ -32,14 +33,16 @@ CI order: **lint → typecheck** (no tests)
2. `shadcn-vue build` outputs per-item JSON to `public/r/*.json`
3. Intermediate file `app/registry/registry.json` is auto-deleted

Run `pnpm registry:verify` after `pnpm registry:build` when registry dependencies change.

Config: `registry.config.ts` and `components.json` `registries` field.

## Style conventions

- ESLint stylistic: **double quotes**, **semicolons**
- Tailwind v4 via `@tailwindcss/vite` plugin — no `tailwind.config` file
- shadcn-vue style: `new-york`, baseColor: `neutral`, CSS variables enabled
- Icons: `lucide-vue-next`
- Icons: `lucide-vue-next` for app and local shadcn primitives; registry-distributed items targeting shadcn-vue Create may use `@lucide/vue` when fresh `shadcn-vue add` verification requires it

## Content

Expand Down
1 change: 1 addition & 0 deletions app/registry/blocks/chat-message/ChatMessage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ function handleAction(e: MouseEvent, action: ChatMessageAction) {
<Button
variant="ghost"
size="icon-sm"
:aria-label="action.label"
@click="handleAction($event, action)"
>
<LucideIcon
Expand Down
6 changes: 4 additions & 2 deletions app/registry/blocks/chat-messages/ChatMessages.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import type { UIMessage } from "ai";
import { ArrowDown } from "lucide-vue-next";
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { ArrowDown } from "@lucide/vue";
import type { ChatMessageAction } from "../chat-message/ChatMessage.vue";
import { ChatMessage } from "../chat-message";
import { Button } from "@/components/ui/button";
Expand Down Expand Up @@ -105,7 +106,7 @@ watch(
<div
ref="containerRef"
data-slot="chat-messages"
:class="cn('flex flex-col gap-4', props.class)"
:class="cn('relative flex flex-col gap-4', props.class)"
>
<ChatMessage
v-for="(message, index) in messages"
Expand Down Expand Up @@ -152,6 +153,7 @@ watch(
variant="outline"
size="icon-sm"
class="absolute right-1/2 translate-x-1/2 bottom-0 rounded-full shadow-md"
aria-label="Scroll to bottom"
@click="scrollToBottom"
>
<ArrowDown class="size-4" />
Expand Down
14 changes: 12 additions & 2 deletions app/registry/blocks/chat-prompt-submit/ChatPromptSubmit.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { ArrowUp, RotateCcw, Square } from "lucide-vue-next";
import { computed, type HTMLAttributes } from "vue";
import { ArrowUp, RotateCcw, Square } from "@lucide/vue";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

Expand All @@ -9,6 +9,8 @@ export interface ChatPromptSubmitProps {
status?: "ready" | "streaming" | "submitted" | "error";
/** Disable the button */
disabled?: boolean;
/** Accessible label for the icon-only button */
ariaLabel?: string;
/** Additional CSS classes */
class?: HTMLAttributes["class"];
}
Expand All @@ -30,6 +32,13 @@ const icon = computed(() => {

const isSubmit = computed(() => props.status === "ready");

const label = computed(() => {
if (props.ariaLabel) return props.ariaLabel;
if (props.status === "error") return "Retry message";
if (props.status === "streaming" || props.status === "submitted") return "Stop response";
return "Submit message";
});

function onClick() {
if (props.status === "error") {
emit("reload");
Expand All @@ -47,6 +56,7 @@ function onClick() {
variant="default"
size="icon-sm"
:disabled="disabled"
:aria-label="label"
:class="cn('size-7 rounded-md', props.class)"
@click="!isSubmit && onClick()"
>
Expand Down
2 changes: 1 addition & 1 deletion app/registry/blocks/chat-prompt/ChatPrompt.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue";
import { nextTick, onMounted, ref, watch, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";

export interface ChatPromptProps {
Expand Down
3 changes: 2 additions & 1 deletion app/registry/ui/lucide-icon/LucideIcon.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import type { Component } from "vue";
import { icons } from "lucide-vue-next";
import { computed } from "vue";
import { icons } from "@lucide/vue";

export interface LucideIconProps {
/** Icon identifier - accepts `i-lucide-*` style names or Vue components */
Expand Down
4 changes: 2 additions & 2 deletions content/docs/2.components/2.chat-message.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ npx shadcn-vue@latest add "https://ui.stackhacker.io/r/chat-message.json"

```vue
<script setup lang="ts">
import { ChatMessage } from '@/components/ui/chat-message'
import { ChatMessage } from '@/components/chat-message'
</script>

<template>
Expand Down Expand Up @@ -57,7 +57,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. |
| `actions` | `ChatMessageAction[]` | `[]` | Action buttons shown on hover. Each `label` is used for the tooltip and accessible button name. |
| `class` | `string` | — | Additional CSS classes. |

### Slots
Expand Down
2 changes: 1 addition & 1 deletion content/docs/2.components/3.chat-messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ npx shadcn-vue@latest add "https://ui.stackhacker.io/r/chat-messages.json"

```vue
<script setup lang="ts">
import { ChatMessages } from '@/components/ui/chat-messages'
import { ChatMessages } from '@/components/chat-messages'
</script>

<template>
Expand Down
2 changes: 1 addition & 1 deletion content/docs/2.components/4.chat-prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ npx shadcn-vue@latest add "https://ui.stackhacker.io/r/chat-prompt.json"

```vue
<script setup lang="ts">
import { ChatPrompt } from '@/components/ui/chat-prompt'
import { ChatPrompt } from '@/components/chat-prompt'

const input = ref('')
</script>
Expand Down
3 changes: 2 additions & 1 deletion content/docs/2.components/5.chat-prompt-submit.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ npx shadcn-vue@latest add "https://ui.stackhacker.io/r/chat-prompt-submit.json"

```vue
<script setup lang="ts">
import { ChatPromptSubmit } from '@/components/ui/chat-prompt-submit'
import { ChatPromptSubmit } from '@/components/chat-prompt-submit'
</script>

<template>
Expand All @@ -46,6 +46,7 @@ name: ChatPromptSubmitDemo
|------|------|---------|-------------|
| `status` | `'ready' \| 'streaming' \| 'submitted' \| 'error'` | `'ready'` | Current chat status. Controls the displayed icon. |
| `disabled` | `boolean` | `false` | Disable the button. |
| `ariaLabel` | `string` | status-aware label | Accessible label for the icon-only button. |
| `class` | `string` | — | Additional CSS classes. |

### Emits
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@
"typecheck": "nuxt typecheck",
"lint": "eslint .",
"lint:fix": "eslint --fix",
"registry:build": "tsx ./scripts/registry-build.ts"
"registry:build": "tsx ./scripts/registry-build.ts",
"registry:verify": "tsx ./scripts/registry-verify.ts"
},
"dependencies": {
"@libsql/client": "^0.17.3",
"@lucide/vue": "^1.21.0",
"@nuxt/content": "^3.13.0",
"@nuxt/eslint": "^1.15.2",
"@nuxt/icon": "^2.2.1",
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"shadcn-vue",
"shadcn-vue-registry",
"reka-ui",
"@lucide/vue",
"lucide-vue-next"
]
},
Expand Down
27 changes: 26 additions & 1 deletion scripts/registry-build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { mkdir, unlink, writeFile } from "node:fs/promises";
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
import pkg from "../package.json";
import components from "../components.json";
import { generateShadcnRegistry } from "shadcn-vue-registry";
Expand All @@ -24,6 +24,31 @@ import { x } from "tinyexec";

const registryJson = await generateShadcnRegistry(config);

const customItemNames = new Set(registryJson.items.map(item => item.name));
for (const item of registryJson.items) {
const registryDependencies = new Set(item.registryDependencies ?? []);

for (const file of item.files) {
const filePath = resolve(registryPath, file.path);
const content = await readFile(filePath, "utf8").catch(() => "");

for (const dependency of customItemNames) {
if (dependency === item.name) continue;
if (
content.includes(`../${dependency}`)
|| content.includes(`@/components/${dependency}`)
) {
registryDependencies.add(dependency);
}
}
}

item.registryDependencies = [...registryDependencies].map((dependency) => {
if (!customItemNames.has(dependency)) return dependency;
return `${config.homepage}/r/${dependency}.json`;
});
}

const registryJsonPath = resolve(registryPath, "registry.json");
await mkdir(dirname(registryJsonPath), { recursive: true });
await writeFile(registryJsonPath, JSON.stringify(registryJson, null, 2));
Expand Down
Loading
Loading