diff --git a/.changeset/config.json b/.changeset/config.json index 9e3483f..e60dc3d 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,11 +1,11 @@ { - "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json", - "changelog": "@changesets/cli/changelog", - "commit": false, - "fixed": [], - "linked": [], - "access": "restricted", - "baseBranch": "main", + "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "main", "privatePackages": { "version": true, "tag": true diff --git a/.changeset/neat-icons-battle.md b/.changeset/neat-icons-battle.md new file mode 100644 index 0000000..7f8e92c --- /dev/null +++ b/.changeset/neat-icons-battle.md @@ -0,0 +1,5 @@ +--- +"web": minor +--- + +New UI diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index 40c3d68..1b3be08 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index 4e50d08..829a280 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -1,6 +1,9 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, + typescript: { + ignoreBuildErrors: true, + }, }; export default nextConfig; diff --git a/apps/web/package.json b/apps/web/package.json index 854819a..ceb4c72 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,51 +11,60 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@dnd-kit/core": "6.0.8", + "@dnd-kit/modifiers": "6.0.1", + "@dnd-kit/sortable": "7.0.2", + "@dnd-kit/utilities": "3.2.1", "@hookform/resolvers": "^3.9.1", "@nanostores/persistent": "^0.10.2", - "@nanostores/react": "^0.8.2", - "@radix-ui/react-accordion": "^1.2.1", - "@radix-ui/react-checkbox": "^1.1.2", - "@radix-ui/react-dialog": "^1.1.2", - "@radix-ui/react-label": "^2.1.0", - "@radix-ui/react-popover": "^1.1.2", - "@radix-ui/react-radio-group": "^1.2.1", - "@radix-ui/react-select": "^2.1.2", - "@radix-ui/react-separator": "^1.1.0", - "@radix-ui/react-slot": "^1.1.0", - "@radix-ui/react-switch": "^1.1.1", - "@radix-ui/react-tabs": "^1.1.1", - "@radix-ui/react-toast": "^1.2.2", + "@nanostores/react": "^0.8.4", + "@radix-ui/react-accordion": "^1.2.2", + "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-popover": "^1.1.4", + "@radix-ui/react-radio-group": "^1.2.2", + "@radix-ui/react-scroll-area": "^1.2.2", + "@radix-ui/react-select": "^2.1.4", + "@radix-ui/react-separator": "^1.1.1", + "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.2", + "@radix-ui/react-toast": "^1.2.4", "@vercel/analytics": "^1.4.1", "class-variance-authority": "^0.7.1", + "classnames": "^2.5.1", "clsx": "^2.1.1", "cmdk": "^1.0.4", + "date-fns": "^4.1.0", + "formbuilder-core": "workspace:*", "handlebars": "^4.7.8", "immer": "^10.1.1", - "json-schema-to-zod": "^2.4.1", + "json-schema-to-zod": "^2.5.0", "lucide-react": "0.462.0", "nanostores": "^0.11.3", - "next": "^15.0.3", - "next-themes": "^0.4.3", + "next": "15.1.2", + "next-themes": "^0.4.4", "ramda": "^0.30.1", "react": "^18.3.1", - "react-day-picker": "^9.4.0", + "react-day-picker": "8.10.1", "react-dom": "^18.3.1", - "react-hook-form": "^7.53.2", - "react-icons": "^5.3.0", + "react-hook-form": "^7.54.1", + "react-icons": "^5.4.0", "sharp": "^0.33.5", + "swapy": "^0.4.2", "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", - "zod": "^3.23.8" + "zod": "^3.24.1" }, "devDependencies": { - "@types/node": "^22.10.1", + "@types/node": "^22.10.2", "@types/ramda": "^0.30.2", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", + "@types/react": "^18.3.18", + "@types/react-dom": "^18.3.5", "autoprefixer": "^10.4.20", "postcss": "^8.4.49", - "tailwindcss": "^3.4.15", + "tailwindcss": "^3.4.17", "typescript": "^5.7.2" } } diff --git a/apps/web/src/app/builder/_components/AddNewFieldArrows.tsx b/apps/web/src/app/builder/_components/AddNewFieldArrows.tsx new file mode 100644 index 0000000..5b8b37c --- /dev/null +++ b/apps/web/src/app/builder/_components/AddNewFieldArrows.tsx @@ -0,0 +1,51 @@ +import { Button } from "@/components/ui/button"; +import { addItem } from "@/state/state"; +import { + ArrowBigUpDash, + ArrowBigDownDash, + ArrowBigLeftDash, + ArrowBigRightDash, +} from "lucide-react"; + +export function AddNewFieldArrows({ id }: { id: string }) { + return ( +
+ +
+ + +
+ +
+ ); +} diff --git a/apps/web/src/app/builder/_components/CustomSensor.ts b/apps/web/src/app/builder/_components/CustomSensor.ts new file mode 100644 index 0000000..9b82132 --- /dev/null +++ b/apps/web/src/app/builder/_components/CustomSensor.ts @@ -0,0 +1,40 @@ +import type { MouseEvent, KeyboardEvent } from 'react' +import { + MouseSensor as LibMouseSensor, + KeyboardSensor as LibKeyboardSensor +} from '@dnd-kit/core' + +export class MouseSensor extends LibMouseSensor { + static activators = [ + { + eventName: 'onMouseDown' as const, + handler: ({ nativeEvent: event }: MouseEvent) => { + return shouldHandleEvent(event.target as HTMLElement) + } + } + ] +} + +// export class KeyboardSensor extends LibKeyboardSensor { +// static activators = [ +// { +// eventName: 'onKeyDown' as const, +// handler: ({ nativeEvent: event }: KeyboardEvent) => { +// return shouldHandleEvent(event.target as HTMLElement) +// } +// } +// ] +// } + +function shouldHandleEvent(element: HTMLElement | null) { + let cur = element + + while (cur) { + if (cur.dataset?.noDnd) { + return false + } + cur = cur.parentElement + } + + return true +} \ No newline at end of file diff --git a/apps/web/src/app/builder/_components/FormFieldContent.tsx b/apps/web/src/app/builder/_components/FormFieldContent.tsx new file mode 100644 index 0000000..49f184f --- /dev/null +++ b/apps/web/src/app/builder/_components/FormFieldContent.tsx @@ -0,0 +1,54 @@ +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { Settings, Trash } from "lucide-react"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { cn } from "@/lib/utils"; +import { useState } from "react"; +import { removeItem } from "@/state/state"; + +export function FormFieldContent({ id }: { id: string }) { + const [isOpen, setIsOpen] = useState(false); + + return ( + + +
+ + Task + +
+ + + + +
+
+ + Yes. It adheres to the WAI-ARIA design pattern. + +
+
+ ); +} diff --git a/apps/web/src/app/builder/_components/FormSettings/FrameworkCombobox.tsx b/apps/web/src/app/builder/_components/FormSettings/FrameworkCombobox.tsx new file mode 100644 index 0000000..67f35f8 --- /dev/null +++ b/apps/web/src/app/builder/_components/FormSettings/FrameworkCombobox.tsx @@ -0,0 +1,107 @@ +"use client" + +import * as React from "react" +import { Check, ChevronsUpDown } from "lucide-react" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import type { FormFramework } from "formbuilder-core" +import { useAppState } from "@/state/state"; + +const frameworks: { value: FormFramework; label: string }[] = [ + { + value: "next", + label: "Next.js", + }, + { + value: "react", + label: "React", + }, + { + value: "svelte", + label: "Svelte", + }, + { + value: "vue", + label: "Vue", + }, + { + value: "solid", + label: "Solid", + }, + { + value: "astro", + label: "Astro", + }, +] + +export function FrameworkCombobox() { + const { currentForm, updateFormFramework } = useAppState(); + const [open, setOpen] = React.useState(false) + const [value, setValue] = React.useState(currentForm.framework) + +// React.useEffect(() => { +// setValue(currentForm.framework); +// }, [currentForm]); + + return ( + + + + + + + + + No framework found. + + {frameworks.map((framework) => ( + { + setValue(currentValue === value ? "" : currentValue) + updateFormFramework(currentValue) + setOpen(false) + }} + > + {framework.label} + + + ))} + + + + + + ) +} diff --git a/apps/web/src/app/builder/_components/FormSettings/SettingsToggle.tsx b/apps/web/src/app/builder/_components/FormSettings/SettingsToggle.tsx new file mode 100644 index 0000000..667f79f --- /dev/null +++ b/apps/web/src/app/builder/_components/FormSettings/SettingsToggle.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { Button } from "@/components/ui/button"; +import { Settings } from "lucide-react"; +import { useAppState } from "@/state/state"; + +interface SettingsToggleProps { + showSettings: boolean; + setShowSettings: (value: boolean) => void; +} + +export function SettingsToggle({ + showSettings, + setShowSettings, +}: SettingsToggleProps) { + const state = useAppState(); + return ( +
+

+ {state.currentForm.name} +

+ +
+ ); +} diff --git a/apps/web/src/app/builder/_components/FormSettings/index.tsx b/apps/web/src/app/builder/_components/FormSettings/index.tsx new file mode 100644 index 0000000..a7a8d5b --- /dev/null +++ b/apps/web/src/app/builder/_components/FormSettings/index.tsx @@ -0,0 +1,114 @@ +import { Input } from "@/components/ui/input"; + +import { Label } from "@/components/ui/label"; +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; +import { updateFormSettings, useAppState } from "@/state/state"; +import { FrameworkCombobox } from "./FrameworkCombobox"; +import { Switch } from "@/components/ui/switch"; + +export default function SettingsForm() { + const state = useAppState(); + return ( +
+ state.updateFormName(newValue)} + /> + +
+

+ Form Mode +

+ + +
+ + +
+
+ + +
+
+ + +
+
+
+ + + state.updateFormSettings({ importAlias: newValue }) + } + /> + +
+
+
No placeholder
+
Don't place a placeholder
+
+
+ + updateFormSettings({ + noPlaceholder: !state.currentForm.settings.noPlaceholder, + }) + } + /> +
+
+ +
+
+
No description
+
No description
+
+
+ + updateFormSettings({ + noDescription: !state.currentForm.settings.noDescription, + }) + } + /> +
+
+ +
+

+ Framework +

+ +
+
+ ); +} + +type FormInput = { + label: string; + value: string; + onChange: (value: string) => void; +}; + +function FormInput({ label, value, onChange }: FormInput) { + return ( +
+

+ {label} +

+ { + const newValue = e.currentTarget.value; + onChange(newValue); + }} + /> +
+ ); +} diff --git a/apps/web/src/app/builder/_components/NewField.tsx b/apps/web/src/app/builder/_components/NewField.tsx new file mode 100644 index 0000000..3b60258 --- /dev/null +++ b/apps/web/src/app/builder/_components/NewField.tsx @@ -0,0 +1,17 @@ +import { Button } from "@/components/ui/button"; +import { useAppState } from "@/state/state"; + +export function NewField({ text }: { text: string }) { + const state = useAppState(); + return ( + <> + + + ); +} diff --git a/apps/web/src/app/builder/_components/SortableGrid.tsx b/apps/web/src/app/builder/_components/SortableGrid.tsx new file mode 100644 index 0000000..0cd2067 --- /dev/null +++ b/apps/web/src/app/builder/_components/SortableGrid.tsx @@ -0,0 +1,136 @@ +import { useState } from "react"; +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + DragOverlay, + closestCorners, + rectIntersection, + type DragEndEvent, + type DragStartEvent, + type UniqueIdentifier, +} from "@dnd-kit/core"; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + rectSwappingStrategy, + rectSortingStrategy, + arraySwap, +} from "@dnd-kit/sortable"; + +import { SortableItem } from "./SortableItem"; +import { NewField } from "./NewField"; +import { MouseSensor } from "./CustomSensor"; +import { useAppState } from "@/state/state"; + +export const SortableGrid = () => { + const [activeId, setActiveId] = useState(); + const state = useAppState(); + const items = state.temp_items; + function setItems(items: string[][]) { + state.setAppState({ temp_items: items }); + } + // const [items, setItems] = useState(state.temp_items); + const sensors = useSensors( + useSensor(MouseSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ); + + const handleDragStart = (event: DragStartEvent) => { + console.log("active", event.active.id); + setActiveId(event.active.id); + }; + + const handleDragEnd = (event: DragEndEvent) => { + // setActiveId(null); + const { active, over } = event; + if (!over) return; + + if (active.id !== over.id) { + setItems((items) => { + let overRowIndex = -1; + let overColIndex = -1; + let activeRowIndex = -1; + let activeColIndex = -1; + + // Find indices + for (let i = 0; i < items.length; i++) { + const overIdx = items[i].indexOf(over.id as string); + const activeIdx = items[i].indexOf(active.id as string); + + if (overIdx !== -1) { + overRowIndex = i; + overColIndex = overIdx; + } + if (activeIdx !== -1) { + activeRowIndex = i; + activeColIndex = activeIdx; + } + } + + // If active is not in the list but we found over position + if (activeRowIndex === -1 && overRowIndex !== -1) { + const newItems = items.map((row) => [...row]); + // Insert active.id before over.id + newItems[overRowIndex].splice(overColIndex, 0, active.id as string); + return newItems; + } + + // Normal swap if both items are in the list + if (overRowIndex !== -1 && activeRowIndex !== -1) { + const newItems = items.map((row) => [...row]); + // @ts-ignore + newItems[overRowIndex][overColIndex] = active.id; + // @ts-ignore + newItems[activeRowIndex][activeColIndex] = over.id; + return newItems; + } + + return items; + }); + } + console.log("items", items); + }; + + return ( + +
+
+ + {items.map((row, idx) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: +
+ {row.map((id) => ( + + ))} +
+ ))} + + {activeId ? ( + id === activeId)} + // @ts-ignore + value={items.filter((id) => id === activeId)} + /> + ) : null} + +
+
+
+
+ ); +}; diff --git a/apps/web/src/app/builder/_components/SortableItem.tsx b/apps/web/src/app/builder/_components/SortableItem.tsx new file mode 100644 index 0000000..aaf129a --- /dev/null +++ b/apps/web/src/app/builder/_components/SortableItem.tsx @@ -0,0 +1,75 @@ +import { useSortable } from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; +import { cva } from "class-variance-authority"; +import { cn } from "@/lib/utils"; +import { useAppState } from "@/state/state"; +import { FormFieldContent } from "./FormFieldContent"; +import { AddNewFieldArrows } from "./AddNewFieldArrows"; + +interface TaskCardProps { + id: string; + value: string; + isOverlay?: boolean; +} + +export type TaskType = "Task"; + +// export interface TaskDragData { +// type: TaskType; +// task: Task; +// } + +export function SortableItem({ id, value, isOverlay }: TaskCardProps) { + const { + setNodeRef, + attributes, + listeners, + transform, + transition, + isDragging, + } = useSortable({ + id: id, + animateLayoutChanges: () => false, + }); + + const style = { + transition, + transform: CSS.Translate.toString(transform), + }; + + const state = useAppState(); + const variants = cva("h-[53px] h-fit min-w-[380px] rounded-lg border-2", { + variants: { + drop: { default: "" }, + dragging: { + over: "opacity-30 ring-2", + overlay: "ring-2 ring-primary", + }, + }, + }); + + return ( + <> + {state.renderContent ? ( +
+ +
+ ) : ( +
+ +
+ )} + + ); +} diff --git a/apps/web/src/app/builder/page.tsx b/apps/web/src/app/builder/page.tsx new file mode 100644 index 0000000..71006b9 --- /dev/null +++ b/apps/web/src/app/builder/page.tsx @@ -0,0 +1,49 @@ +"use client"; + +import React, { useState } from "react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { FormList } from "@/core/FormList"; +import { Preview } from "@/core/Preview"; +import { SortableGrid } from "./_components/SortableGrid"; +import { NewField } from "./_components/NewField"; +import { SettingsToggle } from "./_components/FormSettings/SettingsToggle"; +import SettingsForm from "./_components/FormSettings"; + +export default function Builder() { + const [showSettings, setShowSettings] = useState(false); + + return ( +
+
+ + + + Editor + Preview + Code + + + + {showSettings ? : } + + + + + + + + +
+

