├── assets ├── yt3.png └── oc-thinking.png ├── app ├── favicon.ico ├── fonts │ ├── GeistVF.woff │ └── GeistMonoVF.woff ├── (static) │ ├── components │ │ ├── page.tsx │ │ ├── [slug] │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── templates │ │ ├── page.tsx │ │ ├── [category] │ │ │ ├── page.tsx │ │ │ ├── layout.tsx │ │ │ └── (details) │ │ │ │ └── [slug] │ │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── playground │ │ └── page.tsx │ ├── layout.tsx │ └── readme │ │ └── page.tsx ├── [...not_found] │ └── page.tsx ├── page.tsx ├── api │ └── file │ │ └── [filename] │ │ └── route.ts └── layout.tsx ├── public ├── code.webp ├── meta.png ├── validation.webp ├── customization.webp ├── robots.txt ├── sitemap.xml ├── sitemap-0.xml └── registry │ └── signature-input.json ├── global.d.ts ├── registry ├── index.ts ├── registry-components.ts └── schema.ts ├── postcss.config.mjs ├── vitest.config.ts ├── next-sitemap.config.js ├── components ├── ui │ ├── skeleton.tsx │ ├── label.tsx │ ├── textarea.tsx │ ├── separator.tsx │ ├── input.tsx │ ├── progress.tsx │ ├── toaster.tsx │ ├── sonner.tsx │ ├── if.tsx │ ├── slider.tsx │ ├── checkbox.tsx │ ├── switch.tsx │ ├── tooltip.tsx │ ├── badge.tsx │ ├── popover.tsx │ ├── theme-switch.tsx │ ├── radio-group.tsx │ ├── avatar.tsx │ ├── scroll-area.tsx │ ├── password-input.tsx │ ├── tabs.tsx │ ├── card.tsx │ ├── accordion.tsx │ ├── input-otp.tsx │ ├── rating.tsx │ ├── calendar.tsx │ ├── breadcrumb.tsx │ ├── json-schema-export.tsx │ ├── drawer.tsx │ ├── credit-card.md │ └── dialog.tsx ├── blocks │ ├── schema.ts │ └── checkbox.tsx ├── magicui │ ├── tweet-client.tsx │ ├── dot-pattern.tsx │ ├── marquee.tsx │ ├── border-beam.tsx │ ├── blur-fade.tsx │ └── ripple.tsx ├── use-raised-shadow.ts ├── editor │ └── block-content.tsx ├── playground │ └── special-component-notice.tsx ├── section.tsx ├── sections │ ├── how-it-works.tsx │ ├── faq.tsx │ ├── logos.tsx │ ├── problem.tsx │ └── testimonials.tsx ├── smile.tsx ├── star-icon.tsx ├── hearth-icon.tsx ├── number-ticker.tsx ├── code.tsx ├── components │ ├── autocomplete-form.tsx │ ├── signature-form.tsx │ ├── components-sidebar.tsx │ ├── location-form.tsx │ └── signature-pad-form.tsx ├── templates │ ├── templates-sidebar.tsx │ ├── code-viewer.tsx │ ├── forgot-password.tsx │ ├── newsletter.tsx │ └── reset-password.tsx ├── footer │ └── index.tsx └── animated-tooltip.tsx ├── providers ├── theme-provider.tsx └── index.tsx ├── .prettierrc ├── constants ├── array-utils.ts ├── components.ts ├── special-components.ts ├── menu.tsx ├── templates.ts └── index.tsx ├── __tests__ ├── isNotEmpty.spec.ts └── cn.spec.ts ├── components.json ├── hooks ├── use-media-query.ts └── use-mobile.tsx ├── .eslintrc.json ├── tailwind.config.js ├── .gitignore ├── tsconfig.json ├── middleware.ts ├── .github └── FUNDING.yml ├── lib ├── validation-utils.ts ├── utils.ts ├── validation-schemas.ts └── json-schema-generator.ts ├── LICENSE ├── types.ts ├── next.config.mjs ├── context └── DropdownContext.tsx ├── screens ├── field-selector │ └── index.tsx ├── form-wrapper │ └── index.tsx ├── form-field-list │ └── index.tsx └── generate-code-parts │ ├── server-actions.tsx │ └── bring-your-own.tsx ├── scripts └── build-registry.ts └── package.json /assets/yt3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanharman/form-builder/HEAD/assets/yt3.png -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanharman/form-builder/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/code.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanharman/form-builder/HEAD/public/code.webp -------------------------------------------------------------------------------- /public/meta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanharman/form-builder/HEAD/public/meta.png -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*?raw' { 2 | const content: string 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanharman/form-builder/HEAD/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /assets/oc-thinking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanharman/form-builder/HEAD/assets/oc-thinking.png -------------------------------------------------------------------------------- /public/validation.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanharman/form-builder/HEAD/public/validation.webp -------------------------------------------------------------------------------- /app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanharman/form-builder/HEAD/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /public/customization.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hasanharman/form-builder/HEAD/public/customization.webp -------------------------------------------------------------------------------- /registry/index.ts: -------------------------------------------------------------------------------- 1 | import { ui } from './registry-components' 2 | 3 | export const registryComponents = [...ui] 4 | -------------------------------------------------------------------------------- /app/(static)/components/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from 'next/navigation' 2 | 3 | export default function ComponentsPage() { 4 | redirect('/components/location-input') 5 | } 6 | -------------------------------------------------------------------------------- /app/(static)/templates/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from 'next/navigation' 2 | 3 | export default function TemplatesPage() { 4 | redirect('/templates/authentication/login') 5 | } 6 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # * 2 | User-agent: * 3 | Allow: / 4 | 5 | # Host 6 | Host: https://www.shadcn-form.com 7 | 8 | # Sitemaps 9 | Sitemap: https://www.shadcn-form.com/sitemap.xml 10 | -------------------------------------------------------------------------------- /app/(static)/playground/page.tsx: -------------------------------------------------------------------------------- 1 | import FormBuilder from '@/screens/form-builder' 2 | import React from 'react' 3 | 4 | export default function TestPage() { 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | '@tailwindcss/postcss': {}, 5 | }, 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://www.shadcn-form.com/sitemap-0.xml 4 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | import react from '@vitejs/plugin-react' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | test: { 7 | environment: 'jsdom', 8 | }, 9 | }) -------------------------------------------------------------------------------- /next-sitemap.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next-sitemap').IConfig} */ 2 | module.exports = { 3 | siteUrl: process.env.SITE_URL || 'https://www.shadcn-form.com', 4 | generateRobotsTxt: true, // (optional) 5 | // ...other options 6 | } 7 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /providers/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { ThemeProvider as NextThemesProvider } from "next-themes" 5 | 6 | export function ThemeProvider({ 7 | children, 8 | ...props 9 | }: React.ComponentProps) { 10 | return {children} 11 | } 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "bracketSameLine": false, 5 | "jsxSingleQuote": false, 6 | "proseWrap": "preserve", 7 | "printWidth": 80, 8 | "quoteProps": "as-needed", 9 | "semi": false, 10 | "singleQuote": true, 11 | "tabWidth": 2, 12 | "trailingComma": "all", 13 | "useTabs": false, 14 | "endOfLine": "auto" 15 | } 16 | -------------------------------------------------------------------------------- /components/blocks/schema.ts: -------------------------------------------------------------------------------- 1 | // components/editor/schema.ts 2 | import { z } from 'zod' 3 | 4 | export const editorFormSchema = z.object({ 5 | title: z.string().min(2, { 6 | message: 'Title must be at least 2 characters.', 7 | }), 8 | content: z.array(z.any()).min(1, { 9 | message: 'Content cannot be empty.', 10 | }), 11 | description: z.string().optional(), 12 | }) 13 | 14 | export type EditorFormValues = z.infer 15 | -------------------------------------------------------------------------------- /app/(static)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Footer } from '@/components/footer' 2 | import Header from '@/components/header' 3 | 4 | interface StaticLayoutProps { 5 | children: React.ReactNode 6 | } 7 | 8 | export default async function Layout({ children }: StaticLayoutProps) { 9 | return ( 10 |
11 |
12 |
{children}
13 |
14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /app/(static)/templates/[category]/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from 'next/navigation' 2 | 3 | interface CategoryPageProps { 4 | params: Promise<{ 5 | category: string 6 | }> 7 | } 8 | 9 | export default async function CategoryPage({ params }: CategoryPageProps) { 10 | const { category } = await params; 11 | // You could add logic here to handle different categories 12 | // For now, we'll redirect all category roots to login 13 | redirect('/templates/authentication/login') 14 | } 15 | -------------------------------------------------------------------------------- /constants/array-utils.ts: -------------------------------------------------------------------------------- 1 | export function removeItem([...arr]: T[], item: T) { 2 | const index = arr.indexOf(item); 3 | index > -1 && arr.splice(index, 1); 4 | return arr; 5 | } 6 | 7 | export function closestItem(arr: T[], item: T) { 8 | const index = arr.indexOf(item); 9 | if (index === -1) { 10 | return arr[0]; 11 | } else if (index === arr.length - 1) { 12 | return arr[arr.length - 2]; 13 | } else { 14 | return arr[index + 1]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/[...not_found]/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' // {{ edit_1 }} 2 | import Link from 'next/link' 3 | 4 | export default function NotFound() { 5 | return ( 6 |
7 |

Not Found

8 |

Could not find requested resource

9 | 10 | Return Home 11 | 12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /__tests__/isNotEmpty.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | import { isNotEmpty } from '../lib/utils' 3 | 4 | describe('isNotEmpty', () => { 5 | it('should return true for non-empty strings', () => { 6 | expect(isNotEmpty('hello')).toBe(true); 7 | }); 8 | 9 | it('should return false for empty strings', () => { 10 | expect(isNotEmpty('')).toBe(false); 11 | }); 12 | 13 | it('should return false for strings with only spaces', () => { 14 | expect(isNotEmpty(' ')).toBe(false); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks", 19 | "form": "@/components/form" 20 | } 21 | } -------------------------------------------------------------------------------- /hooks/use-media-query.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | 3 | export const useMediaQuery = (query: string): boolean => { 4 | const [matches, setMatches] = useState(false) 5 | 6 | useEffect(() => { 7 | const media = window.matchMedia(query) 8 | if (media.matches !== matches) { 9 | setMatches(media.matches) 10 | } 11 | const listener = () => setMatches(media.matches) 12 | window.addEventListener('resize', listener) 13 | return () => window.removeEventListener('resize', listener) 14 | }, [matches, query]) 15 | 16 | return matches 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": ["*.ts", "*.tsx"], 5 | "parser": "@typescript-eslint/parser" 6 | } 7 | ], 8 | "rules": { 9 | "@typescript-eslint/no-empty-interface": "off", 10 | "@typescript-eslint/no-unused-vars": "off", 11 | "@typescript-eslint/no-explicit-any": "off", 12 | "@typescript-eslint/no-empty-object-type": "off", 13 | "@typescript-eslint/no-unused-expressions": "off" 14 | }, 15 | "extends": [ 16 | "next/core-web-vitals", 17 | "next/typescript", 18 | "plugin:@typescript-eslint/recommended" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './components/**/*.{js,ts,jsx,tsx,mdx}', 6 | './app/**/*.{js,ts,jsx,tsx,mdx}', 7 | './screens/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | safelist: [ 10 | 'col-span-1', 11 | 'col-span-2', 12 | 'col-span-3', 13 | 'col-span-4', 14 | 'col-span-5', 15 | 'col-span-6', 16 | 'col-span-7', 17 | 'col-span-8', 18 | 'col-span-9', 19 | 'col-span-10', 20 | 'col-span-11', 21 | 'col-span-12', 22 | ], 23 | } 24 | -------------------------------------------------------------------------------- /hooks/use-mobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined) 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 | } 13 | mql.addEventListener("change", onChange) 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 | return () => mql.removeEventListener("change", onChange) 16 | }, []) 17 | 18 | return !!isMobile 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # env variables 39 | 40 | .env 41 | .env.production 42 | .env.development 43 | 44 | # Ignore for WebStorm configuration 45 | .idea -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { cn } from '@/lib/utils' 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.TextareaHTMLAttributes 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |