diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..91582a8
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,37 @@
+# Dependencies
+node_modules
+.pnp
+.pnp.js
+
+# Testing
+coverage
+
+# Next.js
+.next
+out
+build
+dist
+
+# Production
+*.min.js
+*.min.css
+
+# Misc
+.DS_Store
+*.pem
+
+# Debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Local env files
+.env*.local
+
+# Vercel
+.vercel
+
+# Typescript
+*.tsbuildinfo
+next-env.d.ts
+
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..115ffb9
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,10 @@
+{
+ "semi": true,
+ "trailingComma": "es5",
+ "singleQuote": false,
+ "printWidth": 80,
+ "tabWidth": 2,
+ "useTabs": false,
+ "arrowParens": "always",
+ "endOfLine": "lf"
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..783bfbb
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,34 @@
+{
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit"
+ },
+ "[javascript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[javascriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[json]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[jsonc]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "prettier.requireConfig": true,
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "typescript.enablePromptUseWorkspaceTsdk": true,
+ "typescript.preferences.importModuleSpecifier": "non-relative",
+ "editor.tabSize": 2,
+ "editor.insertSpaces": true,
+ "files.eol": "\n",
+ "files.insertFinalNewline": true,
+ "files.trimTrailingWhitespace": true
+}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..a71f62a
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,377 @@
+# Contributing Guide
+
+This guide explains how to add new components and blocks to UITripleD.
+
+## Table of Contents
+
+- [Adding a New Component](#adding-a-new-component)
+- [Adding a New Block](#adding-a-new-block)
+- [File Structure](#file-structure)
+- [Component Categories](#component-categories)
+
+---
+
+## Adding a New Component
+
+Components are reusable UI elements organized by category (microinteractions, components, page, data, decorative, blocks).
+
+### Step 1: Create Component File
+
+Create the component file in the appropriate category directory:
+
+```
+components/{category}/{component-id}.tsx
+```
+
+Examples:
+
+- `components/micro/buttons/new-button.tsx` (for microinteractions)
+- `components/components/cards/new-card.tsx` (for components)
+- `components/sections/new-section.tsx` (for blocks)
+- `components/motion-core/new-animation.tsx` (for motion-core components)
+
+**Note:** The file path should match the component's category and subcategory structure.
+
+### Step 2: Update Components Registry
+
+Edit `lib/components-registry.tsx`:
+
+1. **Import the component** at the top:
+
+ ```tsx
+ import { NewComponent } from "@/components/{category}/{component-id}";
+ ```
+
+2. **Add to `componentsRegistry`** array:
+ ```tsx
+ {
+ id: "new-component",
+ name: "New Component",
+ description: "Description of what this component does.",
+ category: "components", // or "microinteractions", "page", "data", "decorative", "blocks"
+ tags: ["tag1", "tag2", "tag3"],
+ component: NewComponent,
+ codePath: "@/components/{category}/{component-id}.tsx",
+ duration: "300ms",
+ easing: "easeOut",
+ display: true, // Set to false if component needs fixes or is not ready
+ },
+ ```
+
+**Important:**
+
+- Use kebab-case for `id` (e.g., `new-component`)
+- Provide a clear `description`
+- Add relevant `tags` for searchability
+- Set `display: false` if the component needs fixes or isn't ready for production
+- The `codePath` should match the actual file location
+
+### Step 3: Sync Registry JSON
+
+Run the sync script to update `registry.json`:
+
+```bash
+npm run sync-registry
+```
+
+This script automatically:
+
+- Reads components from `lib/components-registry.tsx`
+- Detects dependencies from component imports
+- Updates `registry.json` with the correct structure
+- Preserves existing dependencies if they exist
+
+**Note:** The sync script will automatically:
+
+- Map categories to registry types (e.g., `microinteractions` → `registry:ui`)
+- Detect `registryDependencies` from `@/components/ui/` imports
+- Detect external `dependencies` from npm packages
+- Set appropriate `category` and `subcategory` based on file path
+
+### Step 4: Verify
+
+1. Check that the component appears in the components list
+2. Verify the component page loads correctly
+3. Test the component functionality
+4. Ensure all dependencies are correctly listed in `registry.json`
+
+---
+
+## Adding a New Block
+
+Blocks are complex, feature-rich sections typically used in landing pages (hero sections, pricing tables, testimonials, etc.).
+
+### Step 1: Create Block File
+
+Create the block file in the sections directory:
+
+```
+components/sections/{block-id}.tsx
+```
+
+Example: `components/sections/new-feature-block.tsx`
+
+### Step 2: Update Components Registry
+
+Edit `lib/components-registry.tsx`:
+
+1. **Import the block** at the top:
+
+ ```tsx
+ import { NewFeatureBlock } from "@/components/sections/new-feature-block";
+ ```
+
+2. **Add to `componentsRegistry`** array with `category: "blocks"`:
+ ```tsx
+ {
+ id: "new-feature-block",
+ name: "New Feature Block",
+ description: "Description of what this block does.",
+ category: "blocks",
+ tags: ["feature", "landing", "section"],
+ component: NewFeatureBlock,
+ codePath: "@/components/sections/new-feature-block.tsx",
+ duration: "600ms",
+ easing: "easeOut",
+ display: true,
+ },
+ ```
+
+### Step 3: Sync Registry JSON
+
+Run the sync script:
+
+```bash
+npm run sync-registry
+```
+
+### Step 4: Verify
+
+1. Check that the block appears in the blocks category
+2. Verify the block page loads correctly
+3. Test the block functionality
+4. Ensure responsive design works on different screen sizes
+
+---
+
+## File Structure
+
+```
+UITripleD/
+├── components/
+│ ├── micro/ # Microinteractions (buttons, toggles, icons, badges, links)
+│ │ ├── buttons/
+│ │ ├── toggles/
+│ │ ├── icons/
+│ │ ├── badges/
+│ │ └── links/
+│ ├── components/ # Reusable UI components
+│ │ ├── cards/
+│ │ ├── chat/
+│ │ ├── forms/
+│ │ ├── inputs/
+│ │ ├── lists/
+│ │ ├── modal/
+│ │ ├── notifications/
+│ │ ├── tabs/
+│ │ └── ...
+│ ├── sections/ # Block sections (landing page components)
+│ ├── motion-core/ # Advanced motion components
+│ ├── navigation/ # Navigation components
+│ ├── forms/ # Form components
+│ ├── modals/ # Modal components
+│ ├── tooltips/ # Tooltip components
+│ ├── decorative/ # Decorative components (backgrounds, text)
+│ ├── data/ # Data visualization components
+│ ├── page/ # Page-level components
+│ └── ui/ # Base UI components (shadcn/ui)
+├── lib/
+│ ├── components-registry.tsx # Component metadata and mapping
+│ ├── file-reader.ts # Code loading utilities
+│ └── utils.ts # Utility functions
+├── scripts/
+│ └── sync-registry.js # Auto-sync registry.json
+├── registry.json # Shadcn registry configuration (auto-generated)
+└── types/
+ └── index.ts # TypeScript types
+```
+
+---
+
+## Component Categories
+
+### Microinteractions (`microinteractions`)
+
+Small, delightful interactions for buttons, toggles, and icons.
+
+- **Location:** `components/micro/`
+- **Registry Type:** `registry:ui`
+- **Examples:** Magnetic buttons, shimmer effects, animated badges
+
+### Components (`components`)
+
+Animated UI components like modals, dropdowns, and cards.
+
+- **Location:** `components/components/`
+- **Registry Type:** `registry:component`
+- **Examples:** Chat interfaces, animated cards, form components
+
+### Page (`page`)
+
+Smooth transitions and hero sections for pages.
+
+- **Location:** `components/page/` or `components/sections/`
+- **Registry Type:** `registry:page`
+- **Examples:** Hero sections, scroll reveals, page transitions
+
+### Data (`data`)
+
+Bring your data to life with counters, progress bars, and lists.
+
+- **Location:** `components/data/`
+- **Registry Type:** `registry:ui`
+- **Examples:** Animated counters, progress bars, charts
+
+### Decorative (`decorative`)
+
+Beautiful text and background effects.
+
+- **Location:** `components/decorative/`
+- **Registry Type:** `registry:ui`
+- **Examples:** Gradient animations, typewriter text, floating effects
+
+### Blocks (`blocks`)
+
+Reusable block sections for landing pages and portfolios.
+
+- **Location:** `components/sections/`
+- **Registry Type:** `registry:block`
+- **Examples:** Hero blocks, pricing sections, testimonials
+
+---
+
+## Quick Checklist
+
+### For Components:
+
+- [ ] Component file created in appropriate category directory
+- [ ] Component imported in `lib/components-registry.tsx`
+- [ ] Added to `componentsRegistry` array with all required fields
+- [ ] Ran `npm run sync-registry` to update `registry.json`
+- [ ] Verified component appears in the UI
+- [ ] Tested component functionality
+- [ ] Checked dependencies in `registry.json`
+
+### For Blocks:
+
+- [ ] Block file created in `components/sections/`
+- [ ] Block imported in `lib/components-registry.tsx`
+- [ ] Added to `componentsRegistry` with `category: "blocks"`
+- [ ] Ran `npm run sync-registry` to update `registry.json`
+- [ ] Verified block appears in blocks category
+- [ ] Tested responsive design
+- [ ] Checked dependencies in `registry.json`
+
+---
+
+## Tips
+
+1. **Naming Convention:**
+ - Use kebab-case for component IDs (e.g., `new-component`, `hero-section`)
+ - Use PascalCase for component names (e.g., `NewComponent`, `HeroSection`)
+ - File names should match component IDs
+
+2. **Dependencies:**
+ - The sync script automatically detects dependencies from imports
+ - `registryDependencies` are detected from `@/components/ui/` imports
+ - External `dependencies` are detected from npm package imports
+ - Always verify dependencies after syncing
+
+3. **Component Metadata:**
+ - Provide clear, descriptive `description` fields
+ - Add relevant `tags` for better searchability
+ - Set appropriate `duration` and `easing` for animations
+ - Use `display: false` for components that need fixes
+
+4. **Code Quality:**
+ - Follow TypeScript best practices
+ - Use proper React patterns (hooks, composition)
+ - Ensure accessibility (ARIA labels, keyboard navigation)
+ - Support reduced motion preferences where applicable
+ - Make components responsive
+
+5. **Testing:**
+ - Always test components after adding
+ - Verify the component appears in the UI
+ - Test on different screen sizes
+ - Check browser console for errors
+ - Verify dependencies are correctly listed
+
+6. **Sync Script:**
+ - Run `npm run sync-registry` after adding new components
+ - The script preserves existing dependencies
+ - Check the output for any warnings or errors
+ - Verify `registry.json` was updated correctly
+
+---
+
+## Registry Sync Details
+
+The `sync-registry.js` script automatically:
+
+1. **Parses** `lib/components-registry.tsx` to extract component metadata
+2. **Detects** dependencies from component file imports
+3. **Maps** categories to registry types:
+ - `microinteractions` → `registry:ui`
+ - `components` → `registry:component`
+ - `page` → `registry:page`
+ - `data` → `registry:ui`
+ - `decorative` → `registry:ui`
+ - `blocks` → `registry:block`
+4. **Updates** `registry.json` with new/updated entries
+5. **Preserves** existing dependencies if they exist
+
+**Important:** Always run `npm run sync-registry` after adding new components to ensure `registry.json` stays in sync.
+
+---
+
+## Need Help?
+
+If you encounter issues:
+
+1. **Check existing components** for reference patterns
+2. **Verify file paths** match the `codePath` in registry
+3. **Ensure TypeScript types** match the `Component` interface
+4. **Run the linter** to catch errors: `npm run lint`
+5. **Check browser console** for runtime errors
+6. **Verify dependencies** are correctly listed in `registry.json`
+7. **Test the sync script** output for warnings
+
+---
+
+## Code Style
+
+- Use TypeScript for all components
+- Follow React best practices
+- Use functional components with hooks
+- Prefer composition over inheritance
+- Use meaningful variable and function names
+- Add comments for complex logic
+- Keep components focused and single-purpose
+
+---
+
+## Accessibility
+
+When creating components, consider:
+
+- **Keyboard Navigation:** Ensure all interactive elements are keyboard accessible
+- **Screen Readers:** Add appropriate ARIA labels and roles
+- **Reduced Motion:** Respect `prefers-reduced-motion` media query
+- **Focus Management:** Provide visible focus indicators
+- **Color Contrast:** Ensure sufficient contrast ratios
+- **Semantic HTML:** Use appropriate HTML elements
+
+---
+
+Thank you for contributing to UITripleD! 🎉
diff --git a/app/api/registry/[name]/route.ts b/app/api/registry/[name]/route.ts
index f54386f..e61cc05 100644
--- a/app/api/registry/[name]/route.ts
+++ b/app/api/registry/[name]/route.ts
@@ -1,11 +1,15 @@
import { NextResponse } from "next/server";
-import { componentsRegistry, getComponentById, loadComponentCode } from "@/lib/components-registry";
+import {
+ componentsRegistry,
+ getComponentById,
+ loadComponentCode,
+} from "@/lib/components-registry";
/**
* GET handler for registry
* Returns component data from components-registry.tsx
* Compatible with shadcn registry format
- *
+ *
* @param request - Next.js request object
* @param params - Route parameters containing the component name
*/
@@ -19,7 +23,7 @@ export async function GET(
// If name is provided, return specific component
if (name && name !== "index") {
const component = getComponentById(name);
-
+
if (!component) {
return NextResponse.json(
{ error: `Component "${name}" not found` },
@@ -54,7 +58,7 @@ export async function GET(
}
// Return full registry (all components)
- const registry = componentsRegistry.map((component) => ({
+ const registry = componentsRegistry.map((component) => ({
id: component.id,
name: component.name,
description: component.description,
diff --git a/app/builder/page.tsx b/app/builder/page.tsx
index 69f459c..47c249a 100644
--- a/app/builder/page.tsx
+++ b/app/builder/page.tsx
@@ -49,7 +49,7 @@ export default function BuilderPage() {
activationConstraint: {
distance: 8,
},
- }),
+ })
);
useEffect(() => {
@@ -127,10 +127,10 @@ export default function BuilderPage() {
};
}),
};
- }),
+ })
);
},
- [activePageId],
+ [activePageId]
);
const handleUpdateTextNode = useCallback(
@@ -163,16 +163,16 @@ export default function BuilderPage() {
};
}),
};
- }),
+ })
);
},
- [activePageId],
+ [activePageId]
);
const handleAddComponentToPage = useCallback(
(animationId: string) => {
const animation = componentsRegistry.find(
- (item) => item.id === animationId,
+ (item) => item.id === animationId
);
if (!animation || animation.category !== "blocks") {
return;
@@ -198,11 +198,11 @@ export default function BuilderPage() {
return prev.map((page) =>
page.id === targetPageId
? { ...page, components: [...page.components, newComponent] }
- : page,
+ : page
);
});
},
- [activePageId],
+ [activePageId]
);
const handleMobileComponentSelect = useCallback(
@@ -210,7 +210,7 @@ export default function BuilderPage() {
handleAddComponentToPage(animationId);
setMobileSidebarOpen(false);
},
- [handleAddComponentToPage],
+ [handleAddComponentToPage]
);
const handleDragStart = (event: DragStartEvent) => {
@@ -240,7 +240,7 @@ export default function BuilderPage() {
// Check if we're reordering components within the canvas
const activeIndex = currentPage.components.findIndex(
- (c) => c.id === activeId,
+ (c) => c.id === activeId
);
const overIndex = currentPage.components.findIndex((c) => c.id === overId);
@@ -253,8 +253,8 @@ export default function BuilderPage() {
...page,
components: arrayMove(page.components, activeIndex, overIndex),
}
- : page,
- ),
+ : page
+ )
);
setActiveId(null);
return;
@@ -289,7 +289,7 @@ export default function BuilderPage() {
...page,
components: newItems,
};
- }),
+ })
);
} else {
// If dropped on canvas, append to end
@@ -297,8 +297,8 @@ export default function BuilderPage() {
prev.map((page) =>
page.id === currentPage.id
? { ...page, components: [...page.components, newComponent] }
- : page,
- ),
+ : page
+ )
);
}
}
@@ -315,11 +315,11 @@ export default function BuilderPage() {
? {
...page,
components: page.components.filter(
- (component) => component.id !== id,
+ (component) => component.id !== id
),
}
- : page,
- ),
+ : page
+ )
);
};
@@ -368,8 +368,8 @@ export default function BuilderPage() {
name: normalized,
slug: newSlug,
}
- : item,
- ),
+ : item
+ )
);
};
@@ -397,13 +397,13 @@ export default function BuilderPage() {
const loadSavedProjects = () => {
const projects = JSON.parse(
- localStorage.getItem("builderProjects") || "{}",
+ localStorage.getItem("builderProjects") || "{}"
);
const projectList = Object.values(projects) as SavedProject[];
setSavedProjects(
projectList.sort(
- (a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime(),
- ),
+ (a, b) => new Date(b.savedAt).getTime() - new Date(a.savedAt).getTime()
+ )
);
setLoadDialogOpen(true);
};
@@ -429,7 +429,7 @@ export default function BuilderPage() {
const builderComponents = (page.components ?? [])
.map
- We include everything in one file for easy - copy-paste (including dummy data), but keep in mind - you should split your logic when integrating it - (e.g., move data fetching to loaders, hooks, or API - utilities). + We include everything in one file for easy copy-paste + (including dummy data), but keep in mind you should + split your logic when integrating it (e.g., move data + fetching to loaders, hooks, or API utilities).
@@ -467,8 +463,8 @@ export default function AnimationDetailPageClient({ code }: { code: string }) {
The colors and theme are customizable via Tailwind CSS
- classes. The default theme uses dark mode colors
- defined in your{" "}
+ classes. The default theme uses dark mode colors defined
+ in your{" "}
globals.css
{" "}
diff --git a/app/components/[id]/page.tsx b/app/components/[id]/page.tsx
index d457528..47a352a 100644
--- a/app/components/[id]/page.tsx
+++ b/app/components/[id]/page.tsx
@@ -1,6 +1,10 @@
import AnimationDetailPageClient from "./AnimationDetailPage.client";
import { createMetadata } from "@/lib/seo";
-import { getComponentById, componentsRegistry, loadComponentCode } from "@/lib/components-registry";
+import {
+ getComponentById,
+ componentsRegistry,
+ loadComponentCode,
+} from "@/lib/components-registry";
import { notFound } from "next/navigation";
type PageParams = {
diff --git a/app/components/layout.tsx b/app/components/layout.tsx
index 2fbc879..298d138 100644
--- a/app/components/layout.tsx
+++ b/app/components/layout.tsx
@@ -4,7 +4,10 @@ import { useState, useMemo, useEffect, useCallback } from "react";
import { useParams, usePathname, useRouter } from "next/navigation";
import { motion, AnimatePresence } from "framer-motion";
import { ChevronLeft, ChevronRight, Menu, X } from "lucide-react";
-import { getComponentById, componentsRegistry } from "@/lib/components-registry";
+import {
+ getComponentById,
+ componentsRegistry,
+} from "@/lib/components-registry";
import { AnimationsSidebar } from "@/components/animation-sidebar";
import { Button } from "@/components/ui/button";
import {
@@ -33,14 +36,16 @@ export default function ComponentsLayout({
// Get visible animations (display !== false)
const visibleAnimations = useMemo(() => {
- return componentsRegistry.filter((component) => component.display !== false);
+ return componentsRegistry.filter(
+ (component) => component.display !== false
+ );
}, []);
// Find current animation index and navigation
const currentIndex = useMemo(() => {
if (!selectedAnimation) return -1;
return visibleAnimations.findIndex(
- (anim) => anim.id === selectedAnimation.id,
+ (anim) => anim.id === selectedAnimation.id
);
}, [selectedAnimation, visibleAnimations]);
@@ -55,7 +60,7 @@ export default function ComponentsLayout({
(animationId: string) => {
router.push(`/components/${animationId}`);
},
- [router],
+ [router]
);
const handleMobileSelect = useCallback(
@@ -63,7 +68,7 @@ export default function ComponentsLayout({
handleNavigate(animationId);
setMobileSidebarOpen(false);
},
- [handleNavigate],
+ [handleNavigate]
);
// Keyboard navigation
diff --git a/app/globals.css b/app/globals.css
index 72694c0..541f971 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -134,7 +134,6 @@
}
}
-
@layer utilities {
.text-balance {
text-wrap: balance;
@@ -156,4 +155,4 @@
flex-direction: row !important;
display: flex !important;
}
-}
\ No newline at end of file
+}
diff --git a/app/og/route.tsx b/app/og/route.tsx
index 3014cfc..13eded5 100644
--- a/app/og/route.tsx
+++ b/app/og/route.tsx
@@ -75,7 +75,7 @@ function NotFoundOGImage(componentId: string, faviconUrl: string) {
function ComponentOGImage(
name: string,
description: string,
- faviconUrl: string,
+ faviconUrl: string
) {
return (