create-modern-react 2.1.2 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/lib/prompts.js +8 -0
- package/lib/setup.js +18 -1
- package/package.json +1 -1
- package/templates/base/.claude/skills/agent-browser/SKILL.md +356 -0
- package/templates/base/.claude/skills/agent-browser/references/authentication.md +188 -0
- package/templates/base/.claude/skills/agent-browser/references/proxy-support.md +175 -0
- package/templates/base/.claude/skills/agent-browser/references/session-management.md +181 -0
- package/templates/base/.claude/skills/agent-browser/references/snapshot-refs.md +186 -0
- package/templates/base/.claude/skills/agent-browser/references/video-recording.md +162 -0
- package/templates/base/.claude/skills/agent-browser/templates/authenticated-session.sh +91 -0
- package/templates/base/.claude/skills/agent-browser/templates/capture-workflow.sh +68 -0
- package/templates/base/.claude/skills/agent-browser/templates/form-automation.sh +64 -0
- package/templates/base/.claude/skills/autoskill/skill.md +134 -0
- package/templates/base/.claude/skills/design-principles/skill.md +237 -0
- package/templates/base/.claude/skills/frontend-design/skill.md +42 -0
- package/templates/base/.claude/skills/learn-together/skill.md +448 -0
- package/templates/base/.claude/skills/question-me/skill.md +175 -0
- package/templates/base/.claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/templates/base/.claude/skills/react-best-practices/README.md +123 -0
- package/templates/base/.claude/skills/react-best-practices/SKILL.md +135 -0
- package/templates/base/.claude/skills/react-best-practices/metadata.json +15 -0
- package/templates/base/.claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/templates/base/.claude/skills/react-best-practices/rules/_template.md +28 -0
- package/templates/base/.claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/templates/base/.claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/templates/base/.claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/templates/base/.claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/templates/base/.claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/templates/base/.claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/templates/base/.claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/templates/base/.claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/templates/base/.claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/templates/base/.claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/templates/base/.claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/templates/base/.claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/templates/base/.claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/templates/base/.claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/templates/base/.claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/templates/base/.claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-batch-dom-css.md +57 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/templates/base/.claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/templates/base/.claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/templates/base/.claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/templates/base/.claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/templates/base/.claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/templates/base/.claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/templates/base/.claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/SKILL.md +377 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +53 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/templates/base/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -0
- package/templates/base/public/robots.txt +2 -0
- package/templates/base/public/screenshots/healthmug.png +0 -0
- package/templates/base/public/screenshots/resumefreepro.png +0 -0
- package/templates/base/public/vite.svg +1 -0
- package/templates/base/src/screens/home/index.tsx +113 -14
- package/templates/optional/forms/index.ts +2 -0
- package/templates/optional/forms/types.ts +39 -0
- package/templates/optional/forms/use-zod-form.ts +59 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
UI/UX Pro Max Search - BM25 search engine for UI/UX style guides
|
|
5
|
+
Usage: python search.py "<query>" [--domain <domain>] [--stack <stack>] [--max-results 3]
|
|
6
|
+
python search.py "<query>" --design-system [-p "Project Name"]
|
|
7
|
+
python search.py "<query>" --design-system --persist [-p "Project Name"] [--page "dashboard"]
|
|
8
|
+
|
|
9
|
+
Domains: style, prompt, color, chart, landing, product, ux, typography
|
|
10
|
+
Stacks: html-tailwind, react, nextjs
|
|
11
|
+
|
|
12
|
+
Persistence (Master + Overrides pattern):
|
|
13
|
+
--persist Save design system to design-system/MASTER.md
|
|
14
|
+
--page Also create a page-specific override file in design-system/pages/
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
from core import CSV_CONFIG, AVAILABLE_STACKS, MAX_RESULTS, search, search_stack
|
|
19
|
+
from design_system import generate_design_system, persist_design_system
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def format_output(result):
|
|
23
|
+
"""Format results for Claude consumption (token-optimized)"""
|
|
24
|
+
if "error" in result:
|
|
25
|
+
return f"Error: {result['error']}"
|
|
26
|
+
|
|
27
|
+
output = []
|
|
28
|
+
if result.get("stack"):
|
|
29
|
+
output.append(f"## UI Pro Max Stack Guidelines")
|
|
30
|
+
output.append(f"**Stack:** {result['stack']} | **Query:** {result['query']}")
|
|
31
|
+
else:
|
|
32
|
+
output.append(f"## UI Pro Max Search Results")
|
|
33
|
+
output.append(f"**Domain:** {result['domain']} | **Query:** {result['query']}")
|
|
34
|
+
output.append(f"**Source:** {result['file']} | **Found:** {result['count']} results\n")
|
|
35
|
+
|
|
36
|
+
for i, row in enumerate(result['results'], 1):
|
|
37
|
+
output.append(f"### Result {i}")
|
|
38
|
+
for key, value in row.items():
|
|
39
|
+
value_str = str(value)
|
|
40
|
+
if len(value_str) > 300:
|
|
41
|
+
value_str = value_str[:300] + "..."
|
|
42
|
+
output.append(f"- **{key}:** {value_str}")
|
|
43
|
+
output.append("")
|
|
44
|
+
|
|
45
|
+
return "\n".join(output)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
parser = argparse.ArgumentParser(description="UI Pro Max Search")
|
|
50
|
+
parser.add_argument("query", help="Search query")
|
|
51
|
+
parser.add_argument("--domain", "-d", choices=list(CSV_CONFIG.keys()), help="Search domain")
|
|
52
|
+
parser.add_argument("--stack", "-s", choices=AVAILABLE_STACKS, help="Stack-specific search (html-tailwind, react, nextjs)")
|
|
53
|
+
parser.add_argument("--max-results", "-n", type=int, default=MAX_RESULTS, help="Max results (default: 3)")
|
|
54
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
55
|
+
# Design system generation
|
|
56
|
+
parser.add_argument("--design-system", "-ds", action="store_true", help="Generate complete design system recommendation")
|
|
57
|
+
parser.add_argument("--project-name", "-p", type=str, default=None, help="Project name for design system output")
|
|
58
|
+
parser.add_argument("--format", "-f", choices=["ascii", "markdown"], default="ascii", help="Output format for design system")
|
|
59
|
+
# Persistence (Master + Overrides pattern)
|
|
60
|
+
parser.add_argument("--persist", action="store_true", help="Save design system to design-system/MASTER.md (creates hierarchical structure)")
|
|
61
|
+
parser.add_argument("--page", type=str, default=None, help="Create page-specific override file in design-system/pages/")
|
|
62
|
+
parser.add_argument("--output-dir", "-o", type=str, default=None, help="Output directory for persisted files (default: current directory)")
|
|
63
|
+
|
|
64
|
+
args = parser.parse_args()
|
|
65
|
+
|
|
66
|
+
# Design system takes priority
|
|
67
|
+
if args.design_system:
|
|
68
|
+
result = generate_design_system(
|
|
69
|
+
args.query,
|
|
70
|
+
args.project_name,
|
|
71
|
+
args.format,
|
|
72
|
+
persist=args.persist,
|
|
73
|
+
page=args.page,
|
|
74
|
+
output_dir=args.output_dir
|
|
75
|
+
)
|
|
76
|
+
print(result)
|
|
77
|
+
|
|
78
|
+
# Print persistence confirmation
|
|
79
|
+
if args.persist:
|
|
80
|
+
project_slug = args.project_name.lower().replace(' ', '-') if args.project_name else "default"
|
|
81
|
+
print("\n" + "=" * 60)
|
|
82
|
+
print(f"✅ Design system persisted to design-system/{project_slug}/")
|
|
83
|
+
print(f" 📄 design-system/{project_slug}/MASTER.md (Global Source of Truth)")
|
|
84
|
+
if args.page:
|
|
85
|
+
page_filename = args.page.lower().replace(' ', '-')
|
|
86
|
+
print(f" 📄 design-system/{project_slug}/pages/{page_filename}.md (Page Overrides)")
|
|
87
|
+
print("")
|
|
88
|
+
print(f"📖 Usage: When building a page, check design-system/{project_slug}/pages/[page].md first.")
|
|
89
|
+
print(f" If exists, its rules override MASTER.md. Otherwise, use MASTER.md.")
|
|
90
|
+
print("=" * 60)
|
|
91
|
+
# Stack search
|
|
92
|
+
elif args.stack:
|
|
93
|
+
result = search_stack(args.query, args.stack, args.max_results)
|
|
94
|
+
if args.json:
|
|
95
|
+
import json
|
|
96
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
97
|
+
else:
|
|
98
|
+
print(format_output(result))
|
|
99
|
+
# Domain search
|
|
100
|
+
else:
|
|
101
|
+
result = search(args.query, args.domain, args.max_results)
|
|
102
|
+
if args.json:
|
|
103
|
+
import json
|
|
104
|
+
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
105
|
+
else:
|
|
106
|
+
print(format_output(result))
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFBD4F"></stop><stop offset="100%" stop-color="#FF980E"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import { Moon, Sun, Github, Zap } from 'lucide-react';
|
|
2
|
+
import { Moon, Sun, Github, Zap, Bot, ExternalLink } from 'lucide-react';
|
|
3
3
|
import { Button, Card, CardContent, CardHeader, CardTitle } from '~/components/ui';
|
|
4
4
|
import { useTheme } from '~/providers';
|
|
5
5
|
|
|
@@ -16,10 +16,21 @@ export default function Home() {
|
|
|
16
16
|
<div className="w-full max-w-2xl space-y-8">
|
|
17
17
|
{/* Header */}
|
|
18
18
|
<div className="text-center">
|
|
19
|
-
<div className="mb-4 flex justify-
|
|
19
|
+
<div className="mb-4 flex items-center justify-between">
|
|
20
|
+
<div className="w-40" />
|
|
20
21
|
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-primary text-primary-foreground">
|
|
21
22
|
<Zap className="h-8 w-8" />
|
|
22
23
|
</div>
|
|
24
|
+
<Button variant="outline" asChild>
|
|
25
|
+
<a
|
|
26
|
+
href="https://github.com/abhay-rana/create-modern-react"
|
|
27
|
+
target="_blank"
|
|
28
|
+
rel="noopener noreferrer"
|
|
29
|
+
>
|
|
30
|
+
<Github className="mr-2 h-4 w-4" />
|
|
31
|
+
View CLI on GitHub
|
|
32
|
+
</a>
|
|
33
|
+
</Button>
|
|
23
34
|
</div>
|
|
24
35
|
<h1 className="text-4xl font-bold tracking-tight">
|
|
25
36
|
create-modern-react
|
|
@@ -27,6 +38,11 @@ export default function Home() {
|
|
|
27
38
|
<p className="mt-2 text-muted-foreground">
|
|
28
39
|
Production-ready React + TypeScript + Tailwind in seconds
|
|
29
40
|
</p>
|
|
41
|
+
<div className="mt-4">
|
|
42
|
+
<code className="rounded-md bg-muted px-3 py-1.5 font-mono text-sm">
|
|
43
|
+
npx create-modern-react my-app
|
|
44
|
+
</code>
|
|
45
|
+
</div>
|
|
30
46
|
</div>
|
|
31
47
|
|
|
32
48
|
{/* Counter Card */}
|
|
@@ -94,18 +110,42 @@ export default function Home() {
|
|
|
94
110
|
/>
|
|
95
111
|
</div>
|
|
96
112
|
|
|
97
|
-
{/*
|
|
98
|
-
<div className="
|
|
99
|
-
<
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
{/* AI Skills Highlight */}
|
|
114
|
+
<div className="rounded-lg border bg-gradient-to-r from-primary/5 to-primary/10 p-4">
|
|
115
|
+
<div className="flex items-start gap-3">
|
|
116
|
+
<Bot className="h-5 w-5 text-primary mt-0.5" />
|
|
117
|
+
<div>
|
|
118
|
+
<h3 className="font-semibold">Claude Code AI Skills</h3>
|
|
119
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
120
|
+
8 pre-configured skills for React best practices, UI/UX design, browser testing, and spec refinement
|
|
121
|
+
</p>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Projects Showcase */}
|
|
127
|
+
<div className="space-y-4">
|
|
128
|
+
<h2 className="text-center text-2xl font-bold">
|
|
129
|
+
Production Projects created using this boilerplate
|
|
130
|
+
</h2>
|
|
131
|
+
<div className="grid gap-6 sm:grid-cols-2">
|
|
132
|
+
<ProjectCard
|
|
133
|
+
category="AI/Career"
|
|
134
|
+
title="ResumeFreePro"
|
|
135
|
+
description="AI-Powered Resume Builder"
|
|
136
|
+
designStyle="Modern + Glassmorphism"
|
|
137
|
+
url="https://resumefreepro.com?utm_source=starter-template&utm_medium=landing-page&utm_campaign=create-modern-react-demo"
|
|
138
|
+
previewUrl="/screenshots/resumefreepro.png"
|
|
139
|
+
/>
|
|
140
|
+
<ProjectCard
|
|
141
|
+
category="E-Pharmacy"
|
|
142
|
+
title="HealthMug"
|
|
143
|
+
description="Online Pharmacy Platform"
|
|
144
|
+
designStyle="Clean + Professional"
|
|
145
|
+
url="https://healthmug.com?utm_source=starter-template&utm_medium=landing-page&utm_campaign=create-modern-react-demo"
|
|
146
|
+
previewUrl="/screenshots/healthmug.png"
|
|
147
|
+
/>
|
|
148
|
+
</div>
|
|
109
149
|
</div>
|
|
110
150
|
|
|
111
151
|
<p className="text-center text-xs text-muted-foreground">
|
|
@@ -130,3 +170,62 @@ function FeatureCard({
|
|
|
130
170
|
</div>
|
|
131
171
|
);
|
|
132
172
|
}
|
|
173
|
+
|
|
174
|
+
function ProjectCard({
|
|
175
|
+
category,
|
|
176
|
+
title,
|
|
177
|
+
description,
|
|
178
|
+
designStyle,
|
|
179
|
+
url,
|
|
180
|
+
previewUrl,
|
|
181
|
+
}: {
|
|
182
|
+
category: string;
|
|
183
|
+
title: string;
|
|
184
|
+
description: string;
|
|
185
|
+
designStyle: string;
|
|
186
|
+
url: string;
|
|
187
|
+
previewUrl: string;
|
|
188
|
+
}) {
|
|
189
|
+
return (
|
|
190
|
+
<a
|
|
191
|
+
href={url}
|
|
192
|
+
target="_blank"
|
|
193
|
+
rel="noopener noreferrer"
|
|
194
|
+
className="group block"
|
|
195
|
+
>
|
|
196
|
+
<Card className="overflow-hidden transition-all hover:-translate-y-1 hover:shadow-lg">
|
|
197
|
+
{/* Preview Area */}
|
|
198
|
+
<div className="relative h-48 overflow-hidden bg-muted">
|
|
199
|
+
<img
|
|
200
|
+
src={previewUrl}
|
|
201
|
+
alt={`${title} preview`}
|
|
202
|
+
className="h-full w-full object-cover object-top transition-transform duration-300 group-hover:scale-105"
|
|
203
|
+
/>
|
|
204
|
+
{/* Overlay on hover */}
|
|
205
|
+
<div className="absolute inset-0 flex items-center justify-center bg-black/60 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
|
206
|
+
<div className="text-center text-white">
|
|
207
|
+
<ExternalLink className="mx-auto h-8 w-8" />
|
|
208
|
+
<p className="mt-2 text-sm font-medium">View Demo</p>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
<CardContent className="space-y-3 p-4">
|
|
214
|
+
{/* Category Badge */}
|
|
215
|
+
<span className="inline-block rounded-md bg-primary/10 px-2 py-1 text-xs font-medium text-primary">
|
|
216
|
+
{category}
|
|
217
|
+
</span>
|
|
218
|
+
|
|
219
|
+
{/* Title & Description */}
|
|
220
|
+
<div>
|
|
221
|
+
<h3 className="font-bold">{title}</h3>
|
|
222
|
+
<p className="mt-1 text-sm text-muted-foreground">{description}</p>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
{/* Design Style */}
|
|
226
|
+
<p className="text-xs text-muted-foreground">{designStyle}</p>
|
|
227
|
+
</CardContent>
|
|
228
|
+
</Card>
|
|
229
|
+
</a>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type UseFormReturn,
|
|
3
|
+
type FieldValues,
|
|
4
|
+
type UseFormProps,
|
|
5
|
+
} from 'react-hook-form';
|
|
6
|
+
import { type ZodType } from 'zod';
|
|
7
|
+
|
|
8
|
+
export interface UseZodFormProps<T extends FieldValues>
|
|
9
|
+
extends Omit<UseFormProps<T>, 'resolver'> {
|
|
10
|
+
schema: ZodType<T>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type UseZodFormReturn<T extends FieldValues> = UseFormReturn<T>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Common form field props for building reusable form components
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* interface TextFieldProps extends FormFieldProps {
|
|
20
|
+
* type?: 'text' | 'email' | 'password';
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* function TextField({ label, error, required, ...props }: TextFieldProps) {
|
|
24
|
+
* return (
|
|
25
|
+
* <div>
|
|
26
|
+
* {label && <label>{label}{required && '*'}</label>}
|
|
27
|
+
* <input {...props} />
|
|
28
|
+
* {error && <span className="text-red-500">{error}</span>}
|
|
29
|
+
* </div>
|
|
30
|
+
* );
|
|
31
|
+
* }
|
|
32
|
+
*/
|
|
33
|
+
export interface FormFieldProps {
|
|
34
|
+
label?: string;
|
|
35
|
+
error?: string;
|
|
36
|
+
required?: boolean;
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
className?: string;
|
|
39
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useForm,
|
|
3
|
+
type UseFormProps,
|
|
4
|
+
type FieldValues,
|
|
5
|
+
type Path,
|
|
6
|
+
type FieldErrors,
|
|
7
|
+
} from 'react-hook-form';
|
|
8
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
9
|
+
import { type ZodType } from 'zod';
|
|
10
|
+
|
|
11
|
+
interface UseZodFormProps<T extends FieldValues>
|
|
12
|
+
extends Omit<UseFormProps<T>, 'resolver'> {
|
|
13
|
+
schema: ZodType<T>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Custom hook that wraps React Hook Form with Zod validation
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const schema = z.object({
|
|
21
|
+
* email: z.string().email(),
|
|
22
|
+
* password: z.string().min(8),
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const form = useZodForm({ schema });
|
|
26
|
+
*
|
|
27
|
+
* <form onSubmit={form.handleSubmit(onSubmit)}>
|
|
28
|
+
* <input {...form.register('email')} />
|
|
29
|
+
* {form.formState.errors.email && <span>{form.formState.errors.email.message}</span>}
|
|
30
|
+
* </form>
|
|
31
|
+
*/
|
|
32
|
+
export function useZodForm<T extends FieldValues>({
|
|
33
|
+
schema,
|
|
34
|
+
mode = 'onBlur',
|
|
35
|
+
...formProps
|
|
36
|
+
}: UseZodFormProps<T>) {
|
|
37
|
+
return useForm<T>({
|
|
38
|
+
resolver: zodResolver(schema),
|
|
39
|
+
mode,
|
|
40
|
+
...formProps,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Helper to get error message for a field
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* const error = getFieldError(form.formState.errors, 'email');
|
|
49
|
+
* if (error) {
|
|
50
|
+
* console.log(error); // "Invalid email address"
|
|
51
|
+
* }
|
|
52
|
+
*/
|
|
53
|
+
export function getFieldError<T extends FieldValues>(
|
|
54
|
+
errors: FieldErrors<T>,
|
|
55
|
+
name: Path<T>
|
|
56
|
+
): string | undefined {
|
|
57
|
+
const error = errors[name];
|
|
58
|
+
return error?.message as string | undefined;
|
|
59
|
+
}
|