chore(web): heroui skills

This commit is contained in:
2026-03-10 11:08:36 +08:00
parent 4d43ebd21e
commit 3ab225d76b
10 changed files with 1221 additions and 0 deletions

1
.agent/skills/heroui-react Symbolic link
View File

@@ -0,0 +1 @@
../../.agents/skills/heroui-react

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 NextUI Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,253 @@
---
name: heroui-react
description: "HeroUI v3 React component library (Tailwind CSS v4 + React Aria). Use when working with HeroUI components, installing HeroUI, customizing HeroUI themes, or accessing HeroUI component documentation. Keywords: HeroUI, Hero UI, heroui, @heroui/react, @heroui/styles."
metadata:
author: heroui
version: "2.0.0"
---
# HeroUI v3 React Development Guide
HeroUI v3 is a component library built on **Tailwind CSS v4** and **React Aria Components**, providing accessible, customizable UI components for React applications.
---
## CRITICAL: v3 Only - Ignore v2 Knowledge
**This guide is for HeroUI v3 ONLY.** Do NOT use any prior knowledge of HeroUI v2.
### What Changed in v3
| Feature | v2 (DO NOT USE) | v3 (USE THIS) |
| ------------- | --------------------------------- | ------------------------------------------- |
| Provider | `<HeroUIProvider>` required | **No Provider needed** |
| Animations | `framer-motion` package | CSS-based, no extra deps |
| Component API | Flat props: `<Card title="x">` | Compound: `<Card><Card.Header>` |
| Styling | Tailwind v3 + `@heroui/theme` | Tailwind v4 + `@heroui/styles@beta` |
| Packages | `@heroui/system`, `@heroui/theme` | `@heroui/react@beta`, `@heroui/styles@beta` |
### WRONG (v2 patterns)
```tsx
// DO NOT DO THIS - v2 pattern
import { HeroUIProvider } from "@heroui/react";
import { motion } from "framer-motion";
<HeroUIProvider>
<Card title="Product" description="A great product" />
</HeroUIProvider>;
```
### CORRECT (v3 patterns)
```tsx
// DO THIS - v3 pattern (no provider, compound components)
import { Card } from "@heroui/react@beta";
<Card>
<Card.Header>
<Card.Title>Product</Card.Title>
<Card.Description>A great product</Card.Description>
</Card.Header>
</Card>;
```
**Always fetch v3 docs before implementing.** Do not assume v2 patterns work.
---
## Core Principles
- Semantic variants (`primary`, `secondary`, `tertiary`) over visual descriptions
- Composition over configuration (compound components)
- CSS variable-based theming with `oklch` color space
- BEM naming convention for predictable styling
---
## Accessing Documentation & Component Information
**For component details, examples, props, and implementation patterns, always fetch documentation:**
### Using Scripts
```bash
# List all available components
node scripts/list_components.mjs
# Get component documentation (MDX)
node scripts/get_component_docs.mjs Button
node scripts/get_component_docs.mjs Button Card TextField
# Get component source code
node scripts/get_source.mjs Button
# Get component CSS styles (BEM classes)
node scripts/get_styles.mjs Button
# Get theme variables
node scripts/get_theme.mjs
# Get non-component docs (guides, releases)
node scripts/get_docs.mjs /docs/react/getting-started/theming
```
### Direct MDX URLs
Component docs: `https://v3.heroui.com/docs/react/components/{component-name}.mdx`
Examples:
- Button: `https://v3.heroui.com/docs/react/components/button.mdx`
- Modal: `https://v3.heroui.com/docs/react/components/modal.mdx`
- Form: `https://v3.heroui.com/docs/react/components/form.mdx`
Getting started guides: `https://v3.heroui.com/docs/react/getting-started/{topic}.mdx`
**Important:** Always fetch component docs before implementing. The MDX docs include complete examples, props, anatomy, and API references.
---
## Installation Essentials
**CRITICAL**: HeroUI v3 is currently in BETA. Always use `@beta` tag when installing packages.
### Quick Install
```bash
npm i @heroui/styles@beta @heroui/react@beta tailwind-variants
```
### Framework Setup (Next.js App Router - Recommended)
1. **Install dependencies:**
```bash
npm i @heroui/styles@beta @heroui/react@beta tailwind-variants tailwindcss @tailwindcss/postcss postcss
```
2. **Create/update `app/globals.css`:**
```css
/* Tailwind CSS v4 - Must be first */
@import "tailwindcss";
/* HeroUI v3 styles - Must be after Tailwind */
@import "@heroui/styles";
```
3. **Import in `app/layout.tsx`:**
```tsx
import "./globals.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en" suppressHydrationWarning>
<body>
{/* No Provider needed in HeroUI v3! */}
{children}
</body>
</html>
);
}
```
4. **Configure PostCSS (`postcss.config.mjs`):**
```js
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};
```
### Critical Setup Requirements
1. **Tailwind CSS v4 is MANDATORY** - HeroUI v3 will NOT work with Tailwind CSS v3
2. **No Provider Required** - Unlike HeroUI v2, v3 components work directly without a Provider
3. **Use Compound Components** - Components use compound structure (e.g., `Card.Header`, `Card.Content`)
4. **Use onPress, not onClick** - For better accessibility, use `onPress` event handlers
5. **Import Order Matters** - Always import Tailwind CSS before HeroUI styles
---
## Component Patterns
HeroUI v3 uses **compound component patterns**. Each component has subcomponents accessed via dot notation.
**Example - Card:**
```tsx
<Card>
<Card.Header>
<Card.Title>Title</Card.Title>
<Card.Description>Description</Card.Description>
</Card.Header>
<Card.Content>{/* Content */}</Card.Content>
<Card.Footer>{/* Actions */}</Card.Footer>
</Card>
```
**Key Points:**
- Always use compound structure - don't flatten to props
- Subcomponents are accessed via dot notation (e.g., `Card.Header`)
- Each subcomponent may have its own props
- **Fetch component docs for complete anatomy and examples**
---
## Semantic Variants
HeroUI uses semantic naming to communicate functional intent:
| Variant | Purpose | Usage |
| ----------- | --------------------------------- | -------------- |
| `primary` | Main action to move forward | 1 per context |
| `secondary` | Alternative actions | Multiple |
| `tertiary` | Dismissive actions (cancel, skip) | Sparingly |
| `danger` | Destructive actions | When needed |
| `ghost` | Low-emphasis actions | Minimal weight |
| `outline` | Secondary actions | Bordered style |
**Don't use raw colors** - semantic variants adapt to themes and accessibility.
---
## Theming
HeroUI v3 uses CSS variables with `oklch` color space:
```css
:root {
--accent: oklch(0.6204 0.195 253.83);
--accent-foreground: var(--snow);
--background: oklch(0.9702 0 0);
--foreground: var(--eclipse);
}
```
**Get current theme variables:**
```bash
node scripts/get_theme.mjs
```
**Color naming:**
- Without suffix = background (e.g., `--accent`)
- With `-foreground` = text color (e.g., `--accent-foreground`)
**Theme switching:**
```html
<html class="dark" data-theme="dark"></html>
```
For detailed theming, fetch: `https://v3.heroui.com/docs/react/getting-started/theming.mdx`