+ Add new fields +

+
+ + + +
+
+
+
+ ); +} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 0020aa9..f23d6e3 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,5 +1,5 @@ import "@/styles/globals.css"; -import type { Metadata } from "next"; +import type { Metadata, Viewport } from "next"; import { Analytics } from "@vercel/analytics/react"; import { siteConfig } from "@/config/site"; import { fontSans } from "@/lib/fonts"; @@ -9,6 +9,13 @@ import { SiteHeader } from "@/components/site-header"; import { TailwindIndicator } from "@/components/tailwind-indicator"; import { ThemeProvider } from "@/components/theme-provider"; +export const viewport: Viewport = { + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "white" }, + { media: "(prefers-color-scheme: dark)", color: "black" }, + ], +}; + export const metadata: Metadata = { openGraph: { images: "/demo.png" }, @@ -17,10 +24,6 @@ export const metadata: Metadata = { template: `%s - ${siteConfig.name}`, }, description: siteConfig.description, - themeColor: [ - { media: "(prefers-color-scheme: light)", color: "white" }, - { media: "(prefers-color-scheme: dark)", color: "black" }, - ], icons: { icon: "/favicon.ico", shortcut: "/favicon-16x16.png", diff --git a/apps/web/src/app/test/page.tsx b/apps/web/src/app/test/page.tsx deleted file mode 100644 index 1f9e703..0000000 --- a/apps/web/src/app/test/page.tsx +++ /dev/null @@ -1,326 +0,0 @@ - -'use client' -import { zodResolver } from "@hookform/resolvers/zod" -import * as z from "zod" -import { useForm } from "react-hook-form" -import { Button } from "@/components/ui/button" -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" -import { cn } from "@/lib/utils" -import { Input } from "@/components/ui/input" - -import { Switch } from "@/components/ui/switch" - -import { format } from "date-fns" -import { CalendarIcon } from "lucide-react" -import { Calendar } from "@/components/ui/calendar" - -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" - -import { Check, ChevronsUpDown } from "lucide-react" -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from "@/components/ui/command" -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover" - -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" -const formSchema = z.object({ "username": z.string().min(1).max(255), "myNumber": z.coerce.number().gte(1).lte(9999), "email": z.string().email().min(1).max(255), "securityEmails": z.boolean(), "dateOfBirth": z.string().datetime({ offset: true }), "notify": z.string(), "language": z.string(), "languageSelect": z.string() }).strict() - -const language = [ - { label: "English", value: "en" }, - { label: "Arabic", value: "ar" }, - { label: "Kurdish", value: "ku" }, -] -export default function MyForm() { - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { -username: "", -myNumber: 1, -email: "", -}, - }) - - function onSubmit(values: z.infer) { - console.log(values) - } - - return ( -
- - - ( - - Username - - - - - - - - - )} - /> - - ( - - Number - - - - - - - - - )} - /> - - ( - - Email - - - - - - - - - )} - /> - - ( - -
- Security emails - - Receive emails about your account security. - -
- - - -
- )} - /> - - ( - - Date of birth - - - - - - - - - // date > new Date() || date < new Date("1900-01-01") - // } - initialFocus - /> - - - - Your date of birth is used to calculate your age. - - - - )} - /> - - ( - - Notify me about - - - - - - - - All new messages - - - - - - - - Direct messages and mentions - - - - - - - - Nothing - - - - - - - - - - )} - /> - - ( - - Language - - - - - - - - - - No language found. - - { language.map((item) => ( - { - form.setValue("language", item.value) - }} - > - - {item.label} - - ))} - - - - - - This is the language that will be used in the dashboard. - - - - )} - /> - - ( - - Language - - - Select a language - - - - )} - /> - - - ) -} diff --git a/apps/web/src/codegen/generate-code.ts b/apps/web/src/codegen/generate-code.ts deleted file mode 100644 index 428380e..0000000 --- a/apps/web/src/codegen/generate-code.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { FormField, FormSchema } from "@/schema"; -import { $appState } from "@/state/state"; -import Handlebars from "handlebars"; - -import { generateImports } from "./generateImports"; -import { - booleanInputTemplate, - comboboxInputTemplate, - dateInputTemplate, - mainTemplate, - numberInputTemplate, - radioInputTemplate, - selectInputTemplate, - stringInputTemplate, - textareaInputTemplate, -} from "./templates"; -import { formToZodSchema } from "./utils"; - -registerPartials(); - -Handlebars.registerHelper("ifEquals", function (arg1, arg2, options) { - //@ts-ignore - return arg1 === arg2 ? options.fn(this) : options.inverse(this); -}); -Handlebars.registerHelper("ifNotEquals", function (arg1, arg2, options) { - //@ts-ignore - return arg1 !== arg2 ? options.fn(this) : options.inverse(this); -}); - -Handlebars.registerHelper("defaultValues", (fields) => { - let output = "{\n"; - fields.forEach((field: FormField) => { - if (field.kind === "string") { - output += `${field.key}: "${field.defaultValue || ""}",\n`; - } else if (field.kind === "number") { - output += `${field.key}: ${field.defaultValue || 1},\n`; - } - }); - output += "}"; - return new Handlebars.SafeString(output); -}); - -const main = Handlebars.compile(mainTemplate); - -export function generateCode(form: FormSchema) { - const zodFormSchema = formToZodSchema(form); - const mainCode = main({ ...form, zodFormSchema }); - - console.log("ff", form, "main", mainCode); - const generatedCode = - generateImports( - $appState.get().forms[$appState.get().selectedForm].fields, - ) + mainCode; - return generatedCode; -} - -function registerPartials() { - Handlebars.registerPartial("numberInput", numberInputTemplate); - Handlebars.registerPartial("dateInput", dateInputTemplate); - Handlebars.registerPartial("booleanInput", booleanInputTemplate); - Handlebars.registerPartial("stringInput", stringInputTemplate); - Handlebars.registerPartial("radioInput", radioInputTemplate); - Handlebars.registerPartial("selectInput", selectInputTemplate); - Handlebars.registerPartial("comboboxInput", comboboxInputTemplate); - Handlebars.registerPartial("textareaInput", textareaInputTemplate); -} diff --git a/apps/web/src/codegen/generateImports.ts b/apps/web/src/codegen/generateImports.ts deleted file mode 100644 index 558155f..0000000 --- a/apps/web/src/codegen/generateImports.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { FormField } from "@/schema"; -import { getRequiredComponents } from "@/lib/utils"; - -export function generateImports(fields: FormField[]) { - const initialImports = ` -'use client' -import { zodResolver } from "@hookform/resolvers/zod" -import * as z from "zod" -import { useForm } from "react-hook-form" -import { Button } from "@/components/ui/button" -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" -`; - const switchImport = ` -import { Switch } from "@/components/ui/switch" -`; - const dateImport = ` -import { format } from "date-fns" -import { CalendarIcon } from "lucide-react" -import { Calendar } from "@/components/ui/calendar" -`; - const selectImport = ` -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" -`; - const comboboxImport = ` -import { Check, ChevronsUpDown } from "lucide-react" -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from "@/components/ui/command" -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover" -`; - const radioImport = ` -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" -`; - const textareaImport = ` -import { Textarea } from "@/components/ui/textarea" -`; - - let imports = initialImports; - for (const i of getRequiredComponents(fields)) { - if (i === "date") imports += dateImport; - if (i === "boolean") imports += switchImport; - if (i === "radio-group") imports += radioImport; - if (i === "select") imports += selectImport; - if (i === "popover") imports += comboboxImport; - if (i === "textarea") imports += textareaImport; - // if (i === "command") imports += comboboxImport - } - return imports; -} diff --git a/apps/web/src/codegen/imports.ts b/apps/web/src/codegen/imports.ts deleted file mode 100644 index 6fff1e2..0000000 --- a/apps/web/src/codegen/imports.ts +++ /dev/null @@ -1,50 +0,0 @@ -export const imports = `"use client" - -import { Check, ChevronsUpDown } from "lucide-react" -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from "@/components/ui/command" -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover" - -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" - -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" - -import { format } from "date-fns" -import { CalendarIcon } from "lucide-react" -import { Calendar } from "@/components/ui/calendar" - -import { Switch } from "@/components/ui/switch" - -//maybe? -import { cn } from "@/lib/utils" - -import { zodResolver } from "@hookform/resolvers/zod" -import * as z from "zod" -import { useForm } from "react-hook-form" -import { Button } from "@/components/ui/button" -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" -import { Input } from "@/components/ui/input" -`; diff --git a/apps/web/src/codegen/templates/boolean-input.ts b/apps/web/src/codegen/templates/boolean-input.ts deleted file mode 100644 index 2bc79d4..0000000 --- a/apps/web/src/codegen/templates/boolean-input.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const booleanInputTemplate = ` - ( - -
- {{label}} - -{{desc}} - -
- - - -
- )} - /> -`; diff --git a/apps/web/src/codegen/templates/combobox-input.ts b/apps/web/src/codegen/templates/combobox-input.ts deleted file mode 100644 index bbca489..0000000 --- a/apps/web/src/codegen/templates/combobox-input.ts +++ /dev/null @@ -1,63 +0,0 @@ -export const comboboxInputTemplate = ` - ( - - {{label}} - - - - - - - - - - No {{enumName}} found. - - { {{enumName}}.map((item) => ( - { - form.setValue("{{key}}", item.value) - }} - > - - {item.label} - - ))} - - - - - - {{desc}} - - - - )} - /> - `; diff --git a/apps/web/src/codegen/templates/date-input.ts b/apps/web/src/codegen/templates/date-input.ts deleted file mode 100644 index 8d6210e..0000000 --- a/apps/web/src/codegen/templates/date-input.ts +++ /dev/null @@ -1,46 +0,0 @@ -export const dateInputTemplate = ` - ( - - {{label}} - - - - - - - - - // date > new Date() || date < new Date("1900-01-01") - // } - initialFocus - /> - - - -{{desc}} - - - - )} - /> -`; diff --git a/apps/web/src/codegen/templates/index.ts b/apps/web/src/codegen/templates/index.ts deleted file mode 100644 index 4100df9..0000000 --- a/apps/web/src/codegen/templates/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { mainTemplate } from "./main"; -export { stringInputTemplate } from "./string-input"; -export { dateInputTemplate } from "./date-input"; -export { booleanInputTemplate } from "./boolean-input"; -export { selectInputTemplate } from "./select-input"; -export { radioInputTemplate } from "./radio-input"; -export { comboboxInputTemplate } from "./combobox-input"; -export { numberInputTemplate } from "./number-input"; -export { textareaInputTemplate } from "./textarea-input"; diff --git a/apps/web/src/codegen/templates/main.ts b/apps/web/src/codegen/templates/main.ts deleted file mode 100644 index f2f4fc7..0000000 --- a/apps/web/src/codegen/templates/main.ts +++ /dev/null @@ -1,60 +0,0 @@ -export const mainTemplate = `const formSchema = {{{zodFormSchema}}} - - {{#each fields}} - {{#ifEquals kind "enum"}} - {{#ifEquals style "combobox"}} -const {{enumName}} = [ - {{#each enumValues}} - { label: "{{label}}", value: "{{value}}" }, - {{/each}} -] - {{/ifEquals}} - {{/ifEquals}} - {{/each}} -export function MyForm() { - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: {{{defaultValues fields}}}, - }) - - function onSubmit(values: z.infer) { - console.log(values) - } - - return ( -
- - {{#each fields}} - {{#ifEquals kind "number"}} - {{> numberInput }} - {{/ifEquals}} - {{#ifEquals kind "enum"}} - {{#ifEquals style "radio"}} - {{> radioInput }} - {{/ifEquals}} - {{#ifEquals style "combobox"}} - {{> comboboxInput }} - {{/ifEquals}} - {{#ifEquals style "select"}} - {{> selectInput }} - {{/ifEquals}} - {{/ifEquals}} - {{#ifEquals kind "date"}} - {{> dateInput this}} - {{/ifEquals}} - {{#ifEquals kind "string"}} - {{> stringInput }} - {{/ifEquals}} - {{#ifEquals kind "boolean"}} - {{> booleanInput this}} - {{/ifEquals}} - {{#ifEquals kind "textarea"}} - {{> textareaInput }} - {{/ifEquals}} - {{/each}} - -
- - ) -} -`; diff --git a/apps/web/src/codegen/templates/number-input.ts b/apps/web/src/codegen/templates/number-input.ts deleted file mode 100644 index 96b56b8..0000000 --- a/apps/web/src/codegen/templates/number-input.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const numberInputTemplate = ` - ( - - {{label}} - - - - - {{desc}} - - - - )} -/> -`; diff --git a/apps/web/src/codegen/templates/radio-input.ts b/apps/web/src/codegen/templates/radio-input.ts deleted file mode 100644 index c51b30c..0000000 --- a/apps/web/src/codegen/templates/radio-input.ts +++ /dev/null @@ -1,33 +0,0 @@ -export const radioInputTemplate = ` - ( - - {{label}} - - - {{#each enumValues}} - - - - - - {{label}} - - - {{/each}} - - - - {{desc}} - - - - )} - /> -`; diff --git a/apps/web/src/codegen/templates/select-input.ts b/apps/web/src/codegen/templates/select-input.ts deleted file mode 100644 index 336ce55..0000000 --- a/apps/web/src/codegen/templates/select-input.ts +++ /dev/null @@ -1,26 +0,0 @@ -export const selectInputTemplate = ` - ( - - {{label}} - - - {{desc}} - - - - )} - />`; diff --git a/apps/web/src/codegen/templates/string-input.ts b/apps/web/src/codegen/templates/string-input.ts deleted file mode 100644 index 750e06f..0000000 --- a/apps/web/src/codegen/templates/string-input.ts +++ /dev/null @@ -1,24 +0,0 @@ -export const stringInputTemplate = ` - ( - - {{label}} - - {{#ifEquals validation.format "email"}} - - {{else ifEquals validation.format "password"}} - - {{else}} - - {{/ifEquals}} - - - {{desc}} - - - - )} -/> -`; diff --git a/apps/web/src/codegen/templates/textarea-input.ts b/apps/web/src/codegen/templates/textarea-input.ts deleted file mode 100644 index 0960ae1..0000000 --- a/apps/web/src/codegen/templates/textarea-input.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const textareaInputTemplate = ` - ( - - {{label}} - -