8.8 KiB
8.8 KiB
Create GlitchComponent
Create a new GlitchComponent project that conforms to the standardized plugin architecture for Glitch University.
Arguments
$ARGUMENTS- Component name (e.g., "matrix-rain", "particle-system")
Instructions
When creating a new GlitchComponent:
-
If no directory specified, create in a sibling directory:
../glitch-components/$ARGUMENTS/ -
Create the full project structure:
$ARGUMENTS/ ├── src/ │ ├── index.tsx # Main export with metadata │ ├── Component.tsx # Component implementation │ ├── styles.module.css # Scoped CSS styles │ ├── types.ts # TypeScript interfaces │ └── dev.tsx # Leva dev harness ├── vite.config.ts ├── package.json ├── tsconfig.json └── index.html -
After creation, explain how to:
- Run
npm install && npm run devfor local development - Build with
npm run build - Deploy output to host app
- Run
Architecture Principles
- Build as Library: Use Vite Library Mode to output a self-contained bundle
- CSS Isolation: Use CSS Modules to prevent style bleeding
- Inversion of Control: Accept props for all configurable parameters
- Local Dev Support: Use Leva controls during development, props in production
Required TypeScript Interfaces
// src/types.ts
export interface GlitchComponentConfig {
id: string;
name: string;
version: string;
params: Record<string, unknown>;
}
export interface GlitchComponentProps {
config: GlitchComponentConfig;
onComplete: (result: GlitchComponentResult) => void;
onProgress?: (percent: number) => void;
theme?: GlitchTheme;
className?: string;
}
export interface GlitchComponentResult {
success: boolean;
score?: number;
data?: unknown;
error?: string;
}
export interface GlitchTheme {
primary: string; // #6366f1
accent: string; // #22d3ee
bg: string; // #0a0a0f
bgSecondary: string; // #12121a
text: string; // #e8e8ec
textMuted: string; // #9999a8
border: string; // #2a2a3a
}
export interface GlitchComponentMetadata {
name: string;
displayName: string;
version: string;
paramSchema: ParamSchema;
defaultParams: Record<string, unknown>;
}
export interface ParamSchema {
[key: string]: {
type: 'number' | 'string' | 'boolean' | 'color' | 'select' | 'range';
label?: string;
description?: string;
default: unknown;
options?: { value: string | number; label: string }[];
min?: number;
max?: number;
step?: number;
};
}
Main Export Structure
// src/index.tsx
import Component from './Component';
import type { GlitchComponentMetadata } from './types';
export default Component;
export const metadata: GlitchComponentMetadata = {
name: 'my-component',
displayName: 'My Component',
version: '1.0.0',
paramSchema: {
speed: { type: 'range', label: 'Speed', default: 1.0, min: 0.1, max: 5.0, step: 0.1 },
primaryColor: { type: 'color', label: 'Color', default: '#6366f1' }
},
defaultParams: { speed: 1.0, primaryColor: '#6366f1' }
};
export type { GlitchComponentProps, GlitchComponentConfig, GlitchComponentResult } from './types';
Component Template
// src/Component.tsx
import { useCallback, useEffect, useState } from 'react';
import styles from './styles.module.css';
import type { GlitchComponentProps } from './types';
interface ComponentParams {
speed: number;
primaryColor: string;
}
export default function MyComponent({ config, onComplete, onProgress, theme, className }: GlitchComponentProps) {
const params = config.params as ComponentParams;
const { speed = 1.0, primaryColor } = params;
const handleComplete = useCallback((success: boolean, score?: number) => {
onComplete({ success, score, data: { completedAt: new Date().toISOString() } });
}, [onComplete]);
const cssVars = {
'--gc-primary': primaryColor || theme?.primary || '#6366f1',
'--gc-accent': theme?.accent || '#22d3ee',
'--gc-bg': theme?.bg || '#0a0a0f',
'--gc-text': theme?.text || '#e8e8ec',
} as React.CSSProperties;
return (
<div className={`${styles.container} ${className || ''}`} style={cssVars}>
<h2 className={styles.title}>My GlitchComponent</h2>
<button className={styles.button} onClick={() => handleComplete(true, 100)}>
Complete
</button>
</div>
);
}
CSS Module Template
/* src/styles.module.css */
.container {
background: var(--gc-bg, #0a0a0f);
color: var(--gc-text, #e8e8ec);
padding: 2rem;
border-radius: 12px;
box-sizing: border-box;
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.5;
}
.container *, .container *::before, .container *::after {
box-sizing: inherit;
}
.title {
color: var(--gc-primary, #6366f1);
margin: 0 0 1rem;
font-size: 1.5rem;
font-weight: 600;
}
.button {
background: var(--gc-primary, #6366f1);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-size: 1rem;
cursor: pointer;
transition: opacity 0.2s ease;
}
.button:hover { opacity: 0.9; }
.button:disabled { opacity: 0.5; cursor: not-allowed; }
Vite Configuration
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
import { resolve } from 'path';
export default defineConfig(({ mode }) => ({
plugins: [react(), cssInjectedByJsPlugin()],
build: {
lib: {
entry: resolve(__dirname, 'src/index.tsx'),
name: 'MyGlitchComponent',
fileName: 'my-glitch-component',
formats: ['es']
},
rollupOptions: {
external: ['react', 'react-dom', 'react/jsx-runtime'],
output: {
globals: { react: 'React', 'react-dom': 'ReactDOM', 'react/jsx-runtime': 'jsxRuntime' },
assetFileNames: 'assets/[name][extname]'
}
},
sourcemap: true,
minify: mode === 'production'
},
server: { port: 3001, open: true }
}));
Package.json
{
"name": "my-glitch-component",
"version": "1.0.0",
"type": "module",
"main": "./dist/my-glitch-component.js",
"module": "./dist/my-glitch-component.js",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@vitejs/plugin-react": "^4.2.0",
"leva": "^0.9.35",
"typescript": "^5.3.0",
"vite": "^5.0.0",
"vite-plugin-css-injected-by-js": "^3.3.0"
}
}
Dev Harness with Leva
// src/dev.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { useControls, Leva } from 'leva';
import Component from './Component';
import { metadata } from './index';
function DevHarness() {
const levaSchema = Object.entries(metadata.paramSchema).reduce((acc, [key, schema]) => {
if (schema.type === 'range' || schema.type === 'number') {
acc[key] = { value: schema.default, min: schema.min, max: schema.max, step: schema.step };
} else if (schema.type === 'select') {
acc[key] = { value: schema.default, options: schema.options?.reduce((o, opt) => ({ ...o, [opt.label]: opt.value }), {}) };
} else {
acc[key] = schema.default;
}
return acc;
}, {} as Record<string, unknown>);
const params = useControls(levaSchema);
return (
<div style={{ minHeight: '100vh', background: '#0a0a0f', padding: '2rem' }}>
<Leva collapsed={false} />
<Component
config={{ id: 'dev', name: metadata.name, version: metadata.version, params }}
theme={{ primary: '#6366f1', accent: '#22d3ee', bg: '#0a0a0f', bgSecondary: '#12121a', text: '#e8e8ec', textMuted: '#9999a8', border: '#2a2a3a' }}
onComplete={(r) => { console.log('Complete:', r); alert(`Done! Score: ${r.score}`); }}
/>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')!).render(<React.StrictMode><DevHarness /></React.StrictMode>);
Three.js / R3F Components
For 3D components, add to externals in vite.config.ts:
external: ['react', 'react-dom', 'react/jsx-runtime', 'three', '@react-three/fiber', '@react-three/drei']
Deployment
nvm use 20# to select node version 20npm run build- Copy
dist/*.jsto host app'ssrc/components/glitch/ - Add database entry:
INSERT INTO glitch_components (name, display_name, version, file_path, param_schema, default_params) VALUES ('my-component', 'My Component', '1.0.0', 'my-component.js', '{"speed":{"type":"range","default":1.0}}', '{"speed":1.0}'); - Link to tech via
glitch_component_id