View File

@@ -0,0 +1,157 @@
#!/usr/bin/env node
/**
* Get complete component documentation (MDX) for HeroUI v3 components.
*
* Usage:
* node get_component_docs.mjs Button
* node get_component_docs.mjs Button Card TextField
*
* Output:
* MDX documentation including imports, usage, variants, props, examples
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const FALLBACK_BASE = "https://v3.heroui.com";
const APP_PARAM = "app=react-skills";
/**
* Convert PascalCase to kebab-case.
*/
function toKebabCase(name) {
return name
.replace(/([a-z])([A-Z])/g, "$1-$2")
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
.toLowerCase();
}
/**
* Fetch data from HeroUI API with app parameter for analytics.
*/
async function fetchApi(endpoint, method = "GET", body = null) {
const separator = endpoint.includes("?") ? "&" : "?";
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
try {
const options = {
headers: {
"Content-Type": "application/json",
"User-Agent": "HeroUI-Skill/1.0",
},
method,
signal: AbortSignal.timeout(30000),
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
if (!response.ok) {
return null;
}
return await response.json();
} catch {
return null;
}
}
/**
* Fetch MDX directly from v3.heroui.com as fallback.
*/
async function fetchFallback(component) {
const kebabName = toKebabCase(component);
const url = `${FALLBACK_BASE}/docs/react/components/${kebabName}.mdx`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
return {component, error: `Failed to fetch docs for ${component}`};
}
const content = await response.text();
return {
component,
content,
contentType: "mdx",
source: "fallback",
url,
};
} catch {
return {component, error: `Failed to fetch docs for ${component}`};
}
}
/**
* Main function to get component documentation.
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Usage: node get_component_docs.mjs <Component1> [Component2] ...");
console.error("Example: node get_component_docs.mjs Button Card");
process.exit(1);
}
const components = args;
// Try API first - use POST /v1/components/docs for batch requests
console.error(`# Fetching docs for: ${components.join(", ")}...`);
const data = await fetchApi("/v1/components/docs", "POST", {components});
if (data && data.results) {
// Output results
if (data.results.length === 1) {
// Single component - output content directly for easier reading
const result = data.results[0];
if (result.content) {
console.log(result.content);
} else if (result.error) {
console.error(`# Error for ${result.component}: ${result.error}`);
console.log(JSON.stringify(result, null, 2));
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
// Multiple components - output as JSON array
console.log(JSON.stringify(data, null, 2));
}
return;
}
// Fallback to individual component fetches
console.error("# API failed, using fallback...");
const results = [];
for (const component of components) {
const result = await fetchFallback(component);
results.push(result);
}
// Output results
if (results.length === 1) {
// Single component - output content directly for easier reading
const result = results[0];
if (result.content) {
console.log(result.content);
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
// Multiple components - output as JSON array
console.log(JSON.stringify(results, null, 2));
}
}
main();

View File

@@ -0,0 +1,148 @@
#!/usr/bin/env node
/**
* Get non-component HeroUI documentation (guides, theming, releases).
*
* Usage:
* node get_docs.mjs /docs/react/getting-started/theming
* node get_docs.mjs /docs/react/releases/v3-0-0-beta-3
*
* Output:
* MDX documentation content
*
* Note: For component docs, use get_component_docs.mjs instead.
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const FALLBACK_BASE = "https://v3.heroui.com";
const APP_PARAM = "app=react-skills";
/**
* Fetch documentation from HeroUI API.
* Uses v1 endpoint pattern: /v1/docs/:path
*/
async function fetchApi(path) {
// The v1 API expects path without /docs/ prefix
// Input: /docs/react/getting-started/theming
// API expects: react/getting-started/theming (route is /v1/docs/:path(*))
let apiPath = path.startsWith("/docs/")
? path.slice(6) // Remove /docs/ prefix
: path.startsWith("/")
? path.slice(1) // Remove leading /
: path;
const separator = "?";
const url = `${API_BASE}/v1/docs/${apiPath}${separator}${APP_PARAM}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
console.error(`# API Error: HTTP ${response.status}`);
return null;
}
return await response.json();
} catch (error) {
console.error(`# API Error: ${error.message}`);
return null;
}
}
/**
* Fetch MDX directly from v3.heroui.com as fallback.
*/
async function fetchFallback(path) {
// Ensure path starts with /docs and ends with .mdx
let cleanPath = path.replace(/^\//, "");
if (!cleanPath.endsWith(".mdx")) {
cleanPath = `${cleanPath}.mdx`;
}
const url = `${FALLBACK_BASE}/${cleanPath}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
return {error: `HTTP ${response.status}: ${response.statusText}`, path};
}
const content = await response.text();
return {
content,
contentType: "mdx",
path,
source: "fallback",
url,
};
} catch (error) {
return {error: `Fetch Error: ${error.message}`, path};
}
}
/**
* Main function to get documentation for specified path.
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Usage: node get_docs.mjs <path>");
console.error("Example: node get_docs.mjs /docs/react/getting-started/theming");
console.error();
console.error("Available paths include:");
console.error(" /docs/react/getting-started/theming");
console.error(" /docs/react/getting-started/colors");
console.error(" /docs/react/getting-started/animations");
console.error(" /docs/react/releases/v3-0-0-beta-3");
console.error();
console.error("Note: For component docs, use get_component_docs.mjs instead.");
process.exit(1);
}
const path = args[0];
// Check if user is trying to get component docs
if (path.includes("/components/")) {
console.error("# Warning: Use get_component_docs.mjs for component documentation.");
const componentName = path.split("/").pop().replace(".mdx", "");
const titleCase = componentName.charAt(0).toUpperCase() + componentName.slice(1);
console.error(`# Example: node get_component_docs.mjs ${titleCase}`);
}
console.error(`# Fetching documentation for ${path}...`);
// Try API first
const data = await fetchApi(path);
if (data && data.content) {
data.source = "api";
console.log(data.content);
return;
}
// Fallback to direct fetch
console.error("# API failed, using fallback...");
const fallbackData = await fetchFallback(path);
if (fallbackData.content) {
console.log(fallbackData.content);
} else {
console.log(JSON.stringify(fallbackData, null, 2));
process.exit(1);
}
}
main();

