initial commit
This commit is contained in:
21
.agents/skills/heroui-native/LICENSE.txt
Normal file
21
.agents/skills/heroui-native/LICENSE.txt
Normal 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.
|
||||
243
.agents/skills/heroui-native/SKILL.md
Normal file
243
.agents/skills/heroui-native/SKILL.md
Normal file
@@ -0,0 +1,243 @@
|
||||
---
|
||||
name: heroui-native
|
||||
description: "HeroUI Native component library for React Native (Tailwind v4 via Uniwind). Use when working with HeroUI Native components, installing HeroUI Native, customizing themes, or accessing component documentation. Keywords: HeroUI Native, heroui-native, React Native UI, Uniwind."
|
||||
metadata:
|
||||
author: heroui
|
||||
version: "1.0.0"
|
||||
---
|
||||
|
||||
# HeroUI Native Development Guide
|
||||
|
||||
HeroUI Native is a component library built on **Uniwind (Tailwind CSS for React Native)** and **React Native**, providing accessible, customizable UI components for mobile applications.
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: Native Only - Do Not Use Web Patterns
|
||||
|
||||
**This guide is for HeroUI Native ONLY.** Do NOT use any prior knowledge of HeroUI React (web) patterns.
|
||||
|
||||
### What Changed in Native
|
||||
|
||||
| Feature | React (Web) | Native (Mobile) |
|
||||
| ------------ | -------------------- | ----------------------------------- |
|
||||
| **Styling** | Tailwind CSS v4 | Uniwind (Tailwind for React Native) |
|
||||
| **Colors** | oklch format | HSL format |
|
||||
| **Package** | `@heroui/react@beta` | `heroui-native` |
|
||||
| **Platform** | Web browsers | iOS & Android |
|
||||
|
||||
### WRONG (React web patterns)
|
||||
|
||||
```tsx
|
||||
// DO NOT DO THIS - React web pattern
|
||||
import { Button } from "@heroui/react";
|
||||
import "./styles.css"; // CSS files don't work in React Native
|
||||
|
||||
<Button className="bg-blue-500">Click me</Button>;
|
||||
```
|
||||
|
||||
### CORRECT (Native patterns)
|
||||
|
||||
```tsx
|
||||
// DO THIS - Native pattern (Uniwind, React Native components)
|
||||
import { Button } from "heroui-native";
|
||||
|
||||
<Button variant="primary" onPress={() => console.log("Pressed!")}>
|
||||
Click me
|
||||
</Button>;
|
||||
```
|
||||
|
||||
**Always fetch Native docs before implementing.** Do not assume React web patterns work.
|
||||
|
||||
---
|
||||
|
||||
## Core Principles
|
||||
|
||||
- Semantic variants (`primary`, `secondary`, `tertiary`) over visual descriptions
|
||||
- Composition over configuration (compound components)
|
||||
- Theme variables with HSL color format
|
||||
- React Native StyleSheet patterns with Uniwind utilities
|
||||
|
||||
---
|
||||
|
||||
## 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 theme variables
|
||||
node scripts/get_theme.mjs
|
||||
|
||||
# Get non-component docs (guides, releases)
|
||||
node scripts/get_docs.mjs /docs/native/getting-started/theming
|
||||
```
|
||||
|
||||
### Direct MDX URLs
|
||||
|
||||
Component docs: `https://v3.heroui.com/docs/native/components/{component-name}.mdx`
|
||||
|
||||
Examples:
|
||||
|
||||
- Button: `https://v3.heroui.com/docs/native/components/button.mdx`
|
||||
- Dialog: `https://v3.heroui.com/docs/native/components/dialog.mdx`
|
||||
- TextField: `https://v3.heroui.com/docs/native/components/text-field.mdx`
|
||||
|
||||
Getting started guides: `https://v3.heroui.com/docs/native/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 Native is currently in BETA.
|
||||
|
||||
### Quick Install
|
||||
|
||||
```bash
|
||||
npm i heroui-native
|
||||
```
|
||||
|
||||
### Required Peer Dependencies
|
||||
|
||||
```bash
|
||||
npm i react-native-reanimated react-native-gesture-handler react-native-safe-area-context @gorhom/bottom-sheet react-native-svg react-native-worklets tailwind-merge tailwind-variants
|
||||
```
|
||||
|
||||
### Framework Setup (Expo - Recommended)
|
||||
|
||||
1. **Install dependencies:**
|
||||
|
||||
```bash
|
||||
npx create-expo-app MyApp
|
||||
cd MyApp
|
||||
npm i heroui-native uniwind tailwindcss
|
||||
npm i react-native-reanimated react-native-gesture-handler react-native-safe-area-context @gorhom/bottom-sheet react-native-svg react-native-worklets tailwind-merge tailwind-variants
|
||||
```
|
||||
|
||||
2. **Create `global.css`:**
|
||||
|
||||
```css
|
||||
@import "tailwindcss";
|
||||
@import "uniwind";
|
||||
@import "heroui-native/styles";
|
||||
|
||||
@source "./node_modules/heroui-native/lib";
|
||||
```
|
||||
|
||||
3. **Wrap app with providers:**
|
||||
|
||||
```tsx
|
||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||
import { HeroUINativeProvider } from "heroui-native";
|
||||
import "./global.css";
|
||||
|
||||
export default function Layout() {
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<HeroUINativeProvider>
|
||||
<App />
|
||||
</HeroUINativeProvider>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Critical Setup Requirements
|
||||
|
||||
1. **Uniwind is Required** - HeroUI Native uses Uniwind (Tailwind CSS for React Native)
|
||||
2. **HeroUINativeProvider Required** - Wrap your app with `HeroUINativeProvider`
|
||||
3. **GestureHandlerRootView Required** - Wrap with `GestureHandlerRootView` from react-native-gesture-handler
|
||||
4. **Use Compound Components** - Components use compound structure (e.g., `Card.Header`, `Card.Body`)
|
||||
5. **Use onPress, not onClick** - React Native uses `onPress` event handlers
|
||||
6. **Platform-Specific Code** - Use `Platform.OS` for iOS/Android differences
|
||||
|
||||
---
|
||||
|
||||
## Component Patterns
|
||||
|
||||
HeroUI Native 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.Body>{/* Content */}</Card.Body>
|
||||
<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 |
|
||||
| `danger-soft` | Soft destructive actions | Less prominent |
|
||||
| `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 Native uses CSS variables via Tailwind/Uniwind for theming. Theme colors are defined in `global.css`:
|
||||
|
||||
```css
|
||||
@theme {
|
||||
--color-accent: hsl(260, 100%, 70%);
|
||||
--color-accent-foreground: hsl(0, 0%, 100%);
|
||||
}
|
||||
```
|
||||
|
||||
**Get current theme variables:**
|
||||
|
||||
```bash
|
||||
node scripts/get_theme.mjs
|
||||
```
|
||||
|
||||
**Access theme colors programmatically:**
|
||||
|
||||
```tsx
|
||||
import { useThemeColor } from "heroui-native";
|
||||
|
||||
const accentColor = useThemeColor("accent");
|
||||
```
|
||||
|
||||
**Theme switching (Light/Dark Mode):**
|
||||
|
||||
```tsx
|
||||
import { Uniwind, useUniwind } from "uniwind";
|
||||
|
||||
const { theme } = useUniwind();
|
||||
Uniwind.setTheme(theme === "light" ? "dark" : "light");
|
||||
```
|
||||
|
||||
For detailed theming, fetch: `https://v3.heroui.com/docs/native/getting-started/theming.mdx`
|
||||
157
.agents/skills/heroui-native/scripts/get_component_docs.mjs
Executable file
157
.agents/skills/heroui-native/scripts/get_component_docs.mjs
Executable file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Get complete component documentation (MDX) for HeroUI Native 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_NATIVE_API_BASE || "https://native-mcp-api.heroui.com";
|
||||
const FALLBACK_BASE = "https://v3.heroui.com";
|
||||
const APP_PARAM = "app=native-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 Native 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-Native-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/native/components/${kebabName}.mdx`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: { "User-Agent": "HeroUI-Native-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 Native 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();
|
||||
154
.agents/skills/heroui-native/scripts/get_docs.mjs
Executable file
154
.agents/skills/heroui-native/scripts/get_docs.mjs
Executable file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Get non-component HeroUI Native documentation (guides, theming, releases).
|
||||
*
|
||||
* Usage:
|
||||
* node get_docs.mjs /docs/native/getting-started/theming
|
||||
* node get_docs.mjs /docs/native/releases/beta-12
|
||||
*
|
||||
* Output:
|
||||
* MDX documentation content
|
||||
*
|
||||
* Note: For component docs, use get_component_docs.mjs instead.
|
||||
*/
|
||||
|
||||
const API_BASE = process.env.HEROUI_NATIVE_API_BASE || "https://native-mcp-api.heroui.com";
|
||||
const FALLBACK_BASE = "https://v3.heroui.com";
|
||||
const APP_PARAM = "app=native-skills";
|
||||
|
||||
/**
|
||||
* Fetch documentation from HeroUI Native API.
|
||||
* Uses v1 endpoint pattern: /v1/docs/:path
|
||||
*/
|
||||
async function fetchApi(path) {
|
||||
// The v1 API expects path without /docs/ prefix
|
||||
// Input: /docs/native/getting-started/theming
|
||||
// API expects: native/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-Native-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-Native-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/native/getting-started/theming");
|
||||
console.error();
|
||||
console.error("Available paths include:");
|
||||
console.error(" /docs/native/getting-started/theming");
|
||||
console.error(" /docs/native/getting-started/colors");
|
||||
console.error(" /docs/native/getting-started/styling");
|
||||
console.error(" /docs/native/releases/beta-12");
|
||||
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}`);
|
||||
}
|
||||
|
||||
// Validate Native path
|
||||
if (!path.startsWith("/docs/native/")) {
|
||||
console.error("# Warning: Native documentation paths should start with /docs/native/");
|
||||
console.error(`# Provided path: ${path}`);
|
||||
}
|
||||
|
||||
console.error(`# Fetching Native 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();
|
||||
222
.agents/skills/heroui-native/scripts/get_theme.mjs
Executable file
222
.agents/skills/heroui-native/scripts/get_theme.mjs
Executable file
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Get theme variables and design tokens for HeroUI Native.
|
||||
*
|
||||
* Usage:
|
||||
* node get_theme.mjs
|
||||
*
|
||||
* Output:
|
||||
* Theme variables organized by light/dark with HSL color format
|
||||
*/
|
||||
|
||||
const API_BASE = process.env.HEROUI_NATIVE_API_BASE || "https://native-mcp-api.heroui.com";
|
||||
const APP_PARAM = "app=native-skills";
|
||||
|
||||
// Fallback theme reference when API is unavailable
|
||||
const FALLBACK_THEME = {
|
||||
borderRadius: {
|
||||
full: 9999,
|
||||
lg: 12,
|
||||
md: 8,
|
||||
sm: 6,
|
||||
},
|
||||
dark: {
|
||||
colors: [
|
||||
{
|
||||
category: "base",
|
||||
name: "--color-background",
|
||||
value: "hsl(0, 0%, 14.5%)",
|
||||
},
|
||||
{
|
||||
category: "semantic",
|
||||
name: "--color-foreground",
|
||||
value: "hsl(0, 0%, 98.4%)",
|
||||
},
|
||||
{
|
||||
category: "semantic",
|
||||
name: "--color-accent",
|
||||
value: "hsl(264.1, 100%, 55.1%)",
|
||||
},
|
||||
{
|
||||
category: "status",
|
||||
name: "--color-danger",
|
||||
value: "hsl(25.3, 100%, 63.7%)",
|
||||
},
|
||||
{
|
||||
category: "status",
|
||||
name: "--color-success",
|
||||
value: "hsl(163.2, 100%, 76.5%)",
|
||||
},
|
||||
{
|
||||
category: "status",
|
||||
name: "--color-warning",
|
||||
value: "hsl(86.0, 100%, 79.5%)",
|
||||
},
|
||||
],
|
||||
},
|
||||
latestVersion: "beta",
|
||||
light: {
|
||||
colors: [
|
||||
{
|
||||
category: "base",
|
||||
name: "--color-background",
|
||||
value: "hsl(0, 0%, 100%)",
|
||||
},
|
||||
{
|
||||
category: "semantic",
|
||||
name: "--color-foreground",
|
||||
value: "hsl(285.89, 5.9%, 21.03%)",
|
||||
},
|
||||
{
|
||||
category: "semantic",
|
||||
name: "--color-accent",
|
||||
value: "hsl(253.83, 100%, 62.04%)",
|
||||
},
|
||||
{
|
||||
category: "status",
|
||||
name: "--color-danger",
|
||||
value: "hsl(25.74, 100%, 65.32%)",
|
||||
},
|
||||
{
|
||||
category: "status",
|
||||
name: "--color-success",
|
||||
value: "hsl(150.81, 100%, 73.29%)",
|
||||
},
|
||||
{
|
||||
category: "status",
|
||||
name: "--color-warning",
|
||||
value: "hsl(72.33, 100%, 78.19%)",
|
||||
},
|
||||
],
|
||||
},
|
||||
note: "This is a fallback. For complete theme variables, ensure the API is accessible.",
|
||||
opacity: {
|
||||
disabled: 0.4,
|
||||
hover: 0.8,
|
||||
pressed: 0.6,
|
||||
},
|
||||
source: "fallback",
|
||||
theme: "default",
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch data from HeroUI Native 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-Native-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 colors grouped by category.
|
||||
*/
|
||||
function formatColors(colors) {
|
||||
const grouped = {};
|
||||
|
||||
for (const color of colors) {
|
||||
const category = color.category || "semantic";
|
||||
|
||||
if (!grouped[category]) {
|
||||
grouped[category] = [];
|
||||
}
|
||||
grouped[category].push(color);
|
||||
}
|
||||
|
||||
const lines = [];
|
||||
|
||||
for (const [category, tokens] of Object.entries(grouped)) {
|
||||
lines.push(` /* ${category.charAt(0).toUpperCase() + category.slice(1)} Colors */`);
|
||||
for (const token of tokens) {
|
||||
const name = token.name || "";
|
||||
const value = token.value || "";
|
||||
|
||||
lines.push(` ${name}: ${value};`);
|
||||
}
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to get theme variables.
|
||||
*/
|
||||
async function main() {
|
||||
console.error("# Fetching Native 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
|
||||
data = rawData;
|
||||
version = rawData.latestVersion || "unknown";
|
||||
}
|
||||
|
||||
// Output as formatted structure for readability
|
||||
console.log("/* HeroUI Native Theme Variables */");
|
||||
console.log(`/* Theme: ${data.theme || "default"} */`);
|
||||
console.log(`/* Version: ${version} */`);
|
||||
console.log();
|
||||
|
||||
// Light mode colors
|
||||
if (data.light && data.light.colors) {
|
||||
console.log("/* Light Mode Colors */");
|
||||
console.log(formatColors(data.light.colors));
|
||||
}
|
||||
|
||||
// Dark mode colors
|
||||
if (data.dark && data.dark.colors) {
|
||||
console.log("/* Dark Mode Colors */");
|
||||
console.log(formatColors(data.dark.colors));
|
||||
}
|
||||
|
||||
// Border radius
|
||||
if (data.borderRadius) {
|
||||
console.log("/* Border Radius */");
|
||||
for (const [key, value] of Object.entries(data.borderRadius)) {
|
||||
console.log(` --radius-${key}: ${value};`);
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
// Opacity
|
||||
if (data.opacity) {
|
||||
console.log("/* Opacity */");
|
||||
for (const [key, value] of Object.entries(data.opacity)) {
|
||||
console.log(` --opacity-${key}: ${value};`);
|
||||
}
|
||||
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();
|
||||
134
.agents/skills/heroui-native/scripts/list_components.mjs
Executable file
134
.agents/skills/heroui-native/scripts/list_components.mjs
Executable file
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* List all available HeroUI Native components.
|
||||
*
|
||||
* Usage:
|
||||
* node list_components.mjs
|
||||
*
|
||||
* Output:
|
||||
* JSON with components array, latestVersion, and count
|
||||
*/
|
||||
|
||||
const API_BASE = process.env.HEROUI_NATIVE_API_BASE || "https://native-mcp-api.heroui.com";
|
||||
const APP_PARAM = "app=native-skills";
|
||||
const LLMS_TXT_URL = "https://v3.heroui.com/native/llms.txt";
|
||||
|
||||
/**
|
||||
* Fetch data from HeroUI Native 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-Native-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-Native-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/native/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\/native\/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 Native 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} Native components (${data.latestVersion || "unknown"})`,
|
||||
);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user