#!/usr/bin/env node /* eslint-disable no-console */ import path from "path"; import {fileURLToPath} from "url"; import fs from "fs-extra"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const rootDir = path.resolve(__dirname, ".."); async function addComponent(componentName) { if (!componentName) { console.error("āŒ Please provide a component name"); console.log("Usage: node scripts/add-component.mjs "); process.exit(1); } // Convert to kebab-case if needed const kebabName = componentName.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(); const componentDir = path.join(rootDir, "src/components", kebabName); if (await fs.pathExists(componentDir)) { console.error(`āŒ Component "${kebabName}" already exists`); process.exit(1); } console.log(`šŸ“¦ Creating new component: ${kebabName}`); // Create component directory await fs.ensureDir(componentDir); // Create component files const componentContent = `"use client"; import React from "react"; import {${componentName}Variants, type ${componentName}VariantProps} from "./${kebabName}.styles"; export interface ${componentName}Props extends ${componentName}VariantProps { children?: React.ReactNode; className?: string; } export const ${componentName} = React.forwardRef( ({children, className, ...props}, ref) => { return (
{children}
); } ); ${componentName}.displayName = "HeroUI.${componentName}"; `; const stylesContent = `import type {VariantProps} from "tailwind-variants"; import {tv} from "tailwind-variants"; export const ${componentName}Variants = tv({ base: "inline-flex items-center justify-center", variants: { variant: { default: "bg-background text-foreground", primary: "bg-primary text-primary-foreground", }, size: { sm: "h-8 px-3 text-sm", md: "h-10 px-4", lg: "h-12 px-6 text-lg", }, }, defaultVariants: { variant: "default", size: "md", }, }); export type ${componentName}VariantProps = VariantProps; `; const storiesContent = `import type {Meta, StoryObj} from "@storybook/react"; import {${componentName}} from "./${kebabName}"; const meta: Meta = { title: "Components/${componentName}", component: ${componentName}, tags: ["autodocs"], }; export default meta; type Story = StoryObj; export const Default: Story = { args: { children: "${componentName} Component", }, }; export const Variants: Story = { render: () => (
<${componentName} variant="default">Default <${componentName} variant="primary">Primary
), }; export const Sizes: Story = { render: () => (
<${componentName} size="sm">Small <${componentName} size="md">Medium <${componentName} size="lg">Large
), }; `; const indexContent = `export {${componentName}, type ${componentName}Props} from "./${kebabName}"; export {${componentName}Variants, type ${componentName}VariantProps} from "./${kebabName}.styles"; `; // Write files await fs.writeFile(path.join(componentDir, `${kebabName}.tsx`), componentContent); await fs.writeFile(path.join(componentDir, `${kebabName}.styles.ts`), stylesContent); await fs.writeFile(path.join(componentDir, `${kebabName}.stories.tsx`), storiesContent); await fs.writeFile(path.join(componentDir, "index.ts"), indexContent); // Update components index const componentsIndexPath = path.join(rootDir, "src/components/index.ts"); const componentsIndex = await fs.readFile(componentsIndexPath, "utf-8"); // Find the right place to insert (alphabetically) const lines = componentsIndex.split("\n"); const exportLine = `export * from "./${kebabName}";`; let inserted = false; for (let i = 0; i < lines.length; i++) { if (lines[i].startsWith('export * from "./') && lines[i] > exportLine) { lines.splice(i, 0, exportLine); inserted = true; break; } } if (!inserted) { // Insert before the icons section or at the end const iconsIndex = lines.findIndex((line) => line.includes("Icons")); if (iconsIndex > 0) { lines.splice(iconsIndex - 1, 0, exportLine); } else { lines.push(exportLine); } } await fs.writeFile(componentsIndexPath, lines.join("\n")); console.log(`āœ… Component "${kebabName}" created successfully!`); console.log("\nšŸ“ Created files:"); console.log(` - src/components/${kebabName}/${kebabName}.tsx`); console.log(` - src/components/${kebabName}/${kebabName}.styles.ts`); console.log(` - src/components/${kebabName}/${kebabName}.stories.tsx`); console.log(` - src/components/${kebabName}/index.ts`); console.log("\nāœļø Updated src/components/index.ts"); console.log("\nšŸŽÆ Next steps:"); console.log(" 1. Run 'pnpm build' to generate the export in package.json"); console.log(" 2. The component will be available as:"); console.log(` - import { ${componentName} } from "@heroui/react"`); console.log(` - import { ${componentName} } from "@heroui/react/${kebabName}"`); } // Get component name from command line const componentName = process.argv[2]; addComponent(componentName).catch((error) => { console.error("āŒ Failed to create component:", error); process.exit(1); });