View File

@@ -0,0 +1,160 @@
#!/usr/bin/env node
/**
* Get React/TypeScript source code implementation for HeroUI v3 components.
*
* Usage:
* node get_source.mjs Button
* node get_source.mjs Button Accordion Card
*
* Output:
* Full TSX source code with GitHub URL for each component
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const GITHUB_RAW_BASE = "https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3";
const APP_PARAM = "app=react-skills";
/**
* Fetch data from HeroUI API with app parameter for analytics.
*/
async function fetchApi(endpoint, method = "GET", body = null) {
const separator = endpoint.includes("?") ? "&" : "?";
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
try {
const options = {
headers: {
"Content-Type": "application/json",
"User-Agent": "HeroUI-Skill/1.0",
},
method,
signal: AbortSignal.timeout(30000),
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
if (!response.ok) {
console.error(`# API Error: HTTP ${response.status}`);
return null;
}
return await response.json();
} catch (error) {
console.error(`# API Error: ${error.message}`);
return null;
}
}
/**
* Fetch source code directly from GitHub as fallback.
*/
async function fetchGithubFallback(component) {
// Try common patterns for component paths
const patterns = [
`packages/react/src/components/${component.toLowerCase()}/${component.toLowerCase()}.tsx`,
`packages/react/src/components/${component.toLowerCase()}/index.tsx`,
];
for (const path of patterns) {
const url = `${GITHUB_RAW_BASE}/${path}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (response.ok) {
const content = await response.text();
return {
component,
filePath: path,
githubUrl: `https://github.com/heroui-inc/heroui/blob/v3/${path}`,
source: "fallback",
sourceCode: content,
};
}
} catch {
continue;
}
}
return {component, error: `Failed to fetch source for ${component}`};
}
/**
* Main function to get source code for specified components.
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Usage: node get_source.mjs <Component1> [Component2] ...");
console.error("Example: node get_source.mjs Button Accordion");
process.exit(1);
}
const components = args;
// Try API first
console.error(`# Fetching source code for: ${components.join(", ")}...`);
const data = await fetchApi("/v1/components/source", "POST", {components});
if (data && data.results) {
for (const result of data.results) {
result.source = "api";
}
// Output results
if (data.results.length === 1) {
const result = data.results[0];
if (result.sourceCode) {
console.log(`// File: ${result.filePath || "unknown"}`);
console.log(`// GitHub: ${result.githubUrl || "unknown"}`);
console.log();
console.log(result.sourceCode);
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
console.log(JSON.stringify(data, null, 2));
}
return;
}
// Fallback to GitHub direct fetch
console.error("# API failed, using GitHub fallback...");
const results = [];
for (const component of components) {
const result = await fetchGithubFallback(component);
results.push(result);
}
if (results.length === 1) {
const result = results[0];
if (result.sourceCode) {
console.log(`// File: ${result.filePath || "unknown"}`);
console.log(`// GitHub: ${result.githubUrl || "unknown"}`);
console.log();
console.log(result.sourceCode);
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
console.log(JSON.stringify({results}, null, 2));
}
}
main();

View File

@@ -0,0 +1,160 @@
#!/usr/bin/env node
/**
* Get CSS styles (BEM classes) for HeroUI v3 components.
*
* Usage:
* node get_styles.mjs Button
* node get_styles.mjs Button Card Chip
*
* Output:
* CSS file content with BEM classes and GitHub URL for each component
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const GITHUB_RAW_BASE = "https://raw.githubusercontent.com/heroui-inc/heroui/refs/heads/v3";
const APP_PARAM = "app=react-skills";
/**
* Fetch data from HeroUI API with app parameter for analytics.
*/
async function fetchApi(endpoint, method = "GET", body = null) {
const separator = endpoint.includes("?") ? "&" : "?";
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
try {
const options = {
headers: {
"Content-Type": "application/json",
"User-Agent": "HeroUI-Skill/1.0",
},
method,
signal: AbortSignal.timeout(30000),
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
if (!response.ok) {
console.error(`# API Error: HTTP ${response.status}`);
return null;
}
return await response.json();
} catch (error) {
console.error(`# API Error: ${error.message}`);
return null;
}
}
/**
* Fetch CSS styles directly from GitHub as fallback.
*/
async function fetchGithubFallback(component) {
// Try common patterns for style paths
const patterns = [
`packages/styles/src/components/${component.toLowerCase()}.css`,
`packages/styles/components/${component.toLowerCase()}.css`,
];
for (const path of patterns) {
const url = `${GITHUB_RAW_BASE}/${path}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (response.ok) {
const content = await response.text();
return {
component,
filePath: path,
githubUrl: `https://github.com/heroui-inc/heroui/blob/v3/${path}`,
source: "fallback",
stylesCode: content,
};
}
} catch {
continue;
}
}
return {component, error: `Failed to fetch styles for ${component}`};
}
/**
* Main function to get CSS styles for specified components.
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Usage: node get_styles.mjs <Component1> [Component2] ...");
console.error("Example: node get_styles.mjs Button Card");
process.exit(1);
}
const components = args;
// Try API first
console.error(`# Fetching styles for: ${components.join(", ")}...`);
const data = await fetchApi("/v1/components/styles", "POST", {components});
if (data && data.results) {
for (const result of data.results) {
result.source = "api";
}
// Output results
if (data.results.length === 1) {
const result = data.results[0];
if (result.stylesCode) {
console.log(`/* File: ${result.filePath || "unknown"} */`);
console.log(`/* GitHub: ${result.githubUrl || "unknown"} */`);
console.log();
console.log(result.stylesCode);
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
console.log(JSON.stringify(data, null, 2));
}
return;
}
// Fallback to GitHub direct fetch
console.error("# API failed, using GitHub fallback...");
const results = [];
for (const component of components) {
const result = await fetchGithubFallback(component);
results.push(result);
}
if (results.length === 1) {
const result = results[0];
if (result.stylesCode) {
console.log(`/* File: ${result.filePath || "unknown"} */`);
console.log(`/* GitHub: ${result.githubUrl || "unknown"} */`);
console.log();
console.log(result.stylesCode);
} else {
console.log(JSON.stringify(result, null, 2));
}
} else {
console.log(JSON.stringify({results}, null, 2));
}
}
main();

View File

@@ -0,0 +1,177 @@
#!/usr/bin/env node
/**
* Get theme variables and design tokens for HeroUI v3.
*
* Usage:
* node get_theme.mjs
*
* Output:
* Theme variables organized by common/light/dark with oklch color format
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const APP_PARAM = "app=react-skills";
// Fallback theme reference when API is unavailable
const FALLBACK_THEME = {
common: {
base: [
{name: "--font-sans", value: "ui-sans-serif, system-ui, sans-serif"},
{name: "--font-mono", value: "ui-monospace, monospace"},
{name: "--radius-sm", value: "0.375rem"},
{name: "--radius-md", value: "0.5rem"},
{name: "--radius-lg", value: "0.75rem"},
{name: "--radius-full", value: "9999px"},
],
calculated: [{name: "--spacing-unit", value: "0.25rem"}],
},
dark: {
semantic: [
{name: "--color-background", value: "oklch(14.5% 0 0)"},
{name: "--color-foreground", value: "oklch(98.4% 0 0)"},
{name: "--color-accent", value: "oklch(55.1% 0.228 264.1)"},
{name: "--color-danger", value: "oklch(63.7% 0.237 25.3)"},
{name: "--color-success", value: "oklch(76.5% 0.177 163.2)"},
{name: "--color-warning", value: "oklch(79.5% 0.184 86.0)"},
],
},
latestVersion: "3.0.0-beta",
light: {
semantic: [
{name: "--color-background", value: "oklch(100% 0 0)"},
{name: "--color-foreground", value: "oklch(14.5% 0 0)"},
{name: "--color-accent", value: "oklch(55.1% 0.228 264.1)"},
{name: "--color-danger", value: "oklch(63.7% 0.237 25.3)"},
{name: "--color-success", value: "oklch(76.5% 0.177 163.2)"},
{name: "--color-warning", value: "oklch(79.5% 0.184 86.0)"},
],
},
note: "This is a fallback. For complete theme variables, ensure the API is accessible.",
source: "fallback",
theme: "default",
};
/**
* Fetch data from HeroUI API with app parameter for analytics.
*/
async function fetchApi(endpoint) {
const separator = endpoint.includes("?") ? "&" : "?";
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
console.error(`# API Error: HTTP ${response.status}`);
return null;
}
return await response.json();
} catch (error) {
console.error(`# API Error: ${error.message}`);
return null;
}
}
/**
* Format theme variables for display.
*/
function formatVariables(variables) {
const lines = [];
for (const variable of variables) {
const name = variable.name || "";
const value = variable.value || "";
const desc = variable.description || "";
if (desc) {
lines.push(` ${name}: ${value}; /* ${desc} */`);
} else {
lines.push(` ${name}: ${value};`);
}
}
return lines.join("\n");
}
/**
* Main function to get theme variables.
*/
async function main() {
console.error("# Fetching theme variables...");
const rawData = await fetchApi("/v1/themes/variables?theme=default");
let data;
let version;
if (!rawData) {
console.error("# API failed, using fallback theme reference...");
data = FALLBACK_THEME;
version = FALLBACK_THEME.latestVersion || "unknown";
} else {
// Handle API response format: { themes: [...], latestVersion: "..." }
if (rawData.themes && rawData.themes.length > 0) {
data = rawData.themes[0]; // Get first theme (default)
version = rawData.latestVersion || rawData.version || "unknown";
} else {
// Direct format
data = rawData;
version = rawData.latestVersion || "unknown";
}
}
// Output as formatted CSS-like structure for readability
console.log("/* HeroUI v3 Theme Variables */");
console.log(`/* Theme: ${data.theme || "default"} */`);
console.log(`/* Version: ${version} */`);
console.log();
// Common variables
if (data.common) {
console.log(":root {");
console.log(" /* Base Variables */");
if (data.common.base) {
console.log(formatVariables(data.common.base));
}
console.log();
console.log(" /* Calculated Variables */");
if (data.common.calculated) {
console.log(formatVariables(data.common.calculated));
}
console.log("}");
console.log();
}
// Light mode
if (data.light) {
console.log(":root, [data-theme='light'] {");
console.log(" /* Light Mode Semantic Variables */");
if (data.light.semantic) {
console.log(formatVariables(data.light.semantic));
}
console.log("}");
console.log();
}
// Dark mode
if (data.dark) {
console.log("[data-theme='dark'] {");
console.log(" /* Dark Mode Semantic Variables */");
if (data.dark.semantic) {
console.log(formatVariables(data.dark.semantic));
}
console.log("}");
}
// Also output raw JSON to stderr for programmatic use
console.error("\n# Raw JSON output:");
console.error(JSON.stringify(rawData || data, null, 2));
}
main();

