Skip to main content
Glama
templates.ts14.7 kB
/** * Component Templates * Pre-built templates that follow all component.build rules * * FRAMEWORK: React + TypeScript * * These templates are specifically for React/TypeScript projects. * The components.build specification is framework-agnostic, but these * templates implement the patterns in React. * * Future: Add Vue, Svelte, Angular templates as needed. */ export interface TemplateOptions { name: string; element?: string; hasVariants?: boolean; variants?: string[]; sizes?: string[]; isComposable?: boolean; hasState?: boolean; stateType?: 'open' | 'active' | 'value'; } /** * Generate a basic component following all rules */ export function generateBasicComponent(options: TemplateOptions): string { const { name, element = 'div', hasVariants = false, variants = ['default', 'secondary'], sizes = ['default', 'sm', 'lg'], } = options; const propsName = `${name}Props`; const variantsName = `${name.toLowerCase()}Variants`; if (hasVariants) { return `import { forwardRef } from 'react'; import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils'; const ${variantsName} = cva( // Base styles 'inline-flex items-center justify-center', { variants: { variant: { ${variants.map(v => ` ${v}: '',`).join('\n')} }, size: { ${sizes.map(s => ` ${s}: '',`).join('\n')} }, }, defaultVariants: { variant: '${variants[0]}', size: '${sizes[0]}', }, } ); export type ${propsName} = React.ComponentProps<'${element}'> & VariantProps<typeof ${variantsName}>; export const ${name} = forwardRef<HTML${element.charAt(0).toUpperCase() + element.slice(1)}Element, ${propsName}>( ({ className, variant, size, ...props }, ref) => ( <${element} ref={ref} data-slot="${name.toLowerCase()}" className={cn(${variantsName}({ variant, size, className }))} {...props} /> ) ); ${name}.displayName = '${name}'; `; } return `import { forwardRef } from 'react'; import { cn } from '@/lib/utils'; export type ${propsName} = React.ComponentProps<'${element}'>; export const ${name} = forwardRef<HTML${element.charAt(0).toUpperCase() + element.slice(1)}Element, ${propsName}>( ({ className, ...props }, ref) => ( <${element} ref={ref} data-slot="${name.toLowerCase()}" className={cn('', className)} {...props} /> ) ); ${name}.displayName = '${name}'; `; } /** * Generate a composable component with sub-components (Root, Trigger, Content pattern) */ export function generateComposableComponent(options: TemplateOptions): string { const { name, hasState = true, stateType = 'open' } = options; const stateProp = stateType === 'value' ? 'value' : stateType === 'active' ? 'active' : 'open'; const setStateProp = stateType === 'value' ? 'setValue' : stateType === 'active' ? 'setActive' : 'setOpen'; const stateValues = stateType === 'open' ? "'open' | 'closed'" : stateType === 'active' ? "'active' | 'inactive'" : 'T'; return `'use client'; import { createContext, forwardRef, useContext, type ReactNode, } from 'react'; import { useControllableState } from '@radix-ui/react-use-controllable-state'; import { cn } from '@/lib/utils'; /* ------------------------------------------------------------------------------------------------- * Context * -------------------------------------------------------------------------------------------------*/ type ${name}ContextValue = { ${stateProp}: boolean; ${setStateProp}: (value: boolean) => void; }; const ${name}Context = createContext<${name}ContextValue | undefined>(undefined); function use${name}Context() { const context = useContext(${name}Context); if (!context) { throw new Error('${name} components must be used within a ${name}.Root'); } return context; } /* ------------------------------------------------------------------------------------------------- * Root * -------------------------------------------------------------------------------------------------*/ export type ${name}RootProps = React.ComponentProps<'div'> & { /** Controlled ${stateProp} state */ ${stateProp}?: boolean; /** Default ${stateProp} state for uncontrolled usage */ default${stateProp.charAt(0).toUpperCase() + stateProp.slice(1)}?: boolean; /** Callback when ${stateProp} state changes */ on${stateProp.charAt(0).toUpperCase() + stateProp.slice(1)}Change?: (${stateProp}: boolean) => void; children: ReactNode; }; export const Root = forwardRef<HTMLDivElement, ${name}RootProps>( ( { ${stateProp}: controlledState, default${stateProp.charAt(0).toUpperCase() + stateProp.slice(1)} = false, on${stateProp.charAt(0).toUpperCase() + stateProp.slice(1)}Change, className, children, ...props }, ref ) => { const [${stateProp}, ${setStateProp}] = useControllableState({ prop: controlledState, defaultProp: default${stateProp.charAt(0).toUpperCase() + stateProp.slice(1)}, onChange: on${stateProp.charAt(0).toUpperCase() + stateProp.slice(1)}Change, }); return ( <${name}Context.Provider value={{ ${stateProp}: ${stateProp} ?? false, ${setStateProp} }}> <div ref={ref} data-slot="${name.toLowerCase()}" data-state={${stateProp} ? '${stateType === 'open' ? 'open' : 'active'}' : '${stateType === 'open' ? 'closed' : 'inactive'}'} className={cn('', className)} {...props} > {children} </div> </${name}Context.Provider> ); } ); Root.displayName = '${name}.Root'; /* ------------------------------------------------------------------------------------------------- * Trigger * -------------------------------------------------------------------------------------------------*/ export type ${name}TriggerProps = React.ComponentProps<'button'>; export const Trigger = forwardRef<HTMLButtonElement, ${name}TriggerProps>( ({ className, ...props }, ref) => { const { ${stateProp}, ${setStateProp} } = use${name}Context(); return ( <button ref={ref} type="button" data-slot="${name.toLowerCase()}-trigger" data-state={${stateProp} ? '${stateType === 'open' ? 'open' : 'active'}' : '${stateType === 'open' ? 'closed' : 'inactive'}'} aria-expanded={${stateProp}} onClick={() => ${setStateProp}(!${stateProp})} className={cn('', className)} {...props} /> ); } ); Trigger.displayName = '${name}.Trigger'; /* ------------------------------------------------------------------------------------------------- * Content * -------------------------------------------------------------------------------------------------*/ export type ${name}ContentProps = React.ComponentProps<'div'>; export const Content = forwardRef<HTMLDivElement, ${name}ContentProps>( ({ className, ...props }, ref) => { const { ${stateProp} } = use${name}Context(); if (!${stateProp}) return null; return ( <div ref={ref} data-slot="${name.toLowerCase()}-content" data-state="${stateType === 'open' ? 'open' : 'active'}" className={cn('', className)} {...props} /> ); } ); Content.displayName = '${name}.Content'; /* ------------------------------------------------------------------------------------------------- * Exports * -------------------------------------------------------------------------------------------------*/ export const ${name} = { Root, Trigger, Content, }; `; } /** * Generate a button component with full accessibility */ export function generateButtonComponent(): string { return `import { forwardRef } from 'react'; import { Slot } from '@radix-ui/react-slot'; import { cva, type VariantProps } from 'class-variance-authority'; import { cn } from '@/lib/utils'; const buttonVariants = cva( 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', { variants: { variant: { default: 'bg-primary text-primary-foreground hover:bg-primary/90', destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline', }, size: { default: 'h-10 px-4 py-2', sm: 'h-9 rounded-md px-3', lg: 'h-11 rounded-md px-8', icon: 'h-10 w-10', }, }, defaultVariants: { variant: 'default', size: 'default', }, } ); export type ButtonProps = React.ComponentProps<'button'> & VariantProps<typeof buttonVariants> & { /** Render as child element instead of button */ asChild?: boolean; }; export const Button = forwardRef<HTMLButtonElement, ButtonProps>( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : 'button'; return ( <Comp ref={ref} type={asChild ? undefined : 'button'} data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} /> ); } ); Button.displayName = 'Button'; `; } /** * Generate a card component with composable sub-components */ export function generateCardComponent(): string { return `import { forwardRef } from 'react'; import { cn } from '@/lib/utils'; /* ------------------------------------------------------------------------------------------------- * Card Root * -------------------------------------------------------------------------------------------------*/ export type CardProps = React.ComponentProps<'div'>; export const Card = forwardRef<HTMLDivElement, CardProps>( ({ className, ...props }, ref) => ( <div ref={ref} data-slot="card" className={cn( 'rounded-lg border bg-card text-card-foreground shadow-sm', className )} {...props} /> ) ); Card.displayName = 'Card'; /* ------------------------------------------------------------------------------------------------- * Card Header * -------------------------------------------------------------------------------------------------*/ export type CardHeaderProps = React.ComponentProps<'div'>; export const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>( ({ className, ...props }, ref) => ( <div ref={ref} data-slot="card-header" className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} /> ) ); CardHeader.displayName = 'CardHeader'; /* ------------------------------------------------------------------------------------------------- * Card Title * -------------------------------------------------------------------------------------------------*/ export type CardTitleProps = React.ComponentProps<'h3'>; export const CardTitle = forwardRef<HTMLHeadingElement, CardTitleProps>( ({ className, ...props }, ref) => ( <h3 ref={ref} data-slot="card-title" className={cn( 'text-2xl font-semibold leading-none tracking-tight', className )} {...props} /> ) ); CardTitle.displayName = 'CardTitle'; /* ------------------------------------------------------------------------------------------------- * Card Description * -------------------------------------------------------------------------------------------------*/ export type CardDescriptionProps = React.ComponentProps<'p'>; export const CardDescription = forwardRef<HTMLParagraphElement, CardDescriptionProps>( ({ className, ...props }, ref) => ( <p ref={ref} data-slot="card-description" className={cn('text-sm text-muted-foreground', className)} {...props} /> ) ); CardDescription.displayName = 'CardDescription'; /* ------------------------------------------------------------------------------------------------- * Card Content * -------------------------------------------------------------------------------------------------*/ export type CardContentProps = React.ComponentProps<'div'>; export const CardContent = forwardRef<HTMLDivElement, CardContentProps>( ({ className, ...props }, ref) => ( <div ref={ref} data-slot="card-content" className={cn('p-6 pt-0', className)} {...props} /> ) ); CardContent.displayName = 'CardContent'; /* ------------------------------------------------------------------------------------------------- * Card Footer * -------------------------------------------------------------------------------------------------*/ export type CardFooterProps = React.ComponentProps<'div'>; export const CardFooter = forwardRef<HTMLDivElement, CardFooterProps>( ({ className, ...props }, ref) => ( <div ref={ref} data-slot="card-footer" className={cn('flex items-center p-6 pt-0', className)} {...props} /> ) ); CardFooter.displayName = 'CardFooter'; `; } /** * Generate an input component with accessibility */ export function generateInputComponent(): string { return `import { forwardRef } from 'react'; import { cn } from '@/lib/utils'; export type InputProps = React.ComponentProps<'input'>; export const Input = forwardRef<HTMLInputElement, InputProps>( ({ className, type = 'text', ...props }, ref) => ( <input ref={ref} type={type} data-slot="input" className={cn( 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background', 'file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground', 'placeholder:text-muted-foreground', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', 'disabled:cursor-not-allowed disabled:opacity-50', className )} {...props} /> ) ); Input.displayName = 'Input'; `; } export const TEMPLATES = { basic: generateBasicComponent, composable: generateComposableComponent, button: generateButtonComponent, card: generateCardComponent, input: generateInputComponent, };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/audreyui/components-build-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server