View File

@@ -0,0 +1,134 @@
#!/usr/bin/env node
/**
* List all available HeroUI v3 components.
*
* Usage:
* node list_components.mjs
*
* Output:
* JSON with components array, latestVersion, and count
*/
const API_BASE = process.env.HEROUI_API_BASE || "https://mcp-api.heroui.com";
const APP_PARAM = "app=react-skills";
const LLMS_TXT_URL = "https://v3.heroui.com/react/llms.txt";
/**
* Fetch data from HeroUI API with app parameter for analytics.
*/
async function fetchApi(endpoint) {
const separator = endpoint.includes("?") ? "&" : "?";
const url = `${API_BASE}${endpoint}${separator}${APP_PARAM}`;
try {
const response = await fetch(url, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
console.error(`HTTP Error ${response.status}: ${response.statusText}`);
return null;
}
return await response.json();
} catch (error) {
console.error(`API Error: ${error.message}`);
return null;
}
}
/**
* Fetch component list from llms.txt fallback URL.
*/
async function fetchFallback() {
try {
const response = await fetch(LLMS_TXT_URL, {
headers: {"User-Agent": "HeroUI-Skill/1.0"},
signal: AbortSignal.timeout(30000),
});
if (!response.ok) {
return null;
}
const content = await response.text();
// Parse markdown to extract component names from pattern: - [ComponentName](url)
// Look for links under the Components section (### Components)
const components = [];
let inComponentsSection = false;
for (const line of content.split("\n")) {
// Check if we're entering the Components section (uses ### header)
if (line.trim() === "### Components") {
inComponentsSection = true;
continue;
}
// Check if we're leaving the Components section (another ### header)
if (inComponentsSection && line.trim().startsWith("### ")) {
break;
}
// Extract component name from markdown link pattern
// Match: - [ComponentName](https://v3.heroui.com/docs/react/components/component-name)
// Skip "All Components" which links to /components without a specific component
if (inComponentsSection) {
const match = line.match(
/^\s*-\s*\[([^\]]+)\]\(https:\/\/v3\.heroui\.com\/docs\/react\/components\/[a-z]/,
);
if (match) {
components.push(match[1]);
}
}
}
if (components.length > 0) {
console.error(`# Using fallback: ${LLMS_TXT_URL}`);
return {
components: components.sort(),
count: components.length,
latestVersion: "unknown",
};
}
return null;
} catch (error) {
console.error(`Fallback Error: ${error.message}`);
return null;
}
}
/**
* Main function to list all available HeroUI v3 components.
*/
async function main() {
let data = await fetchApi("/v1/components");
// Check if API returned valid data with components
if (!data || !data.components || data.components.length === 0) {
console.error("# API returned no components, trying fallback...");
data = await fetchFallback();
}
if (!data || !data.components || data.components.length === 0) {
console.error("Error: Failed to fetch component list from API and fallback");
process.exit(1);
}
// Output formatted JSON
console.log(JSON.stringify(data, null, 2));
// Print summary to stderr for human readability
console.error(
`\n# Found ${data.components.length} components (${data.latestVersion || "unknown"})`,
);
}
main();

10
skills-lock.json Normal file
View File

@@ -0,0 +1,10 @@
{
"version": 1,
"skills": {
"heroui-react": {
"source": "heroui-inc/heroui",
"sourceType": "github",
"computedHash": "927aea70da0c060f930998e4c7373f790b87125798698e89f6431de689690f23"
}
}
}