From 82f4c0f9884c7fea5bfff65b7ffa271695b03696 Mon Sep 17 00:00:00 2001 From: KryptXBSA Date: Wed, 25 Dec 2024 11:17:22 +0300 Subject: [PATCH 01/22] add mock form --- .../builder/_components/AddNewFieldArrows.tsx | 3 +- .../builder/_components/FormFieldContent.tsx | 10 +- .../_components/FormSettings/index.tsx | 25 +++- .../src/app/builder/_components/NewField.tsx | 10 +- .../app/builder/_components/SortableGrid.tsx | 85 ++++++------ apps/web/src/app/builder/page.tsx | 17 ++- apps/web/src/mock/mockForm.ts | 121 ++++++++++++++++++ apps/web/src/state/state.ts | 118 ++++++++++------- apps/web/src/utils/findFieldIndex.ts | 8 +- biome.json | 1 + packages/core/src/index.ts | 1 + packages/core/src/types/index.ts | 28 ++-- packages/core/src/types/prettify.ts | 5 + 13 files changed, 312 insertions(+), 120 deletions(-) create mode 100644 apps/web/src/mock/mockForm.ts create mode 100644 packages/core/src/types/prettify.ts diff --git a/apps/web/src/app/builder/_components/AddNewFieldArrows.tsx b/apps/web/src/app/builder/_components/AddNewFieldArrows.tsx index 5b8b37c..5468114 100644 --- a/apps/web/src/app/builder/_components/AddNewFieldArrows.tsx +++ b/apps/web/src/app/builder/_components/AddNewFieldArrows.tsx @@ -1,5 +1,5 @@ import { Button } from "@/components/ui/button"; -import { addItem } from "@/state/state"; +import { useAppState } from "@/state/state"; import { ArrowBigUpDash, ArrowBigDownDash, @@ -8,6 +8,7 @@ import { } from "lucide-react"; export function AddNewFieldArrows({ id }: { id: string }) { + const { addItem } = useAppState(); return (
); diff --git a/apps/web/src/app/builder/_components/SortableGrid.tsx b/apps/web/src/app/builder/_components/SortableGrid.tsx index b4dba5e..3db5851 100644 --- a/apps/web/src/app/builder/_components/SortableGrid.tsx +++ b/apps/web/src/app/builder/_components/SortableGrid.tsx @@ -23,18 +23,14 @@ import { } from "@dnd-kit/sortable"; import { SortableItem } from "./SortableItem"; -import { NewField } from "./NewField"; import { MouseSensor } from "./CustomSensor"; import { useAppState } from "@/state/state"; +import type { FormField } from "formbuilder-core"; 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 items = state.currentForm.fields; const sensors = useSensors( useSensor(MouseSensor), useSensor(KeyboardSensor, { @@ -43,7 +39,6 @@ export const SortableGrid = () => { ); const handleDragStart = (event: DragStartEvent) => { - console.log("active", event.active.id); setActiveId(event.active.id); }; @@ -53,47 +48,45 @@ export const SortableGrid = () => { if (!over) return; if (active.id !== over.id) { - setItems((items) => { - let overRowIndex = -1; - let overColIndex = -1; - let activeRowIndex = -1; - let activeColIndex = -1; + 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); + // Find indices + for (let i = 0; i < items.length; i++) { + const overIdx = items[i].findIndex((field) => field.id === over.id); + const activeIdx = items[i].findIndex((field) => field.id === active.id); - if (overIdx !== -1) { - overRowIndex = i; - overColIndex = overIdx; - } - if (activeIdx !== -1) { - activeRowIndex = i; - activeColIndex = activeIdx; - } + if (overIdx !== -1) { + overRowIndex = i; + overColIndex = overIdx; } - - // 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; + if (activeIdx !== -1) { + activeRowIndex = i; + activeColIndex = activeIdx; } + } - // 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; - } + // 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; - }); + return items; } console.log("items", items); }; @@ -111,8 +104,12 @@ export const SortableGrid = () => { {items.map((row, idx) => (
- {row.map((id) => ( - + {row.map((formField) => ( + ))}
))} diff --git a/apps/web/src/app/builder/page.tsx b/apps/web/src/app/builder/page.tsx index 71006b9..26ed729 100644 --- a/apps/web/src/app/builder/page.tsx +++ b/apps/web/src/app/builder/page.tsx @@ -23,7 +23,10 @@ export default function Builder() { Code - + {showSettings ? : } @@ -33,14 +36,16 @@ export default function Builder() { -
+

- Add new fields + Add Field

- - - + + + + +
diff --git a/apps/web/src/mock/mockForm.ts b/apps/web/src/mock/mockForm.ts new file mode 100644 index 0000000..fa0efed --- /dev/null +++ b/apps/web/src/mock/mockForm.ts @@ -0,0 +1,121 @@ +import type { FormSchema } from "formbuilder-core"; + +export const mockForm: FormSchema = { + id: 1, + settings: { + importAlias: "default", + mode: "default", + noDescription: false, + noPlaceholder: false, + }, + framework: "next", + name: "User Registration Form", + fields: [ + [ + { + id: "username", + label: "Username", + placeholder: "username", + key: "username", + kind: "string", + defaultValue: "", + required: true, + validation: { min: 1, max: 255 }, + }, + { + id: "mmm", + label: "Number", + key: "myNumber", + kind: "number", + required: true, + validation: { min: 1, max: 9999 }, + }, + ], + [ + { + id: "email", + label: "Email", + key: "email", + defaultValue: "", + kind: "string", + required: true, + validation: { format: "email", min: 1, max: 255 }, + }, + { + id: "bool", + label: "Security emails", + desc: "Receive emails about your account security.", + key: "securityEmails", + defaultValue: false, + kind: "boolean", + required: true, + }, + ], + [ + { + id: "dateee", + label: "Date of birth", + placeholder: "Pick a date", + desc: "Your date of birth is used to calculate your age.", + key: "dateOfBirth", + kind: "date", + required: true, + }, + { + id: "aeenum", + label: "Notify me about", + key: "notify", + style: "radio", + enumValues: [ + { + id: Date.now().toString(), + label: "All new messages", + value: "all", + }, + { + id: Date.now().toString(), + label: "Direct messages and mentions", + value: "dm", + }, + { id: Date.now().toString(), label: "Nothing", value: "none" }, + ], + kind: "enum", + enumName: "languagee", + required: true, + }, + ], + [ + { + id: "enum", + label: "Language", + desc: "This is the language that will be used in the dashboard.", + key: "language", + style: "combobox", + enumName: "language", + enumValues: [ + { id: Date.now().toString(), label: "English", value: "en" }, + { id: Date.now().toString(), label: "Arabic", value: "ar" }, + { id: Date.now().toString(), label: "Kurdish", value: "ku" }, + ], + kind: "enum", + required: true, + }, + { + id: "enummm", + label: "Language", + desc: "Select a language", + placeholder: "Select a value", + key: "languageSelect", + style: "select", + enumValues: [ + { id: Date.now().toString(), label: "English", value: "en" }, + { id: Date.now().toString(), label: "Arabic", value: "ar" }, + { id: Date.now().toString(), label: "Kurdish", value: "ku" }, + ], + enumName: "languageee", + kind: "enum", + required: true, + }, + ], + ], +}; diff --git a/apps/web/src/state/state.ts b/apps/web/src/state/state.ts index 48e69b7..c862c87 100644 --- a/apps/web/src/state/state.ts +++ b/apps/web/src/state/state.ts @@ -1,33 +1,35 @@ "use client"; -import { mockFields } from "@/mock/mockFields"; -import type { FormSchema, FormField, FormFramework } from "formbuilder-core"; +import { + type FormSchema, + type FormField, + type FormFramework, + newBooleanField, + type FieldKind, + newDateField, + newTextAreaField, + newEnumField, + newNumberField, + newStringField, +} from "formbuilder-core"; import { persistentAtom } from "@nanostores/persistent"; import { useStore } from "@nanostores/react"; -import { randNum } from "@/utils/randNum"; import { findFieldIndex } from "@/utils/findFieldIndex"; +import { mockForm } from "@/mock/mockForm"; export type State = { selectedForm: number; - temp_items: string[][]; forms: FormSchema[]; renderContent: boolean; + chosenField: FieldKind; }; export const $appState = persistentAtom( "state", { - temp_items: [["0", "98"], ["1", "33"], ["2"], ["3"], ["4"], ["5"], ["6"]], renderContent: false, + chosenField: "string", selectedForm: 0, - forms: [ - { - id: 1, - settings: { importAlias: "a", mode: "a" }, - name: "My Form", - fields: mockFields, - framework: "react", - }, - ], + forms: [mockForm], }, { encode: JSON.stringify, @@ -37,7 +39,6 @@ export const $appState = persistentAtom( export function useAppState() { return { - temp_items: useStore($appState).temp_items, renderContent: useStore($appState).renderContent, currentForm: useStore($appState).forms[useStore($appState).selectedForm], selectedForm: useStore($appState).selectedForm, @@ -66,7 +67,7 @@ function newForm(f: FormSchema) { }); } -function updateFormFields(p: FormField[]) { +function updateFormFields(p: FormField[][]) { const newForms = $appState.get().forms; newForms[$appState.get().selectedForm].fields = p; $appState.set({ @@ -82,8 +83,7 @@ function selectForm(selectedForm: number) { function deleteForm(idx: number) { if ($appState.get().forms.length === 1) return $appState.get(); $appState.set({ - temp_items: [], - renderContent: true, + ...$appState.get(), forms: $appState.get().forms.filter((_f, i) => i !== idx), selectedForm: 0, }); @@ -98,76 +98,102 @@ function updateFormName(newName: string) { }); } -export function addItem( - id: string, - direction: "up" | "down" | "left" | "right", -) { - const temp_items = $appState.get().temp_items; - const index = findFieldIndex(temp_items, id); - console.log("index", index); +function createNewField(chosenField: FieldKind): FormField { + switch (chosenField) { + case "string": + return newStringField(); + case "number": + return newNumberField(); + case "boolean": + return newBooleanField(); + case "enum": + return newEnumField(); + case "date": + return newDateField(); + case "textarea": + return newTextAreaField(); + default: + return newBooleanField(); // Return undefined if chosenField is not recognized + } +} + +function addItem(id: string, direction: "up" | "down" | "left" | "right") { + const currentForms = $appState.get().forms; + const chosenField = $appState.get().chosenField; + const currentForm = currentForms[$appState.get().selectedForm]; + const fields = currentForm.fields; + const index = findFieldIndex(fields, id); if (!index) return; - const newItem = randNum().toString(); + const newItem = createNewField(chosenField); + if (!newItem) return; + const { row, col } = index; - const newTempItems = [...temp_items]; + const newFields = [...fields]; switch (direction) { case "up": if (row === 0) { - newTempItems.unshift([newItem]); + newFields.unshift([newItem]); } else { - newTempItems[row - 1].push(newItem); + newFields[row - 1].push(newItem); } break; + // TODO: down for the last row is broken case "down": - if (row === newTempItems.length - 1) { - newTempItems.push([newItem]); + if (row === newFields.length - 1) { + newFields.push([newItem]); } else { - newTempItems[row + 1].push(newItem); + newFields[row + 1].push(newItem); } break; // TODO: Left is broken case "left": if (col === 0) { - newTempItems[row].unshift(newItem); + newFields[row].unshift(newItem); } else { - newTempItems[row].splice(col - 1, 0, newItem); + newFields[row].splice(col - 1, 0, newItem); } break; case "right": - newTempItems[row].splice(col + 1, 0, newItem); + newFields[row].splice(col + 1, 0, newItem); break; } + currentForm.fields = newFields; $appState.set({ ...$appState.get(), - temp_items: newTempItems, + renderContent: true, + forms: currentForms, }); } export function removeItem(id: string) { - const temp_items = $appState.get().temp_items; - const index = findFieldIndex(temp_items, id); + const currentForms = $appState.get().forms; + const currentForm = currentForms[$appState.get().selectedForm]; + const fields = currentForm.fields; + const index = findFieldIndex(fields, id); if (!index) return; const { row, col } = index; - const newTempItems = [...temp_items]; + const newFields = [...fields]; - // Remove the item at the found index - newTempItems[row].splice(col, 1); + newFields[row].splice(col, 1); - // Check if the array at the index is empty and remove it if so - if (newTempItems[row].length === 0) { - newTempItems.splice(row, 1); + if (newFields[row].length === 0) { + newFields.splice(row, 1); } + currentForm.fields = newFields; $appState.set({ ...$appState.get(), - temp_items: newTempItems, + forms: currentForms, }); } -export function updateFormSettings(newSettings: Partial) { +export function updateFormSettings( + newSettings: Partial, +) { const currentForms = $appState.get().forms; currentForms[$appState.get().selectedForm].settings = { ...currentForms[$appState.get().selectedForm].settings, diff --git a/apps/web/src/utils/findFieldIndex.ts b/apps/web/src/utils/findFieldIndex.ts index 2f53062..b39341f 100644 --- a/apps/web/src/utils/findFieldIndex.ts +++ b/apps/web/src/utils/findFieldIndex.ts @@ -1,6 +1,8 @@ -export function findFieldIndex(temp_items: string[][], id: string) { - for (let i = 0; i < temp_items.length; i++) { - const index = temp_items[i].indexOf(id); +import type { FormField } from "formbuilder-core"; + +export function findFieldIndex(formFields: FormField[][], id: string) { + for (let i = 0; i < formFields.length; i++) { + const index = formFields[i].findIndex((field) => field.id === id); if (index !== -1) return { row: i, col: index }; } return null; diff --git a/biome.json b/biome.json index 52323df..146fb30 100644 --- a/biome.json +++ b/biome.json @@ -28,6 +28,7 @@ "useExhaustiveDependencies": "off" }, "a11y": { + "useKeyWithClickEvents": "off", "useSemanticElements": "off", "noSvgWithoutTitle": { "level": "off" diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a0b3596..c8a113f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,5 @@ export * from "./codegen"; export * from "./codegen/imports"; export * from "./types"; +export * from "./types/prettify"; export * from "./utils"; diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 2c36052..6fa669e 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,3 +1,5 @@ +import type { Prettify } from "./prettify"; + export const fieldKind = [ "string", "number", @@ -8,22 +10,22 @@ export const fieldKind = [ ] as const; export type FieldKind = (typeof fieldKind)[number]; -export interface EnumValue { +export type EnumValue = { label: string; value: string; id: string; -} +}; export const enumStyleValues = ["radio", "select", "combobox"] as const; export type EnumStyleValues = (typeof enumStyleValues)[number]; -export interface ValidationOptions { +export type ValidationOptions = { format?: "email" | "string" | "password"; min: number; max: number; -} +}; -export interface FormField { +export type FormField = { id: string; label: string; desc?: string; @@ -36,14 +38,14 @@ export interface FormField { enumValues?: EnumValue[]; enumName?: string; validation?: ValidationOptions; -} +}; // TODO: add more settings and framework independent settings -export interface Settings { +export type Settings = { importAlias: string; mode: string; - noDescription:boolean - noPlaceholder:boolean -} + noDescription: boolean; + noPlaceholder: boolean; +}; export type FormFramework = | "next" @@ -52,10 +54,10 @@ export type FormFramework = | "vue" | "solid" | "astro"; -export interface FormSchema { +export type FormSchema = Prettify<{ id: number; settings: Settings; framework: FormFramework; name: string; - fields: FormField[]; -} + fields: FormField[][]; +}>; diff --git a/packages/core/src/types/prettify.ts b/packages/core/src/types/prettify.ts new file mode 100644 index 0000000..061cb72 --- /dev/null +++ b/packages/core/src/types/prettify.ts @@ -0,0 +1,5 @@ +export type Prettify = { + [K in keyof T]: T[K]; +} extends infer O + ? { [K in keyof O]: O[K] } + : never; From fbcb713bd1f679a35b60880128dc09070b5a987e Mon Sep 17 00:00:00 2001 From: KryptXBSA Date: Wed, 25 Dec 2024 17:40:15 +0300 Subject: [PATCH 02/22] improve FormSchema --- bun.lockb | Bin 150272 -> 150392 bytes packages/core/src/types/field.ts | 193 +++++++++++++++++++++++++++++++ packages/core/src/types/index.ts | 87 +++++--------- 3 files changed, 225 insertions(+), 55 deletions(-) create mode 100644 packages/core/src/types/field.ts diff --git a/bun.lockb b/bun.lockb index 5e48f83ae0a7bb0ad7d0060d03afcb53d3fe6f0d..dc288ed093385cb16f5fa35cd4a6ac84b606acb6 100755 GIT binary patch delta 29163 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 X4cA6!k~rL12{9eg*{i}iT)FZ;7SA6x delta 29273 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 RCqwT}O4D>PtCRHY{{ga-WK947 diff --git a/packages/core/src/types/field.ts b/packages/core/src/types/field.ts new file mode 100644 index 0000000..a8b13f1 --- /dev/null +++ b/packages/core/src/types/field.ts @@ -0,0 +1,193 @@ +export type FormField = + | TextField + | NumberField + | BooleanField + | DateField + | FileField + | EnumField; + +export type BaseField = { + id: string; + label: string; + key: string; + required: boolean; + desc?: string; + placeholder?: string; + disabled?: boolean; + hidden?: boolean; + className?: string; + tooltip?: string; + message?: string; + frameworkSettings?: FrameworkFieldSettings; +}; + +export type EnumField = BaseField & { + kind: "enum"; + variant: SelectionFieldVariant; + enumValues: EnumValue[]; + enumName?: string; + defaultValue?: string | string[]; + validation?: SelectionValidation; +}; + +export type EnumValue = { + label: string; + value: string; + id: string; + disabled?: boolean; + description?: string; +}; + +export type FileField = BaseField & { + kind: "file"; + variant: FileFieldVariant; + defaultValue?: string | string[]; + validation?: FileValidation; +}; + +export type DateField = BaseField & { + kind: "date"; + variant: DateFieldVariant; + defaultValue?: string | Date; + validation?: DateValidation; +}; + +export type BooleanField = BaseField & { + kind: "boolean"; + variant: BooleanFieldVariant; + defaultValue?: boolean; +}; + +export type NumberField = BaseField & { + kind: "number"; + variant: NumberFieldVariant; + defaultValue?: number; + validation?: NumberValidation; +}; + +export type TextField = BaseField & { + kind: "string"; + variant: TextFieldVariant; + defaultValue?: string; + validation?: TextValidation; +}; + +export type FileValidation = { + maxSize?: number; + allowedTypes?: string[]; + maxFiles?: number; + minFiles?: number; +}; + +export type DateValidation = { + minDate?: string | Date; + maxDate?: string | Date; + excludeDates?: Array; + excludeWeekends?: boolean; +}; + +export type SelectionValidation = { + minSelect?: number; + maxSelect?: number; + unique?: boolean; +}; + +export type NumberValidation = { + min?: number; + max?: number; + step?: number; + precision?: number; + allowNegative?: boolean; + allowDecimal?: boolean; +}; + +export type TextValidation = { + minLength?: number; + maxLength?: number; + pattern?: string; + email?: boolean; + url?: boolean; +}; + +export type FrameworkFieldSettings = { + next?: NextFieldSettings; + react?: ReactFieldSettings; + svelte?: SvelteFieldSettings; + vue?: VueFieldSettings; + solid?: SolidFieldSettings; + astro?: AstroFieldSettings; +}; + +export type AstroFieldSettings = { + partial?: boolean; +}; + +export type SolidFieldSettings = { + signal?: string; +}; + +export type VueFieldSettings = { + vModel?: string; +}; + +export type SvelteFieldSettings = { + reactive?: boolean; +}; + +export type ReactFieldSettings = { + stateKey?: string; +}; + +export type NextFieldSettings = { + serverAction?: boolean; + apiEndpoint?: string; +}; + +export const booleanFieldVariants = ["checkbox", "switch", "toggle"] as const; +export type BooleanFieldVariant = (typeof booleanFieldVariants)[number]; + +export const fileFieldVariants = [ + "single", + "multiple", + "drag-drop", + "avatar", + "image-gallery", +] as const; +export type FileFieldVariant = (typeof fileFieldVariants)[number]; + +export const dateFieldVariants = [ + "date", + "datetime", + "time", + "daterange", +] as const; +export type DateFieldVariant = (typeof dateFieldVariants)[number]; + +export const selectionFieldVariants = [ + "select", + "combobox", + "radio", + "checkbox", + "segmented", + "chips", +] as const; +export type SelectionFieldVariant = (typeof selectionFieldVariants)[number]; + +export const numberFieldVariants = [ + "input", + "slider", + "rating", + "otp", + "stepper", +] as const; +export type NumberFieldVariant = (typeof numberFieldVariants)[number]; + +export const textFieldVariants = [ + "input", + "textarea", + "rich-text", + "markdown", + "code", + "password", +] as const; +export type TextFieldVariant = (typeof textFieldVariants)[number]; diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 6fa669e..cfef42d 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,63 +1,40 @@ import type { Prettify } from "./prettify"; +import type { FormField } from "./field"; -export const fieldKind = [ - "string", - "number", - "boolean", - "date", - "enum", - "textarea", -] as const; -export type FieldKind = (typeof fieldKind)[number]; - -export type EnumValue = { - label: string; - value: string; - id: string; -}; - -export const enumStyleValues = ["radio", "select", "combobox"] as const; -export type EnumStyleValues = (typeof enumStyleValues)[number]; - -export type ValidationOptions = { - format?: "email" | "string" | "password"; - min: number; - max: number; -}; - -export type FormField = { - id: string; - label: string; - desc?: string; - placeholder?: string; - key: string; - kind: FieldKind; - required: boolean; - defaultValue?: string | number | boolean; - style?: EnumStyleValues; - enumValues?: EnumValue[]; - enumName?: string; - validation?: ValidationOptions; -}; -// TODO: add more settings and framework independent settings -export type Settings = { - importAlias: string; - mode: string; - noDescription: boolean; - noPlaceholder: boolean; -}; - -export type FormFramework = - | "next" - | "react" - | "svelte" - | "vue" - | "solid" - | "astro"; export type FormSchema = Prettify<{ id: number; settings: Settings; - framework: FormFramework; name: string; fields: FormField[][]; }>; + +export type Settings = { + importAliasComponents: string; + importAliasUtils: string; + noDescription: boolean; + noPlaceholder: boolean; + framework: "next" | "react" | "svelte" | "vue" | "solid" | "astro"; + frameworkSettings?: FrameworkSettings; +}; + +export type FrameworkSettings = { + next?: { + useServerActions: boolean; + apiRoute?: string; + }; + react?: { + stateManager: "context" | "redux" | "none"; + }; + svelte?: { + kit: boolean; + }; + vue?: { + composition: boolean; + }; + solid?: { + signals: boolean; + }; + astro?: { + ssr: boolean; + }; +}; From 172c2c2e2afde82b11a41515f1986a924c5425e9 Mon Sep 17 00:00:00 2001 From: KryptXBSA Date: Fri, 27 Dec 2024 23:59:30 +0300 Subject: [PATCH 03/22] AddField dropdowns --- apps/web/package.json | 1 + .../AddField/AddFieldAccordion.tsx | 114 ++++++++ .../builder/_components/AddField/index.tsx | 46 +++ .../builder/_components/FormFieldContent.tsx | 5 +- .../src/app/builder/_components/NewField.tsx | 21 -- apps/web/src/app/builder/page.tsx | 23 +- apps/web/src/components/ui/collapsible.tsx | 9 + apps/web/src/state/state.ts | 20 +- bun.lockb | Bin 150392 -> 150360 bytes packages/core/src/index.ts | 1 + packages/core/src/types/field.ts | 269 ++++++++++-------- packages/core/src/types/index.ts | 16 +- 12 files changed, 362 insertions(+), 163 deletions(-) create mode 100644 apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx create mode 100644 apps/web/src/app/builder/_components/AddField/index.tsx delete mode 100644 apps/web/src/app/builder/_components/NewField.tsx create mode 100644 apps/web/src/components/ui/collapsible.tsx diff --git a/apps/web/package.json b/apps/web/package.json index e6bf360..799afd6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -20,6 +20,7 @@ "@nanostores/react": "^0.8.4", "@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-collapsible": "^1.1.2", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.4", diff --git a/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx b/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx new file mode 100644 index 0000000..aaa77e3 --- /dev/null +++ b/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx @@ -0,0 +1,114 @@ +"use client"; +import * as React from "react"; +import { useAppState } from "@/state/state"; +import { + textFieldVariants, + numberFieldVariants, + booleanFieldVariants, + dateFieldVariants, + fileFieldVariants, + selectionFieldVariants, + type FieldKind, +} from "formbuilder-core"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { cn } from "@/lib/utils"; +import { ChevronDown, XIcon } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +const variantMap = { + text: textFieldVariants, + number: numberFieldVariants, + boolean: booleanFieldVariants, + date: dateFieldVariants, + file: fileFieldVariants, + enum: selectionFieldVariants, +} as const; + +export function AddFieldAccordion({ + field, +}: { + field: { + label: string; + kind: FieldKind; + }; +}) { + const state = useAppState(); + const [isOpen, setIsOpen] = React.useState(false); + return ( + + { + setIsOpen(!isOpen); + if (state.chosenField) { + state.setAppState({ + renderContent: !state.renderContent, + chosenField: undefined, + }); + } + }} + className={cn("p-1 hover:no-underline", { + "border-b-2": isOpen, + })} + > + {field.label} + + + +
+ {variantMap[field.kind]?.map((variant) => ( + + {!state.renderContent && + state.chosenField?.variant === variant.value ? ( + + ) : ( + + )} + + ))} +
+
+
+ ); +} diff --git a/apps/web/src/app/builder/_components/AddField/index.tsx b/apps/web/src/app/builder/_components/AddField/index.tsx new file mode 100644 index 0000000..c38f2e0 --- /dev/null +++ b/apps/web/src/app/builder/_components/AddField/index.tsx @@ -0,0 +1,46 @@ +"use client"; +import * as React from "react"; +import { useAppState } from "@/state/state"; +import { + textFieldVariants, + numberFieldVariants, + booleanFieldVariants, + dateFieldVariants, + fileFieldVariants, + selectionFieldVariants, + type FieldKind, +} from "formbuilder-core"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { cn } from "@/lib/utils"; +import { ChevronDown } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { AddFieldAccordion } from "./AddFieldAccordion"; + +export function AddField({ + fields, +}: { + fields: { + label: string; + kind: FieldKind; + }[]; +}) { + return ( +
+

+ Add Field +

+ +
+ {fields.map((f) => ( + + ))} +
+
+
+ ); +} diff --git a/apps/web/src/app/builder/_components/FormFieldContent.tsx b/apps/web/src/app/builder/_components/FormFieldContent.tsx index b2068bb..d71794c 100644 --- a/apps/web/src/app/builder/_components/FormFieldContent.tsx +++ b/apps/web/src/app/builder/_components/FormFieldContent.tsx @@ -34,7 +34,10 @@ export function FormFieldContent({ id }: { id: string }) { > - + - - ); -} diff --git a/apps/web/src/app/builder/page.tsx b/apps/web/src/app/builder/page.tsx index 26ed729..569080a 100644 --- a/apps/web/src/app/builder/page.tsx +++ b/apps/web/src/app/builder/page.tsx @@ -5,12 +5,18 @@ 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 { AddField } from "./_components/AddField"; import { SettingsToggle } from "./_components/FormSettings/SettingsToggle"; import SettingsForm from "./_components/FormSettings"; - +import { fieldKind } from "formbuilder-core"; export default function Builder() { const [showSettings, setShowSettings] = useState(false); + const [isOpen, setIsOpen] = useState(false); + + const fields = fieldKind.map((v) => ({ + label: v.charAt(0).toUpperCase() + v.slice(1), + kind: v, + })); return (
@@ -36,18 +42,7 @@ export default function Builder() { -
-

- Add Field -

-
- - - - - -
-
+
); diff --git a/apps/web/src/components/ui/collapsible.tsx b/apps/web/src/components/ui/collapsible.tsx new file mode 100644 index 0000000..a23e7a2 --- /dev/null +++ b/apps/web/src/components/ui/collapsible.tsx @@ -0,0 +1,9 @@ +import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" + +const Collapsible = CollapsiblePrimitive.Root + +const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger + +const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/apps/web/src/state/state.ts b/apps/web/src/state/state.ts index c862c87..ef23810 100644 --- a/apps/web/src/state/state.ts +++ b/apps/web/src/state/state.ts @@ -10,6 +10,8 @@ import { newEnumField, newNumberField, newStringField, + type FormVariant, + type ChosenField, } from "formbuilder-core"; import { persistentAtom } from "@nanostores/persistent"; import { useStore } from "@nanostores/react"; @@ -20,14 +22,14 @@ export type State = { selectedForm: number; forms: FormSchema[]; renderContent: boolean; - chosenField: FieldKind; + chosenField: ChosenField | null; }; export const $appState = persistentAtom( "state", { - renderContent: false, - chosenField: "string", + renderContent: true, + chosenField: null, selectedForm: 0, forms: [mockForm], }, @@ -39,6 +41,7 @@ export const $appState = persistentAtom( export function useAppState() { return { + chosenField: useStore($appState).chosenField, renderContent: useStore($appState).renderContent, currentForm: useStore($appState).forms[useStore($appState).selectedForm], selectedForm: useStore($appState).selectedForm, @@ -98,9 +101,9 @@ function updateFormName(newName: string) { }); } -function createNewField(chosenField: FieldKind): FormField { - switch (chosenField) { - case "string": +function createNewField(chosenField: ChosenField): FormField { + switch (chosenField.kind) { + case "text": return newStringField(); case "number": return newNumberField(); @@ -110,8 +113,8 @@ function createNewField(chosenField: FieldKind): FormField { return newEnumField(); case "date": return newDateField(); - case "textarea": - return newTextAreaField(); + // case "text": + // return newTextAreaField(); default: return newBooleanField(); // Return undefined if chosenField is not recognized } @@ -124,6 +127,7 @@ function addItem(id: string, direction: "up" | "down" | "left" | "right") { const fields = currentForm.fields; const index = findFieldIndex(fields, id); if (!index) return; + if (!chosenField) return; const newItem = createNewField(chosenField); if (!newItem) return; diff --git a/bun.lockb b/bun.lockb index dc288ed093385cb16f5fa35cd4a6ac84b606acb6..655ee6920d2f2ccb4637daba3fd5b06ef98f1e2a 100755 GIT binary patch delta 29426 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 delta 29661 zcmeHwcYIVu_xH{v8?rzkK!7xQ2`wQ7vT1Ar(vqdO4I(9^kU$zKKtf3nQHli|WI&{e z2#9nshN6I=pmZrJEhy5fD3U1fe$TykLWsbl{+{3azJGW>eD9ev<;*!}&di;?yBWTn zW4brTG_yOqG^!}zFCeV(c6+utw^sr82%4zjJnN{FfH3*rNP^lGY z1<+cvZhHo;vez;fgOY?18L27piAiw|s6;(vCa0k3yb2iAnTIOo>l5TmoMMwdbNzHPG>(l|cj0Rdvwo;HlU646nGcnd#B-i7ByOS!o6< z^rZZ7ou+vurN%^O#(e>KRgphklcVIl$}cm|+(O7iPTugO)M3N24Te}~PR$2`l6$H- zY2{I%#1Bi4j)}`K7!K=0et{n$TUc1PbD0SI7zC)?i?1Zj!T6$b&W@21=LRNBgidSN+v}HNU>!OX| ztX!?!D>XF>MTqn>{bJPpcGQ0nOOMtXZt8k-4?4Tf5v>7XRv zQ>XDwwECMsE991HST?wmbwSrXwT{aQ8V$=x!6Bg3QF1eb0nW?o3renT)?CXE15cA8 zJuV|PX>^>Sc?&H7>Vf9cm{voA!Z9H_BXvv)!nviU;g#B&0F#$i9+Qy~ml0zyoI*a; zPfN|nM4rLfTdSWEosyauYcT9ZImOHyprl}SbaE06)o0+Tzj>{+`s-`M|0JlLk5-UI z1BlBDJdO4HXh5!;3rh7yBxWX3xtu4lNu!B1Owk3$B&3c^jEzo@&xjk1;L2ErIYsSS z`D-zd9v2-Oo0y&!osyj#of(r5ot`^f6_5o1G?pE}(b!#Wt&MphC@F9hl=8Rev~_?M zL~uuRQgm#bCwPj1>Y&b`W8#LXa&uDQ4qW$xG>`N|n&e5(+{{G*X_zr4I*m-vfKF+k z)7omYAQ7}4$|FG$(0MIEG2D5Tb$UD0V5klL7${A@0-Y`bbp!tjC}wV68Ysoc^Pn|B zJLojG6%rI2ZaTFGB@2EI(dZeS?gyo5R1q$s*^rr$lt?!2L_S#*lbV#84!#)W_ zHN)nDQu%opN)$s7lZkr0JE`?@^VT3iL2?WgNbo*T;@{}3(XHTVXkszzz2Z|c(xa1O zqak3uE-(cPIaouRNX=q*ohqQNXm=$-^S}|EZUH4vECa1c^M4i+Wbs%~(mYA0!$2uo z?Ye844*+ihFQa}~R>rW@Y;@W~YquN%3!afEs0N<**VgRbs6fFI7Nte+DNq{2#sjpm-3Oi&%m<|y zGC!{==&jSm$R}5~*4tGE#iEzDVW1Y&cWv;$3o;6AT1UG<-NCN}^#Gj;iq$sn$RKSt z32NnTe_3RW{HDV8-p)#W9g$C-x{c{h3LFEa^1?u^{43DXo|K+joExQCB3)>U*DB6Q z&|EwblmsS%k|ljWNzj%=&GjxLv>_`GN)}5Gc7_}pnziVdJUUya6F|vx37{k|SB`No zGF*^RACxS5Az5qa1fB#Jo3)NFg3=Jk>3R%2#mp{Hs<#Z3R>F@{wfY>*Q??()O}^{*)*!TF>Ve@@b z56+uaYeJiL6*n0^+_&>pf9r+2A5U&pe%Z9e4V!m=*D^hQ#I`|^m%kWqKWKj2SC^$s zd*`^#u79QN3t!&y+=}~^-~a5z*o~{+w%XP^);T}zwUu7{QWYPcO1m4J=p}kJ@bIq| z*!WPR1$VvH<@LO9ddvhr@47pymut{@`(QDyMvHX+H)C>kELc2&=hbY=FIBGRFxDgS z(7CCDf8IE@>QuJTq53_=W$pD#AvaQEg|B!4@*W^B9C=1+WU?3yhEQ;fGgmXq;n{9s>yJTd`B%a%jX5|VeB_v{PKS;7GG_oI$fsd4Wfm=`QkuRY~p8 zl@AZ99cG&1peX|j+*J!|hq2#z5lY%(;UmTDsVCE9orBWo_cLBlCyd?aMRmeV{#ZyO zRH-}am`xvmdrsx3-+R2EZWxQ;MYzx9LG{8+N3fcamJXzsu>#^UfZwkbV(O2S1^Jrx zvvp2&k?{z)K&quwaOB0cET%39_YSfX<6Gc@`8Jmj(?O(sG$Byg5l?wUIJuyn*)$4V zfGlInc~HYJc7D;l0FK&WoZ+T3;HVeW#Gra$I%~R-%oo6s zOlW{%n#+rtkfuSNVWto)0~9%sfx(*23p~TvAztJerc`m}!A&i!J64Yj{!fFw^hIqx!1bO#ZcG*u$kPju*5DGc83PDQ{3+^&>datQ^TVHgTaL zoZuPifCL`?=$1PA8k{yaNQv@TBy|C>-o~@N!il0|7+hZhG~l=tfJ;9!gC*E(`g+rg9;*h;oUQ|M6J zY{~&g{$zYopxLww91VXtzN3-ZR9sTF!_908tfzGeyIjpmPCZ^6Vo{FP=xUO?rl>6Zm7mX1D&eLq14X(c$VpotNgB{dy zZQ4ZRPzxqzfFrNMHMPvjXHEF#aEs}8WKz%SI$>(#Su)Mw%TYRq5d^my9CfKprQgAk z{_1#|!kTIw!BY5ng3j6VNw9PaIO;+3?5|CEaeIqtYBTJUk%@t6Wmdj##&e#tn8KSI z43Wr$XW_l+;Amw<6{vf%InQZrF-5d67~<7T#I12HxKMuICq%j0f(N&;7=1AGt$AD5 z5Ys56$XJXSWNif39~|ZtxLRJNd~b{c*IISnJUt)wx|@~LUOdO&Vyf>g1(6z~ym>J) zmm!m)*-jmrJK(g@!^*69wc?xoET%!NG*`m+aKiiGN_=m+1g;~RS5X~n#ZnrosS2B3 z0q4WFxrLbWk?N^-0(Fe#ec@tK)7TNI4wO=6`|{up7SnDjLPdL3n{s##LLsso+W7kM zVzWg__TxE$7Ga59XF>IXF!^ zNjMw8i<2!%12Yd!3E*iyT@X6HI;=_w3<4O)DSh*CQPPgBQ;3PyN%R9N}0MM z#0R1yEd5Q*#^vCm_=Jd1J0$w@2^~X>ePDfWHMI<>o@(mXl2k_wa(6Y4m!!@i)m6>& z!V_CdWtXIOBh`sYO>P(x8b%CUBeN3Kfp3nqn5OHQ3a3Zb9&i{tZQ3jL9eIw`V(N=f zqS3@u#N=BFj-;p$8PhqP!=%N!S{L&PqnTGtT>uAzqX~`vP?ir)o2Hmq%I!`(xEn-w zHW)%=17&V!zPX!4`KB{3?q)GYViDAK3omxzIo&PFrY?MQcZ;bhHYo^51osZiwQZQt zwi3?STACXPu3t&nc5r=4xawU?bMfGMm6YX!>ruklcQY8!RO?Ua)s5%$vY1{&CMj-E zC&8X>ycn6G-KA@2O6GuTQ_^m2cfPr|#dHCg2xj<%9vDr~i{>FLfJ%H1p3}#oEbYNJ z_pzAjVroJUL?l))(@1dS*$V38{kE>cuzrwV15XoB|$x#xgn?9L;uU=4Up2 z22PLb7G|Xg4f;(~LVEMy{uU*tH_rk2v^U=jV%LWkgLLe}gKZX5HarysDK$CkZdNw* z;hSw1W>(+x`&Vp@e%I91XG7?gBU;aO!qZ zsX2rP53wk{hwz*s7SjhqN<3G`Y^)CPT}W}$Y^0!xF5wn9eGm|T;n5|706ks?_ejT6 z;1G#uN8WA@1$EP~9%q8n^rzMNC^%hXNO6ggk5#gC1UPM&Ny<;)^d5!;+Qrh^)wWNl z0}_wiW{Q*j(%9L+=`Fym21nXMr;tDgFw_k36poEwybK41mE<-!ZOC^7noU8&H6d_Y zYpgQh=(&$}o@SFNL3WO3tLZ&(G#AvUG@Svbxqwt@nJ8tFVf3mLD-%hi`5cr%CX5eF)t;;6c{{&A2QW0dv4O&**$2+b@8fee1v|`$-t5<1CriKD`KsZ1hv;*iWO-X?c0LkqH(A5e3s|^r3 zDn*d07f}+3xvpNNDG9)Es~1t~5Kjg5B1#Hj{ZucaMgWVM+A(IndJ&~|gZSYI?yB3d zT&Ouibs7yy7g6dlhA6K+Q5!ML8TBGcaxgR0>q(UCK!mE-lPF0?;2FvTSpdl!4bVlD z%EtnfpG_l122Ip6;6Jq>50tJvfGSP`=psrAOaaI-GXU!7O@Q*}0F?g@K1 zU4N$3qh3%GbUQ!-zXa$iO=-x#0!Z-JI^73K*OMqIazriFC|Q0KAbMQSKd$$$W{}_$ z0MT#t3Ph>F_c~9M1Q!CN(2qL307_SBN^&j&L@xn!5v6ul0m>hA4L7=oQo&7Pa6OAs zy`R-u8YKmP(fQJ}9B-Rji@x9vR|d{AzfYQG&7;UY@1>~)?f4QmBZ%Bra6 z{}oDAE9v!1QwQWb>-nIHf$`dTwQ{Lj7d^W)Esy-Vdj69rsn-zY8lL=Q^9L=(SQ>U3U9J@Gc>;v!1P`M8m57wY^XP=zOtt;P3E zcE}~)6i~JQAte!;bvZ;y<*hnjnvy1;A)ku2%Rxgzy@*nBht8L#w8nj*=l>N-3hdG4 z?FA+NYn|Vx%h@LfZNFY{KrbjwX{k7>mmdQqMZO0mOHPB5;IlgagU+7^t%m%&pcMY} zLlFvdMwN9+?d@pNkU$eC>KH2M6^K%U%6fhkP-;*O6#op)xRGLRI;{sv9eIM{pP?CU zCQv_6YS)G`bxKLI&gaSmQBoiflmvx=I)U~8B?bHHbO0y`d;t{y41;kaMPhY20+c$= zphBI}dNB@sCD2zu>2Wd}6#sG!b8xSSj3uBX_&u$_@IEMYyb9D6bQdTo_BANgI{-=^ zI-=9#pq0U&2c?TB)w>8va<1w8O;G$Z+@>2X8k8W*e+H!viqry)lBV{MLV}$^tAl!i zlE9XrG{zQCvMdsm>f3alevCw-;y_7$yiSMfG=a)#(HemSS&{-uf-|T9G)vFV1|GGHrx1$)s8%Qvz3kF1>mCAIP%(CtoVge;uafsSnJ65gUjNsTW$Oka1*v#*%-bT zT*5j>?zPRzvU&D48+Tdn$WMbC$D4g><9oop_NkRk;NO88yTOsS{mja8`Lxe$yy-?q zejVH-9<<%Yzuj1wFWerLmhtQj zuy1>19?$UPD+-+qm`J|1oZwIsnw~9}LlrB3RdB;swwwlk{1pC1K3T`cL z_c84I99n;DW$XEEa8bK3eqUJGM!pidIP8X$Kdj0o1Fv?!KmP=5)_p5};MMK~4EzFv zd*7-Q7`WSm{@mqDjP3)gvc-Syp zi#gLt*mDN)bJEHl@&a&8&ms;^Srx|kh*SOfw_x{!EyuXq>Hd6bA;$W&RWULeUMu%H z=fb_tSe5dO=bY)!FP+0=K4WDj-s~)l`vH^rtd&*d-+}A$BPMg9l{xZhg$PA(*TGfc zLFW*P=NTBUj`TT6DIQyR#u(Q`vFE?fRW&8@Q5E_B)HW-T3Ib#1TNtsj682; zu6*Tr7J!@et4)`EWruFDPBch$ZUE!cP6%KZ7f>#*-O>;u<^N8EsY;8x$TGBYm% zm+&*}yJ=-XeC18pcL(;}va%39;1=uyw+&odZn_Qoet~_rtt^ZefNT0I?EBfu+VRAn zVIR2t;GW~IcVORL*muXuI`X~X0)K;jzgSskp8X5#19uu+1aI~$?7Ij1ezjtlzXR8$ z2=?8zvTl6ZUDyZiI=CJ@=r`C`4EuhwvR?c$xTxQ;u-vngJK218y1sFOHc5y9Y7%g&S$mpqz&XTL3fR5^A~VGr}$*|X;qqo{$uE67K+oBCsMGs&=53u5)4z$~0dR@dITz(z^}3q{>Tdt!u4Hag~2i5|fnZJt9N< zw6Fegi5OI!P0s(eCY!BLd!=_L@qBf*+2|gDmt3UtNT$k_e`Vl=Z=I`>q;TGoay4pm@H^KaE;q4uQz_K$n8NN0_IbRSs` z*?Syun^jFj#<0QkhXd*riM5@i6F$_k3|-ZAUwf&uR?q9E=V81_0 zo_am{w2vRBs8=t&9=#o;Z9J}ALm#~&z1*a~I-{$vo<}c0=@l|{)(@2GAZ84Q^t?fO zJ$mo?71AVlu%1V+66t+4T|@LddaX$BKj|8(=Ha~|y_38RhB}J|rSd8Oy`!eiV$_P- zTU_~7Sgf8`73K2lusA)B-esNy=!)0#=zZrYfZhZT*Yn8pXE289m7r%L$_;1ryhP-Y zGW6BLHGn!Bsn@H8^c8@FBK@?m!)&HjsyQ^s4g%)L8{kG^_^J0Ba2*AfD9`FT^vCTyIov1^58IfFIxwv0Zjl;pefJ{Xb(IGbO1U66mFe?2ms-lYp^0gF-kE> zan}Q&xT84h4Nz!NC{gJ22W&tTFaUTSSO|-j0?UB+f#pCRFb+rqFp&)-fIxs^H~3zBt=n8ZLx&u9c zoL4Z3@2~gkbgXS{awjn_2{W!ft9|zF8^~2D12e1?P z9M}!)0+sD}bfI`v83_M_;^60J4E8z>7d0jsIjMUIHco zuK+Itqk(b2cwh{W3rqyEfE-{fkPc)5^i}^DAPWcwLV>|@B@5- z#y}Gq|3*kOgdkdHUO?IzbQowf@Ebs1T;B$60f&J7zyV+%a2PlUd%8w zz|X+9z<0oL;27`~U_?juzqMb)ZhygRTSG0&4(r`)XhV)#Q0$YFrU^77F)FuugFbo(1s6#A2@c*mwZu zKJW+dJ3z5~8K4PO2wVWp13v<1fnC7oz)s*Z;8S2bK!HpFOhHQ?C8K0OQh>Jsb^sLU z0e~Y-(Zxtmw7(CKTPdn3nrUV%0BCGa0u_PpffK-S;23Ze_y#xv(3*D$pmpznIF`md z*>15U9w$P4jhubJx4?J6B;XW4VR#xi0~7-1fFJbqPdZ;%ijvQWCsz~!_kiDkUx8nM zI{*zlx#wr#HgJm~|0WW0p}L0jRX~QNq$IQuAO-L0>C)j`tmoNb@X7%SzyNxDQ_$E0 zH@a0NS58(X)g)sSDHrY62vHE)qra%OxSys9vc`QUGZ$Et7eq5P9lp`P4?rD&0n=rB_G>>NQt4pHxT}DMKAd z0mRGvX5w51tDD;f?E(N_fIQn8@CW>WRsc06XOe#2fGW3?XsS~-kJf~;c_g3mN~?gp z+$S1NLZ9RiQkDcs#hU0srC_pE8bWoRqzK6%LxX_o6tB|KKxB}mWi>DBA+6yfC7#qq zDkK%A4oM-ZOP?A-0JWc7+{q=RxwM#gDS#TO6-(9Yio7Q&MMFoE!V1u&cn+YUl%6O( zl%xa=XD&^WFraKF?T{w}I~?iKlce;dXpgeeJf&%pNCP?}-3cf?ETjk-+mXhfT+sn2 ztANZSZ#>BhUC@Tg$a9fMN9ZUWA7y1q&q-y1N{_$Ps%)duV@P9PHeMbZPc)WFm2UU6 zq4CRwKsi1X98VjLCwbsm9h9EJGz*?4JD1#7T9CAk8a&Igz9=ufZj_!C)b?3kq~O&1 z$t_u2$~K_A0lAWB6dF*Pf=ki?dLC_HD4+Nj^gJ@^X?2I7PBbtSh+*#d7=c73Kt9+G zWrM+WMJ&l?^xH0j_%fUIp(B~{_mC)V#;c&L8^RaStv zA9g#U2i5yR%$kCFCs3~{>TRud(erZ4o}1Kq{#5UhSU~mei~Mn{o8q7d`|%9>8j(C6 zxvj(?v}QqK$#~}P-civ^|NPrkcu5b_eY}0KSrj)>-Cdr-)pKIf)>G>@euxTY zRKU*clxQ~>W`#~*tyzp1H333D5i=&R;qHs=HKBo#U)}JV?=}`~;7!tOm2jMhdW(hU zMCQ*v5(9}`7jGc5O|}EZ5cU)~HrP}neDN)XCF?o#!H>o#e`S+PuN~gGPnQQJ{l&R? zke4lfo5;Gc@#6PfaMQ&Q5ce;1|M0`^@guvvR7Y*4nl@MPTvWd<&QW!}Re-3G2ZuaS zcXcFn-NVI%JhZY3kp~%z#ZN?D75GGn4%$`5j%VR*6;V8MFn(04*E^>LItI~lSq1z`6<<$#9~l)dFWTaq1PT3&y2pS zHt?rmIw-D_M0xs`U6fzn{q`U1Muj%oI1U%}C&S6TML&=>`e|Qz`zv)j`uwJw%@pQ` zk)eI|pLHUYsHBzHHW?O=6DLT}I#H2gRtrL5d5Lw`(@YP$$Aa2jo&`2*O`{6qe7BB= zVl?S6GagBB6YZxkH!U*#HEI4bPW5@C$@Y*Zh`sm5ft@RrHv%Ee7y82w@Kn#K%;gyY zJyb-$48?bdaUkxUFpp^dvdZA+em0yoq8g5hr^mL6vQ0XVNq{W3uwq(URw21Dvoa5!z+$cMsGz8tdk`CN^KE-gmv|h$C%Bpk$=T)%GQyn;p45Y zk)!K~lsTATxpl<5b6~*CI^xzG<`N)}dRumPY;2#(UpSIJn!>~C(g#qi%hQgpsS_1h z;gLS6b%jN+<`q6c4O+o$bw#>hejdj9>Wlom?QV_jw+tCth!&W$G|em57uyATtA!f0 zvQ2S%At~u4_Kw<{F9mc1lu&%SR;!lOw_%l|dRy4$S6@_~3)ennWkwBkWp2@^VFPUc zAVMBOcWc*#w0BPI`-VEvo=$ffh?x)-AdjK5^*Y*Z(9e6%scNXBBoCrn_`#c3Yt}e2 z1to!S2TVBEP@F;AHu6Nd`$=qFT7wSXqXvy1YRqh;nQ*UoT*A(%*~=7`jhtH0$X~R; zvhA@6Y-O;q13urmu*zbi!WJW^268@cB!ysOaHE7G!XG2Xpw?tsUp00(?lpE>GUezP;(&lVMvY zqC^eh)`sL}qC47p$TRtdM#NNXFs&br0EHR3LakA=PHTfaj&JFR(OKCK2G&L$NQc>m zdM!lP*_gS_TZs7C%+FaK)Hi?LS8k({KdDHXi1*%P^*rQ}ee!T5D$!y=n{GA4AkNGl zt+1u20Ow^)>+;~EWsPXX*VkxgZ?O)42`tXOiIoE@M)8}Nc=K9`OX$}_p3f&wPC_$% zK->9<^PF`R8)mcC!tE`V;33cIlcy+QeDs)G<|h`u1#7VWeDW5icPoE!4g8ZIpsIhP zMBO=1M_;g%Ev?1tSX%<*>3s5xC53&7;ip(E1f@AEPhnCO_4hX8HBD=Aiq@qkdO%&d zynKYyH!$|k((Unni=00@4H)g9sD?Z& zj31qPr2nv<1r4a*ASx<0iW)BF75-UKz!J@q0_J6nI}QdM;USN_l;@r)+9*LyZJScE9|?Hk zlvKx49w7SXZ3Z^s*xR^xJbe$NFB;F-#5{eAqVK8{eKVtIYp9EOdp>qqJ43`FdMwBj zG7ERqc<|D<3Fqw?<|)lWgFHj?#?+~&96FDB@lnaoA)?MZXe&?Mw2onsz3!ZHdQ?N6 z%enJj%X@40Tz~gbNvlwigti{?D9>@%hTW<(d1a{@&xeY|BwZf$zHRW3=agxkL+~xLbQG<43 zn7ng4iVlk)3%|)JM9o_A2-8a|rdK(B+&)cV3p-&uC3=0pYBUP$tp2fA$$6%|e|&%B z>|tyBYvO2stBH}~nkyb%MEGLnVhrknmj<#r!KfW1ZNYF5>mYEI2@(PP%5r zomYCly?(F4)U7$R9TuTIHQH}_IC)6Kb$YRKbVyS!|OY`Hv&KPqVxDLmfA zsPu>wJ>O+6U4}qLHTZ1cI)|vZ#FvtswFd`Acoa(TA}X)-$IIpv)^$pEX3gOZy8i6n zS0lwbNHe~QHojuXr_7~1Hr4prw7ar!B>NxZP76U&{OfxP1)Q#f*6z1Q10~>Xzh5% z3lR-f%hZn(Bm0WV%TPlexY}lYg^Kb}~!qks0iTB3%3 z1rLNjK6~QCS}}72bGAo&+Wi!M2xrYOi*>76^F|~4si$f@VT#C-v;BkN@=?*M~PcsVT#B@b-#AI)hM-w zSgt!C`vZAg@07xcxkFvo<1?h6H+FOX$Y+bu+cs9ih~*+bO4M12{f0cd*1WBJ{uYlO zU#cC`rxU}eD3P?1xj4%+UfpUmt~#khx zw$*+@9ZguNI$h*}!*%AJDjd6|#${C{HO%CZ#C?7F`s3g4nTHZA;q=^knv#uVu<0jM z=P4&f<%xEFSU1&=P_R?;gqKMDj2Zume#(Y{B5w@_wQQu7(IemYq3|X{~qTArE~%nYFaT!#!|bvjL}G{xkTscaPPjS=Cy5p6OG z|NdzFv+3~{RF&?LbN!#IST+u&+nzZpvb$rkqTV)m=yt3qEci$BvQ(TtX|XEVw@8D_ zjDbb&YyP~Yk|pwVcz35h_g>ntbn2sV|F4e@Hbd&N_Uz3Nt%`ccdB`L1KkWQbhfAIn z_mFbh>m+TPB=<%x`nKrlOQReLeTnpV)|N@6(4duZHNRpglCR)*-`oLJ!NILFC>dI`~LQJ z{TXvZZk^=dN|pc1gDkswnungUCIrbnz|(gJ%ki?m0^YVPI{8CaF*!ZKiJ4OH4pV&< zBf<|b2al)JKpT8<5PJZxv;sSenFm;3<(OGqIDpl8nOV3U1O?xnR_TvZMFR2KsvR%$ zg~uW0=27;`C@1=@fDgU0gb$0q!FM0>4b*>p4`vEH#;~`*pEZcXM_EVTxQw*ubo_5} z``TMJ|K54v_73<~!wGutnZDtV&wKWIzb|;Q{m0A~6P;osP7W3k$5`D4E%Ek=vc&`c z_DSok)p|l=>0hC>tSzP=V|8*_9WDQ4zj5!`R@HN@1fG6nFsW9*$`74epK$?C-R%w< z(kE*JYB`Gl}hW^{e;VG%<$;z8D1H2Nf4Hr z4#^m>^!!Rp`KD5S!{1pk6IF^?d2#1Ab}|2(B36ls@9*KKPHN`j2ka#+b*7lL6VcvE z9Z~N-D-eUrDdk0@2h1$K?x0iC{M3F+yo9dPFL&rbBw>B{3>ex%htopW>-R diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index c8a113f..44c0db0 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,5 +1,6 @@ export * from "./codegen"; export * from "./codegen/imports"; export * from "./types"; +export * from "./types/field"; export * from "./types/prettify"; export * from "./utils"; diff --git a/packages/core/src/types/field.ts b/packages/core/src/types/field.ts index a8b13f1..270d18d 100644 --- a/packages/core/src/types/field.ts +++ b/packages/core/src/types/field.ts @@ -6,6 +6,24 @@ export type FormField = | FileField | EnumField; +export type FormVariant = + | TextFieldVariant + | NumberFieldVariant + | BooleanFieldVariant + | DateFieldVariant + | FileFieldVariant + | SelectionFieldVariant; + +export const fieldKind = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; +export type FieldKind = (typeof fieldKind)[number]; + export type BaseField = { id: string; label: string; @@ -21,55 +39,117 @@ export type BaseField = { frameworkSettings?: FrameworkFieldSettings; }; -export type EnumField = BaseField & { - kind: "enum"; - variant: SelectionFieldVariant; - enumValues: EnumValue[]; - enumName?: string; - defaultValue?: string | string[]; - validation?: SelectionValidation; +// Text Field +export const textFieldVariants = [ + { label: "Input", value: "input" }, + { label: "Textarea", value: "textarea" }, + { label: "Rich Text Editor", value: "rich-text" }, + { label: "Markdown Editor", value: "markdown" }, + { label: "Code Editor", value: "code" }, + { label: "Password Input", value: "password" }, + { label: "Auto-resizing Textarea", value: "autosize-textarea" }, + { label: "OTP Input", value: "input-otp" }, + { label: "Input Mask", value: "input-mask" }, +] as const; +export type TextFieldVariant = (typeof textFieldVariants)[number]["value"]; + +export type TextField = BaseField & { + kind: "text"; + variant: TextFieldVariant; + defaultValue?: string; + validation?: TextValidation; }; -export type EnumValue = { - label: string; - value: string; - id: string; - disabled?: boolean; - description?: string; +export type TextValidation = { + minLength?: number; + maxLength?: number; + pattern?: string; + email?: boolean; + url?: boolean; }; -export type FileField = BaseField & { - kind: "file"; - variant: FileFieldVariant; - defaultValue?: string | string[]; - validation?: FileValidation; +// Number Field +export const numberFieldVariants = [ + { label: "Number Input", value: "input" }, + { label: "Slider", value: "slider" }, + { label: "Rating", value: "rating" }, + { label: "OTP", value: "otp" }, + { label: "Stepper", value: "stepper" }, +] as const; +export type NumberFieldVariant = (typeof numberFieldVariants)[number]["value"]; + +export type NumberField = BaseField & { + kind: "number"; + variant: NumberFieldVariant; + defaultValue?: number; + validation?: NumberValidation; }; -export type DateField = BaseField & { - kind: "date"; - variant: DateFieldVariant; - defaultValue?: string | Date; - validation?: DateValidation; +export type NumberValidation = { + min?: number; + max?: number; + step?: number; + precision?: number; + allowNegative?: boolean; + allowDecimal?: boolean; }; +// Boolean Field +export const booleanFieldVariants = [ + { label: "Checkbox", value: "checkbox" }, + { label: "Switch", value: "switch" }, + { label: "Toggle", value: "toggle" }, +] as const; +export type BooleanFieldVariant = + (typeof booleanFieldVariants)[number]["value"]; + export type BooleanField = BaseField & { kind: "boolean"; variant: BooleanFieldVariant; defaultValue?: boolean; }; -export type NumberField = BaseField & { - kind: "number"; - variant: NumberFieldVariant; - defaultValue?: number; - validation?: NumberValidation; +// Date Field +export const dateFieldVariants = [ + { label: "Date Picker", value: "date" }, + { label: "Date & Time Picker", value: "datetime" }, + { label: "Time Picker", value: "time" }, + { label: "Date Range Picker", value: "daterange" }, + // Shadcn specific + { label: "Calendar Picker", value: "calendar-picker" }, + { label: "Date Range Calendar", value: "date-range-picker" }, +] as const; +export type DateFieldVariant = (typeof dateFieldVariants)[number]["value"]; + +export type DateField = BaseField & { + kind: "date"; + variant: DateFieldVariant; + defaultValue?: string | Date; + validation?: DateValidation; }; -export type TextField = BaseField & { - kind: "string"; - variant: TextFieldVariant; - defaultValue?: string; - validation?: TextValidation; +export type DateValidation = { + minDate?: string | Date; + maxDate?: string | Date; + excludeDates?: Array; + excludeWeekends?: boolean; +}; + +// File Field +export const fileFieldVariants = [ + { label: "Single File Upload", value: "single" }, + { label: "Multiple File Upload", value: "multiple" }, + { label: "Drag & Drop Zone", value: "drag-drop" }, + { label: "Avatar Upload", value: "avatar" }, + { label: "Image Gallery", value: "image-gallery" }, +] as const; +export type FileFieldVariant = (typeof fileFieldVariants)[number]["value"]; + +export type FileField = BaseField & { + kind: "file"; + variant: FileFieldVariant; + defaultValue?: string | string[]; + validation?: FileValidation; }; export type FileValidation = { @@ -79,11 +159,33 @@ export type FileValidation = { minFiles?: number; }; -export type DateValidation = { - minDate?: string | Date; - maxDate?: string | Date; - excludeDates?: Array; - excludeWeekends?: boolean; +// Enum Field +export const selectionFieldVariants = [ + { label: "Select Dropdown", value: "select" }, + { label: "Combobox", value: "combobox" }, + { label: "Radio Group", value: "radio" }, + { label: "Checkbox Group", value: "checkbox" }, + { label: "Segmented Control", value: "segmented" }, + { label: "Chips Input", value: "chips" }, +] as const; +export type SelectionFieldVariant = + (typeof selectionFieldVariants)[number]["value"]; + +export type EnumField = BaseField & { + kind: "enum"; + variant: SelectionFieldVariant; + enumValues: EnumValue[]; + enumName?: string; + defaultValue?: string | string[]; + validation?: SelectionValidation; +}; + +export type EnumValue = { + label: string; + value: string; + id: string; + disabled?: boolean; + description?: string; }; export type SelectionValidation = { @@ -92,23 +194,7 @@ export type SelectionValidation = { unique?: boolean; }; -export type NumberValidation = { - min?: number; - max?: number; - step?: number; - precision?: number; - allowNegative?: boolean; - allowDecimal?: boolean; -}; - -export type TextValidation = { - minLength?: number; - maxLength?: number; - pattern?: string; - email?: boolean; - url?: boolean; -}; - +// Framework Settings export type FrameworkFieldSettings = { next?: NextFieldSettings; react?: ReactFieldSettings; @@ -118,76 +204,27 @@ export type FrameworkFieldSettings = { astro?: AstroFieldSettings; }; -export type AstroFieldSettings = { - partial?: boolean; -}; - -export type SolidFieldSettings = { - signal?: string; +export type NextFieldSettings = { + serverAction?: boolean; + apiEndpoint?: string; }; -export type VueFieldSettings = { - vModel?: string; +export type ReactFieldSettings = { + stateKey?: string; }; export type SvelteFieldSettings = { reactive?: boolean; }; -export type ReactFieldSettings = { - stateKey?: string; +export type VueFieldSettings = { + vModel?: string; }; -export type NextFieldSettings = { - serverAction?: boolean; - apiEndpoint?: string; +export type SolidFieldSettings = { + signal?: string; }; -export const booleanFieldVariants = ["checkbox", "switch", "toggle"] as const; -export type BooleanFieldVariant = (typeof booleanFieldVariants)[number]; - -export const fileFieldVariants = [ - "single", - "multiple", - "drag-drop", - "avatar", - "image-gallery", -] as const; -export type FileFieldVariant = (typeof fileFieldVariants)[number]; - -export const dateFieldVariants = [ - "date", - "datetime", - "time", - "daterange", -] as const; -export type DateFieldVariant = (typeof dateFieldVariants)[number]; - -export const selectionFieldVariants = [ - "select", - "combobox", - "radio", - "checkbox", - "segmented", - "chips", -] as const; -export type SelectionFieldVariant = (typeof selectionFieldVariants)[number]; - -export const numberFieldVariants = [ - "input", - "slider", - "rating", - "otp", - "stepper", -] as const; -export type NumberFieldVariant = (typeof numberFieldVariants)[number]; - -export const textFieldVariants = [ - "input", - "textarea", - "rich-text", - "markdown", - "code", - "password", -] as const; -export type TextFieldVariant = (typeof textFieldVariants)[number]; +export type AstroFieldSettings = { + partial?: boolean; +}; diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index cfef42d..6e8ee2c 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,11 +1,22 @@ import type { Prettify } from "./prettify"; -import type { FormField } from "./field"; +import type { FieldKind, FormVariant, FormField } from "./field"; + +export type FormFramework = + | "next" + | "react" + | "svelte" + | "vue" + | "solid" + | "astro"; + +export type ChosenField = { kind: FieldKind; variant: FormVariant }; export type FormSchema = Prettify<{ id: number; - settings: Settings; name: string; + framework: FormFramework; fields: FormField[][]; + settings: Settings; }>; export type Settings = { @@ -13,7 +24,6 @@ export type Settings = { importAliasUtils: string; noDescription: boolean; noPlaceholder: boolean; - framework: "next" | "react" | "svelte" | "vue" | "solid" | "astro"; frameworkSettings?: FrameworkSettings; }; From ee707721578c1d6f97484a80778a64c9dd1e6243 Mon Sep 17 00:00:00 2001 From: KryptXBSA Date: Sat, 28 Dec 2024 13:37:14 +0300 Subject: [PATCH 04/22] dndkit issue with swapping --- .vscode/settings.json | 5 ++- .../app/builder/_components/SortableGrid.tsx | 32 +++++++++++-------- apps/web/src/mock/mockForm.ts | 23 +++++++------ apps/web/src/state/state.ts | 2 ++ packages/core/src/types/field.ts | 4 +-- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index bd9fb64..3446133 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,8 @@ }, "workbench.editor.enablePreview": false, "workbench.editor.wrapTabs": true, - "workbench.editor.tabSizing": "fit" + "workbench.editor.tabSizing": "fit", + "[typescript]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + } } diff --git a/apps/web/src/app/builder/_components/SortableGrid.tsx b/apps/web/src/app/builder/_components/SortableGrid.tsx index 3db5851..e393b9d 100644 --- a/apps/web/src/app/builder/_components/SortableGrid.tsx +++ b/apps/web/src/app/builder/_components/SortableGrid.tsx @@ -25,7 +25,6 @@ import { import { SortableItem } from "./SortableItem"; import { MouseSensor } from "./CustomSensor"; import { useAppState } from "@/state/state"; -import type { FormField } from "formbuilder-core"; export const SortableGrid = () => { const [activeId, setActiveId] = useState(); @@ -45,6 +44,8 @@ export const SortableGrid = () => { const handleDragEnd = (event: DragEndEvent) => { // setActiveId(null); const { active, over } = event; + console.log("active", active); + console.log("over", over); if (!over) return; if (active.id !== over.id) { @@ -69,26 +70,29 @@ export const SortableGrid = () => { } // 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; - } + // 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; + newItems[overRowIndex][overColIndex] = + items[activeRowIndex][activeColIndex]; + + newItems[activeRowIndex][activeColIndex] = + items[overRowIndex][overColIndex]; + console.log("should be here", newItems); return newItems; } - return items; + console.log("xxw", items); + // return items; } - console.log("items", items); + // console.log("items xxxxxxx", items); }; return ( @@ -96,7 +100,7 @@ export const SortableGrid = () => { sensors={sensors} collisionDetection={rectIntersection} onDragEnd={handleDragEnd} - onDragMove={handleDragEnd} + // onDragMove={handleDragEnd} onDragStart={handleDragStart} >
diff --git a/apps/web/src/mock/mockForm.ts b/apps/web/src/mock/mockForm.ts index fa0efed..2107de3 100644 --- a/apps/web/src/mock/mockForm.ts +++ b/apps/web/src/mock/mockForm.ts @@ -3,8 +3,8 @@ import type { FormSchema } from "formbuilder-core"; export const mockForm: FormSchema = { id: 1, settings: { - importAlias: "default", - mode: "default", + importAliasComponents: "aa", + importAliasUtils: "az", noDescription: false, noPlaceholder: false, }, @@ -17,7 +17,8 @@ export const mockForm: FormSchema = { label: "Username", placeholder: "username", key: "username", - kind: "string", + kind: "text", + variant: "input", defaultValue: "", required: true, validation: { min: 1, max: 255 }, @@ -27,6 +28,7 @@ export const mockForm: FormSchema = { label: "Number", key: "myNumber", kind: "number", + variant: "input", required: true, validation: { min: 1, max: 9999 }, }, @@ -36,10 +38,11 @@ export const mockForm: FormSchema = { id: "email", label: "Email", key: "email", - defaultValue: "", - kind: "string", + kind: "text", + variant: "input", required: true, - validation: { format: "email", min: 1, max: 255 }, + defaultValue: "", + validation: { min: 1, max: 255, email: true }, }, { id: "bool", @@ -48,6 +51,7 @@ export const mockForm: FormSchema = { key: "securityEmails", defaultValue: false, kind: "boolean", + variant: "checkbox", required: true, }, ], @@ -59,13 +63,13 @@ export const mockForm: FormSchema = { desc: "Your date of birth is used to calculate your age.", key: "dateOfBirth", kind: "date", + variant: "date", required: true, }, { id: "aeenum", label: "Notify me about", key: "notify", - style: "radio", enumValues: [ { id: Date.now().toString(), @@ -80,6 +84,7 @@ export const mockForm: FormSchema = { { id: Date.now().toString(), label: "Nothing", value: "none" }, ], kind: "enum", + variant: "radio", enumName: "languagee", required: true, }, @@ -90,7 +95,6 @@ export const mockForm: FormSchema = { label: "Language", desc: "This is the language that will be used in the dashboard.", key: "language", - style: "combobox", enumName: "language", enumValues: [ { id: Date.now().toString(), label: "English", value: "en" }, @@ -98,6 +102,7 @@ export const mockForm: FormSchema = { { id: Date.now().toString(), label: "Kurdish", value: "ku" }, ], kind: "enum", + variant: "combobox", required: true, }, { @@ -106,7 +111,6 @@ export const mockForm: FormSchema = { desc: "Select a language", placeholder: "Select a value", key: "languageSelect", - style: "select", enumValues: [ { id: Date.now().toString(), label: "English", value: "en" }, { id: Date.now().toString(), label: "Arabic", value: "ar" }, @@ -114,6 +118,7 @@ export const mockForm: FormSchema = { ], enumName: "languageee", kind: "enum", + variant: "select", required: true, }, ], diff --git a/apps/web/src/state/state.ts b/apps/web/src/state/state.ts index ef23810..fed3516 100644 --- a/apps/web/src/state/state.ts +++ b/apps/web/src/state/state.ts @@ -126,6 +126,7 @@ function addItem(id: string, direction: "up" | "down" | "left" | "right") { const currentForm = currentForms[$appState.get().selectedForm]; const fields = currentForm.fields; const index = findFieldIndex(fields, id); + if (!index) return; if (!chosenField) return; @@ -168,6 +169,7 @@ function addItem(id: string, direction: "up" | "down" | "left" | "right") { $appState.set({ ...$appState.get(), renderContent: true, + chosenField: null, forms: currentForms, }); } diff --git a/packages/core/src/types/field.ts b/packages/core/src/types/field.ts index 270d18d..7a93d3e 100644 --- a/packages/core/src/types/field.ts +++ b/packages/core/src/types/field.ts @@ -61,8 +61,8 @@ export type TextField = BaseField & { }; export type TextValidation = { - minLength?: number; - maxLength?: number; + min?: number; + max?: number; pattern?: string; email?: boolean; url?: boolean; From 9a061fecbfd471a0aa3a7bae3f9da7b4aa25c4ec Mon Sep 17 00:00:00 2001 From: KryptXBSA Date: Sat, 28 Dec 2024 13:51:22 +0300 Subject: [PATCH 05/22] set state directly --- apps/web/src/app/builder/_components/SortableGrid.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/builder/_components/SortableGrid.tsx b/apps/web/src/app/builder/_components/SortableGrid.tsx index e393b9d..f7affc0 100644 --- a/apps/web/src/app/builder/_components/SortableGrid.tsx +++ b/apps/web/src/app/builder/_components/SortableGrid.tsx @@ -44,8 +44,6 @@ export const SortableGrid = () => { const handleDragEnd = (event: DragEndEvent) => { // setActiveId(null); const { active, over } = event; - console.log("active", active); - console.log("over", over); if (!over) return; if (active.id !== over.id) { @@ -85,11 +83,11 @@ export const SortableGrid = () => { newItems[activeRowIndex][activeColIndex] = items[overRowIndex][overColIndex]; - console.log("should be here", newItems); - return newItems; + // console.log("should be here", newItems); + state.updateFormFields(newItems); + // return newItems; } - console.log("xxw", items); // return items; } // console.log("items xxxxxxx", items); From 488767bc0c34135e75d228feca17c5418ca9460f Mon Sep 17 00:00:00 2001 From: KryptXBSA Date: Mon, 30 Dec 2024 23:42:36 +0300 Subject: [PATCH 06/22] can't figure out the new types with generics maybe it's not needed? --- .../AddField/AddFieldAccordion.tsx | 12 +- .../builder/_components/FormFieldContent.tsx | 75 ++-- .../_components/FormSettings/index.tsx | 18 +- .../app/builder/_components/SortableGrid.tsx | 19 +- .../app/builder/_components/SortableItem.tsx | 15 +- apps/web/src/core/FormBuilder/FormBuilder.tsx | 2 +- apps/web/src/core/FormList.tsx | 30 +- apps/web/src/state/state.ts | 77 ++-- packages/core/src/types/field.ts | 405 +++++++++++------- packages/core/src/types/index.ts | 23 +- packages/core/src/utils/newField.ts | 54 +-- 11 files changed, 416 insertions(+), 314 deletions(-) diff --git a/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx b/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx index aaa77e3..4c0ebfe 100644 --- a/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx +++ b/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx @@ -9,6 +9,7 @@ import { fileFieldVariants, selectionFieldVariants, type FieldKind, + FormFramework, } from "formbuilder-core"; import { Accordion, @@ -19,15 +20,7 @@ import { import { cn } from "@/lib/utils"; import { ChevronDown, XIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; - -const variantMap = { - text: textFieldVariants, - number: numberFieldVariants, - boolean: booleanFieldVariants, - date: dateFieldVariants, - file: fileFieldVariants, - enum: selectionFieldVariants, -} as const; +import { getVariantMap } from "@/utils/getVariantMap"; export function AddFieldAccordion({ field, @@ -38,6 +31,7 @@ export function AddFieldAccordion({ }; }) { const state = useAppState(); + const variantMap = getVariantMap(state.currentForm.framework); const [isOpen, setIsOpen] = React.useState(false); return ( - -
- - {id} - -
- - - - -
+
+
+ + {label} + +
+ +
- - Yes. It adheres to the WAI-ARIA design pattern. - - - +
+
); } diff --git a/apps/web/src/app/builder/_components/FormSettings/index.tsx b/apps/web/src/app/builder/_components/FormSettings/index.tsx index 24940c4..f38854f 100644 --- a/apps/web/src/app/builder/_components/FormSettings/index.tsx +++ b/apps/web/src/app/builder/_components/FormSettings/index.tsx @@ -15,7 +15,12 @@ export default function SettingsForm() { value={state.currentForm.name} onChange={(newValue) => state.updateFormName(newValue)} /> - +
+

+ Framework +

+ +

Form Submission @@ -62,9 +67,9 @@ export default function SettingsForm() { - state.updateFormSettings({ importAlias: newValue }) + state.updateFormSettings({ importAliasUtils: newValue }) } /> @@ -101,13 +106,6 @@ export default function SettingsForm() { />

- -
-

- Framework -

- -
); } diff --git a/apps/web/src/app/builder/_components/SortableGrid.tsx b/apps/web/src/app/builder/_components/SortableGrid.tsx index f7affc0..11a6f95 100644 --- a/apps/web/src/app/builder/_components/SortableGrid.tsx +++ b/apps/web/src/app/builder/_components/SortableGrid.tsx @@ -1,30 +1,25 @@ 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 { MouseSensor } from "./CustomSensor"; import { useAppState } from "@/state/state"; +import type { FieldVariant, FormFramework, FrameworkFieldKinds, FrameworkFieldVariants } from "formbuilder-core"; export const SortableGrid = () => { const [activeId, setActiveId] = useState(); @@ -110,7 +105,9 @@ export const SortableGrid = () => { ( + formField.variant, + )} /> ))} @@ -132,3 +129,11 @@ export const SortableGrid = () => { ); }; + +function findLabel(variant: FrameworkFieldVariants>): string { + variant + const foundVariant = allFieldVariants.find( + (field) => field.value === variant, + ); + return foundVariant?.label!; +} diff --git a/apps/web/src/app/builder/_components/SortableItem.tsx b/apps/web/src/app/builder/_components/SortableItem.tsx index aaf129a..94e9df7 100644 --- a/apps/web/src/app/builder/_components/SortableItem.tsx +++ b/apps/web/src/app/builder/_components/SortableItem.tsx @@ -6,20 +6,13 @@ import { useAppState } from "@/state/state"; import { FormFieldContent } from "./FormFieldContent"; import { AddNewFieldArrows } from "./AddNewFieldArrows"; -interface TaskCardProps { +interface FormFieldProps { id: string; - value: string; + label: string; isOverlay?: boolean; } -export type TaskType = "Task"; - -// export interface TaskDragData { -// type: TaskType; -// task: Task; -// } - -export function SortableItem({ id, value, isOverlay }: TaskCardProps) { +export function SortableItem({ id, label, isOverlay }: FormFieldProps) { const { setNodeRef, attributes, @@ -63,7 +56,7 @@ export function SortableItem({ id, value, isOverlay }: TaskCardProps) { }), )} > - + ) : (
diff --git a/apps/web/src/core/FormBuilder/FormBuilder.tsx b/apps/web/src/core/FormBuilder/FormBuilder.tsx index 2b1ea2f..01f41f0 100644 --- a/apps/web/src/core/FormBuilder/FormBuilder.tsx +++ b/apps/web/src/core/FormBuilder/FormBuilder.tsx @@ -8,7 +8,7 @@ import { newDateField, newEnumField, newNumberField, - newStringField, + newTextField, newTextAreaField, } from "formbuilder-core"; import { zodResolver } from "@hookform/resolvers/zod"; diff --git a/apps/web/src/core/FormList.tsx b/apps/web/src/core/FormList.tsx index 250eba4..7dfc363 100644 --- a/apps/web/src/core/FormList.tsx +++ b/apps/web/src/core/FormList.tsx @@ -11,7 +11,10 @@ export function FormList() {
    {forms?.map((f, idx) => (
  • - @@ -26,9 +29,30 @@ export function FormList() { onClick={() => newForm({ id: 1, - settings: { importAlias: "a", mode: "a" }, + settings: { importAliasComponents: "a", importAliasUtils: "a" }, name: "My Form", - fields: [], + fields: [ + [ + { + id: "textField1", + kind: "text", + label: "First Text Field", + key: "firstTextField", + required: true, + variant: "input", + placeholder: "Enter first text", + }, + { + id: "textField2", + kind: "text", + label: "First Text Field", + key: "firstTextField", + required: true, + variant: "input", + placeholder: "Enter first text", + }, + ], + ], framework: "react", }) } diff --git a/apps/web/src/state/state.ts b/apps/web/src/state/state.ts index fed3516..c0596d1 100644 --- a/apps/web/src/state/state.ts +++ b/apps/web/src/state/state.ts @@ -4,39 +4,38 @@ import { type FormField, type FormFramework, newBooleanField, - type FieldKind, newDateField, - newTextAreaField, newEnumField, newNumberField, - newStringField, - type FormVariant, + newTextField, type ChosenField, } from "formbuilder-core"; import { persistentAtom } from "@nanostores/persistent"; import { useStore } from "@nanostores/react"; import { findFieldIndex } from "@/utils/findFieldIndex"; import { mockForm } from "@/mock/mockForm"; +import { newStringField } from "@/utils/newField"; -export type State = { +type State = { selectedForm: number; - forms: FormSchema[]; + forms: FormSchema[]; renderContent: boolean; - chosenField: ChosenField | null; + chosenField: ChosenField | null; }; -export const $appState = persistentAtom( +type MockFormFramework = typeof mockForm['framework']; +export const $appState = persistentAtom>( "state", { renderContent: true, chosenField: null, selectedForm: 0, - forms: [mockForm], + forms: [mockForm as FormSchema], }, { encode: JSON.stringify, decode: JSON.parse, - }, + } ); export function useAppState() { @@ -69,10 +68,9 @@ function newForm(f: FormSchema) { forms: currentForms.concat(f), }); } - -function updateFormFields(p: FormField[][]) { +function updateFormFields(fields: FormField[][]) { const newForms = $appState.get().forms; - newForms[$appState.get().selectedForm].fields = p; + newForms[$appState.get().selectedForm].fields = fields; $appState.set({ ...$appState.get(), forms: newForms, @@ -100,24 +98,42 @@ function updateFormName(newName: string) { forms: currentForms, }); } +function createNewField( + chosenField: ChosenField +): FormField { + const currentForm = $appState.get().forms[$appState.get().selectedForm]; + const { noDescription, noPlaceholder } = currentForm.settings; + + // TODO: newField.ts is broken after the new types + // switch (chosenField.kind) { + // case "text": + // return newStringField(); + // case "number": + // return newNumberField(); + // case "boolean": + // return newBooleanField(); + // case "enum": + // return newEnumField(); + // case "date": + // return newDateField(); + // case "file": + // return newFile(); + // } + // Create a base field specific to the framework and kind + const baseField: Partial> = { + id: crypto.randomUUID(), + label: `New ${chosenField.kind} Field`, + key: `new_${chosenField.kind}_${Date.now()}`, + required: false, + kind: chosenField.kind, + variant: chosenField.variant, + }; -function createNewField(chosenField: ChosenField): FormField { - switch (chosenField.kind) { - case "text": - return newStringField(); - case "number": - return newNumberField(); - case "boolean": - return newBooleanField(); - case "enum": - return newEnumField(); - case "date": - return newDateField(); - // case "text": - // return newTextAreaField(); - default: - return newBooleanField(); // Return undefined if chosenField is not recognized - } + // Apply settings + if (!noDescription) baseField.desc = ""; + if (!noPlaceholder) baseField.placeholder = ""; + + return baseField as FormField; } function addItem(id: string, direction: "up" | "down" | "left" | "right") { @@ -164,7 +180,6 @@ function addItem(id: string, direction: "up" | "down" | "left" | "right") { newFields[row].splice(col + 1, 0, newItem); break; } - currentForm.fields = newFields; $appState.set({ ...$appState.get(), diff --git a/packages/core/src/types/field.ts b/packages/core/src/types/field.ts index 7a93d3e..b81a203 100644 --- a/packages/core/src/types/field.ts +++ b/packages/core/src/types/field.ts @@ -1,28 +1,5 @@ -export type FormField = - | TextField - | NumberField - | BooleanField - | DateField - | FileField - | EnumField; - -export type FormVariant = - | TextFieldVariant - | NumberFieldVariant - | BooleanFieldVariant - | DateFieldVariant - | FileFieldVariant - | SelectionFieldVariant; - -export const fieldKind = [ - "text", - "number", - "boolean", - "date", - "file", - "enum", -] as const; -export type FieldKind = (typeof fieldKind)[number]; +import type { FormFramework, FrameworkSettings } from "."; +import type { Prettify } from "./prettify"; export type BaseField = { id: string; @@ -36,11 +13,65 @@ export type BaseField = { className?: string; tooltip?: string; message?: string; - frameworkSettings?: FrameworkFieldSettings; }; -// Text Field -export const textFieldVariants = [ +// Framework-specific field kinds +export const nextFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +export const reactFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +export const vueFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +export const svelteFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +export const solidFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +export const astroFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +// Framework-specific variants +const textVariants = [ { label: "Input", value: "input" }, { label: "Textarea", value: "textarea" }, { label: "Rich Text Editor", value: "rich-text" }, @@ -49,118 +80,41 @@ export const textFieldVariants = [ { label: "Password Input", value: "password" }, { label: "Auto-resizing Textarea", value: "autosize-textarea" }, { label: "OTP Input", value: "input-otp" }, - { label: "Input Mask", value: "input-mask" }, + { label: "Input Mask", value: "input-mask" } ] as const; -export type TextFieldVariant = (typeof textFieldVariants)[number]["value"]; - -export type TextField = BaseField & { - kind: "text"; - variant: TextFieldVariant; - defaultValue?: string; - validation?: TextValidation; -}; - -export type TextValidation = { - min?: number; - max?: number; - pattern?: string; - email?: boolean; - url?: boolean; -}; -// Number Field -export const numberFieldVariants = [ +const numberVariants = [ { label: "Number Input", value: "input" }, { label: "Slider", value: "slider" }, { label: "Rating", value: "rating" }, { label: "OTP", value: "otp" }, - { label: "Stepper", value: "stepper" }, + { label: "Stepper", value: "stepper" } ] as const; -export type NumberFieldVariant = (typeof numberFieldVariants)[number]["value"]; - -export type NumberField = BaseField & { - kind: "number"; - variant: NumberFieldVariant; - defaultValue?: number; - validation?: NumberValidation; -}; - -export type NumberValidation = { - min?: number; - max?: number; - step?: number; - precision?: number; - allowNegative?: boolean; - allowDecimal?: boolean; -}; -// Boolean Field -export const booleanFieldVariants = [ +const booleanVariants = [ { label: "Checkbox", value: "checkbox" }, { label: "Switch", value: "switch" }, { label: "Toggle", value: "toggle" }, ] as const; -export type BooleanFieldVariant = - (typeof booleanFieldVariants)[number]["value"]; -export type BooleanField = BaseField & { - kind: "boolean"; - variant: BooleanFieldVariant; - defaultValue?: boolean; -}; - -// Date Field -export const dateFieldVariants = [ +const dateVariants = [ { label: "Date Picker", value: "date" }, { label: "Date & Time Picker", value: "datetime" }, { label: "Time Picker", value: "time" }, { label: "Date Range Picker", value: "daterange" }, - // Shadcn specific { label: "Calendar Picker", value: "calendar-picker" }, { label: "Date Range Calendar", value: "date-range-picker" }, ] as const; -export type DateFieldVariant = (typeof dateFieldVariants)[number]["value"]; -export type DateField = BaseField & { - kind: "date"; - variant: DateFieldVariant; - defaultValue?: string | Date; - validation?: DateValidation; -}; - -export type DateValidation = { - minDate?: string | Date; - maxDate?: string | Date; - excludeDates?: Array; - excludeWeekends?: boolean; -}; - -// File Field -export const fileFieldVariants = [ +const fileVariants = [ { label: "Single File Upload", value: "single" }, { label: "Multiple File Upload", value: "multiple" }, { label: "Drag & Drop Zone", value: "drag-drop" }, { label: "Avatar Upload", value: "avatar" }, { label: "Image Gallery", value: "image-gallery" }, ] as const; -export type FileFieldVariant = (typeof fileFieldVariants)[number]["value"]; - -export type FileField = BaseField & { - kind: "file"; - variant: FileFieldVariant; - defaultValue?: string | string[]; - validation?: FileValidation; -}; -export type FileValidation = { - maxSize?: number; - allowedTypes?: string[]; - maxFiles?: number; - minFiles?: number; -}; - -// Enum Field -export const selectionFieldVariants = [ +const enumVariants = [ { label: "Select Dropdown", value: "select" }, { label: "Combobox", value: "combobox" }, { label: "Radio Group", value: "radio" }, @@ -168,18 +122,148 @@ export const selectionFieldVariants = [ { label: "Segmented Control", value: "segmented" }, { label: "Chips Input", value: "chips" }, ] as const; -export type SelectionFieldVariant = - (typeof selectionFieldVariants)[number]["value"]; -export type EnumField = BaseField & { - kind: "enum"; - variant: SelectionFieldVariant; - enumValues: EnumValue[]; - enumName?: string; - defaultValue?: string | string[]; - validation?: SelectionValidation; +export const nextFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +export const reactFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +export const vueFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +export const svelteFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +export const solidFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +export const astroFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +// Type helpers, Useless? +export type FrameworkFieldKinds = { + next: typeof nextFieldKinds[number]; + react: typeof reactFieldKinds[number]; + vue: typeof vueFieldKinds[number]; + svelte: typeof svelteFieldKinds[number]; + solid: typeof solidFieldKinds[number]; + astro: typeof astroFieldKinds[number]; +}; + +type ExtractVariantValue = T extends readonly { value: infer V }[] ? V : never; + +// this is new +export type FrameworkFieldVariants = + F extends "next" + ? K extends keyof typeof nextFieldVariants + ? ExtractVariantValue + : never + : F extends "react" + ? K extends keyof typeof reactFieldVariants + ? ExtractVariantValue + : never + : F extends "vue" + ? K extends keyof typeof vueFieldVariants + ? ExtractVariantValue + : never + : F extends "svelte" + ? K extends keyof typeof svelteFieldVariants + ? ExtractVariantValue + : never + : F extends "solid" + ? K extends keyof typeof solidFieldVariants + ? ExtractVariantValue + : never + : F extends "astro" + ? K extends keyof typeof astroFieldVariants + ? ExtractVariantValue + : never + : never; + +// useless? +// export type FieldVariants = typeof nextFieldVariants[K]; +// export function getVariants(kind: K): FieldVariants { +// return nextFieldVariants[kind]; +// } + +// TODO: implement helpers, useless? +// type TextVariantValues = ExtractVariantValue; +// type NextTextFieldVariants = FrameworkFieldVariants<"next", "text">; + +// Validation types +export type TextValidation = { + min?: number; + max?: number; + pattern?: string; + email?: boolean; + url?: boolean; +}; + +export type NumberValidation = { + min?: number; + max?: number; + step?: number; + precision?: number; + allowNegative?: boolean; + allowDecimal?: boolean; +}; + +export type DateValidation = { + minDate?: string | Date; + maxDate?: string | Date; + excludeDates?: Array; + excludeWeekends?: boolean; +}; + +export type FileValidation = { + maxSize?: number; + allowedTypes?: string[]; + maxFiles?: number; + minFiles?: number; }; +export type EnumValidation = { + minSelect?: number; + maxSelect?: number; + unique?: boolean; +}; export type EnumValue = { label: string; value: string; @@ -188,43 +272,60 @@ export type EnumValue = { description?: string; }; -export type SelectionValidation = { - minSelect?: number; - maxSelect?: number; - unique?: boolean; +export type BooleanValidation = { + required?: boolean; }; -// Framework Settings -export type FrameworkFieldSettings = { - next?: NextFieldSettings; - react?: ReactFieldSettings; - svelte?: SvelteFieldSettings; - vue?: VueFieldSettings; - solid?: SolidFieldSettings; - astro?: AstroFieldSettings; +export type TextField = BaseField & { + kind: "text"; + variant: FrameworkFieldVariants; + defaultValue?: string; + validation?: TextValidation; }; - -export type NextFieldSettings = { - serverAction?: boolean; - apiEndpoint?: string; +export type NumberField = BaseField & { + kind: "number"; + variant: FrameworkFieldVariants; + defaultValue?: number; + validation?: NumberValidation; }; - -export type ReactFieldSettings = { - stateKey?: string; +export type BooleanField = BaseField & { + kind: "boolean"; + variant: FrameworkFieldVariants; + defaultValue?: boolean; + validation?: BooleanValidation; }; - -export type SvelteFieldSettings = { - reactive?: boolean; +export type DateField = BaseField & { + kind: "date"; + variant: FrameworkFieldVariants; + defaultValue?: number; + validation?: DateValidation; }; - -export type VueFieldSettings = { - vModel?: string; +export type FileField = BaseField & { + kind: "file"; + variant: FrameworkFieldVariants; + defaultValue?: File; + validation?: DateValidation; }; - -export type SolidFieldSettings = { - signal?: string; +export type EnumField = BaseField & { + kind: "enum"; + variant: FrameworkFieldVariants; + defaultValue?: string[]; + enumName?: string + enumValues?: EnumValue[] + validation?: DateValidation; }; +export type FormField = + | TextField + | NumberField + | BooleanField + | DateField + | FileField + | EnumField; -export type AstroFieldSettings = { - partial?: boolean; -}; +export type FieldVariant = + | TextField + | NumberField + | BooleanField + | DateField + | FileField + | EnumField; \ No newline at end of file diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index 6e8ee2c..e1f1ec2 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,5 +1,5 @@ import type { Prettify } from "./prettify"; -import type { FieldKind, FormVariant, FormField } from "./field"; +import type { FormField, FrameworkFieldKinds, FrameworkFieldKinds } from "./field"; export type FormFramework = | "next" @@ -9,21 +9,28 @@ export type FormFramework = | "solid" | "astro"; -export type ChosenField = { kind: FieldKind; variant: FormVariant }; +export type ChosenField = { + kind: FrameworkFieldKinds[F]; + variant: FrameworkFieldKinds; +}; -export type FormSchema = Prettify<{ +export type FormSchema = Prettify<{ id: number; name: string; - framework: FormFramework; - fields: FormField[][]; - settings: Settings; + framework: F; + fields: FormField[][]; + settings: Settings & { + frameworkSettings?: { + [K in F]: FrameworkSettings[K]; + }; + }; }>; export type Settings = { importAliasComponents: string; importAliasUtils: string; - noDescription: boolean; - noPlaceholder: boolean; + noDescription?: boolean; + noPlaceholder?: boolean; frameworkSettings?: FrameworkSettings; }; diff --git a/packages/core/src/utils/newField.ts b/packages/core/src/utils/newField.ts index 2b92373..d382cc1 100644 --- a/packages/core/src/utils/newField.ts +++ b/packages/core/src/utils/newField.ts @@ -1,15 +1,20 @@ -import type { FormField } from "@/types"; +import type { + BooleanFieldVariant, DateFieldVariant, EnumFieldVariant, + FormField, FormVariant, NumberFieldVariant, + TextFieldVariant +} from "@/types/field"; import { randNum } from "./randNum"; -export function newStringField(): FormField { +export function newTextField(variant: FormVariant): FormField { return { id: `id${randNum()}`, key: `key${randNum()}`, label: "My string", desc: "Description", placeholder: "Placeholder", - kind: "string", + kind: "text", + variant: variant as TextFieldVariant, defaultValue: "string", required: true, validation: { @@ -18,7 +23,7 @@ export function newStringField(): FormField { }, }; } -export function newNumberField(): FormField { +export function newNumberField(variant: NumberFieldVariant): FormField { return { id: `id${randNum()}`, key: `key${randNum()}`, @@ -26,8 +31,7 @@ export function newNumberField(): FormField { desc: "Description", placeholder: "Placeholder", kind: "number", - enumName: "myEnum", - enumValues: [], + variant: variant as NumberFieldVariant, defaultValue: 1, required: true, validation: { @@ -36,7 +40,7 @@ export function newNumberField(): FormField { }, }; } -export function newBooleanField(): FormField { +export function newBooleanField(variant: BooleanFieldVariant): FormField { return { id: `id${randNum()}`, key: `key${randNum()}`, @@ -44,25 +48,13 @@ export function newBooleanField(): FormField { desc: "Description", placeholder: "Placeholder", kind: "boolean", + variant: variant as BooleanFieldVariant, defaultValue: true, required: true, }; } -export function newEnumField(): FormField { - return { - id: `id${randNum()}`, - key: `key${randNum()}`, - label: "My enum", - desc: "Description", - placeholder: "Placeholder", - kind: "enum", - style: "combobox", - enumName: `myEnum${randNum()}`, - enumValues: [{ id: Date.now().toString(), label: "label", value: "value" }], - required: true, - }; -} -export function newDateField(): FormField { + +export function newDateField(variant: DateFieldVariant): FormField { return { id: `id${randNum()}`, key: `key${randNum()}`, @@ -70,22 +62,22 @@ export function newDateField(): FormField { desc: "Description", placeholder: "Pick a date", kind: "date", + variant: variant as DateFieldVariant, required: true, }; } -export function newTextAreaField(): FormField { + +export function newEnumField(variant: EnumFieldVariant): FormField { return { id: `id${randNum()}`, key: `key${randNum()}`, - label: "My textarea", + label: "My enum", desc: "Description", placeholder: "Placeholder", - kind: "textarea", - defaultValue: "textarea", + kind: "enum", + variant: variant as EnumFieldVariant, + enumName: `myEnum${randNum()}`, + enumValues: [{ id: Date.now().toString(), label: "label", value: "value" }], required: true, - validation: { - min: 1, - max: 255, - }, }; -} // +} \ No newline at end of file From cace9cc2ad778f4ce1cce577578e56bd7f10d509 Mon Sep 17 00:00:00 2001 From: KryptXBSA Date: Tue, 31 Dec 2024 18:26:47 +0300 Subject: [PATCH 07/22] Refactor Core Type Definitions --- packages/core/src/index.ts | 2 +- packages/core/src/types/field.ts | 326 ++++------------------- packages/core/src/types/fieldVariants.ts | 177 ++++++++++++ packages/core/src/types/index.ts | 15 +- 4 files changed, 240 insertions(+), 280 deletions(-) create mode 100644 packages/core/src/types/fieldVariants.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 44c0db0..dbdc0af 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,6 +1,6 @@ export * from "./codegen"; export * from "./codegen/imports"; export * from "./types"; -export * from "./types/field"; +export * from "./types/field-old2"; export * from "./types/prettify"; export * from "./utils"; diff --git a/packages/core/src/types/field.ts b/packages/core/src/types/field.ts index b81a203..574799e 100644 --- a/packages/core/src/types/field.ts +++ b/packages/core/src/types/field.ts @@ -1,5 +1,13 @@ -import type { FormFramework, FrameworkSettings } from "."; -import type { Prettify } from "./prettify"; +import type { FormFramework } from "./index"; +import type { FrameworkFieldVariants } from "./fieldVariants"; + +export type FormField = + | TextField + | NumberField + | BooleanField + | DateField + | FileField + | EnumField; export type BaseField = { id: string; @@ -15,219 +23,50 @@ export type BaseField = { message?: string; }; -// Framework-specific field kinds -export const nextFieldKinds = [ - "text", - "number", - "boolean", - "date", - "file", - "enum", -] as const; - -export const reactFieldKinds = [ - "text", - "number", - "boolean", - "date", - "file", - "enum", -] as const; - -export const vueFieldKinds = [ - "text", - "number", - "boolean", - "date", - "file", - "enum", -] as const; - -export const svelteFieldKinds = [ - "text", - "number", - "boolean", - "date", - "file", - "enum", -] as const; - -export const solidFieldKinds = [ - "text", - "number", - "boolean", - "date", - "file", - "enum", -] as const; - -export const astroFieldKinds = [ - "text", - "number", - "boolean", - "date", - "file", - "enum", -] as const; - -// Framework-specific variants -const textVariants = [ - { label: "Input", value: "input" }, - { label: "Textarea", value: "textarea" }, - { label: "Rich Text Editor", value: "rich-text" }, - { label: "Markdown Editor", value: "markdown" }, - { label: "Code Editor", value: "code" }, - { label: "Password Input", value: "password" }, - { label: "Auto-resizing Textarea", value: "autosize-textarea" }, - { label: "OTP Input", value: "input-otp" }, - { label: "Input Mask", value: "input-mask" } -] as const; - -const numberVariants = [ - { label: "Number Input", value: "input" }, - { label: "Slider", value: "slider" }, - { label: "Rating", value: "rating" }, - { label: "OTP", value: "otp" }, - { label: "Stepper", value: "stepper" } -] as const; - -const booleanVariants = [ - { label: "Checkbox", value: "checkbox" }, - { label: "Switch", value: "switch" }, - { label: "Toggle", value: "toggle" }, -] as const; - -const dateVariants = [ - { label: "Date Picker", value: "date" }, - { label: "Date & Time Picker", value: "datetime" }, - { label: "Time Picker", value: "time" }, - { label: "Date Range Picker", value: "daterange" }, - { label: "Calendar Picker", value: "calendar-picker" }, - { label: "Date Range Calendar", value: "date-range-picker" }, -] as const; - -const fileVariants = [ - { label: "Single File Upload", value: "single" }, - { label: "Multiple File Upload", value: "multiple" }, - { label: "Drag & Drop Zone", value: "drag-drop" }, - { label: "Avatar Upload", value: "avatar" }, - { label: "Image Gallery", value: "image-gallery" }, -] as const; - -const enumVariants = [ - { label: "Select Dropdown", value: "select" }, - { label: "Combobox", value: "combobox" }, - { label: "Radio Group", value: "radio" }, - { label: "Checkbox Group", value: "checkbox" }, - { label: "Segmented Control", value: "segmented" }, - { label: "Chips Input", value: "chips" }, -] as const; - -export const nextFieldVariants = { - text: textVariants, - number: numberVariants, - boolean: booleanVariants, - date: dateVariants, - file: fileVariants, - enum: enumVariants, -} as const; - -export const reactFieldVariants = { - text: textVariants, - number: numberVariants, - boolean: booleanVariants, - date: dateVariants, - file: fileVariants, - enum: enumVariants, -} as const; - -export const vueFieldVariants = { - text: textVariants, - number: numberVariants, - boolean: booleanVariants, - date: dateVariants, - file: fileVariants, - enum: enumVariants, -} as const; - -export const svelteFieldVariants = { - text: textVariants, - number: numberVariants, - boolean: booleanVariants, - date: dateVariants, - file: fileVariants, - enum: enumVariants, -} as const; - -export const solidFieldVariants = { - text: textVariants, - number: numberVariants, - boolean: booleanVariants, - date: dateVariants, - file: fileVariants, - enum: enumVariants, -} as const; - -export const astroFieldVariants = { - text: textVariants, - number: numberVariants, - boolean: booleanVariants, - date: dateVariants, - file: fileVariants, - enum: enumVariants, -} as const; +export type TextField = BaseField & { + kind: "text"; + variant: FrameworkFieldVariants[F]; + defaultValue?: string; + validation?: TextValidation; +}; -// Type helpers, Useless? -export type FrameworkFieldKinds = { - next: typeof nextFieldKinds[number]; - react: typeof reactFieldKinds[number]; - vue: typeof vueFieldKinds[number]; - svelte: typeof svelteFieldKinds[number]; - solid: typeof solidFieldKinds[number]; - astro: typeof astroFieldKinds[number]; +export type NumberField = BaseField & { + kind: "number"; + variant: FrameworkFieldVariants[F]; + defaultValue?: number; + validation?: NumberValidation; }; -type ExtractVariantValue = T extends readonly { value: infer V }[] ? V : never; +export type BooleanField = BaseField & { + kind: "boolean"; + variant: FrameworkFieldVariants[F]; + defaultValue?: boolean; + validation?: BooleanValidation; +}; -// this is new -export type FrameworkFieldVariants = - F extends "next" - ? K extends keyof typeof nextFieldVariants - ? ExtractVariantValue - : never - : F extends "react" - ? K extends keyof typeof reactFieldVariants - ? ExtractVariantValue - : never - : F extends "vue" - ? K extends keyof typeof vueFieldVariants - ? ExtractVariantValue - : never - : F extends "svelte" - ? K extends keyof typeof svelteFieldVariants - ? ExtractVariantValue - : never - : F extends "solid" - ? K extends keyof typeof solidFieldVariants - ? ExtractVariantValue - : never - : F extends "astro" - ? K extends keyof typeof astroFieldVariants - ? ExtractVariantValue - : never - : never; +export type DateField = BaseField & { + kind: "date"; + variant: FrameworkFieldVariants[F]; + defaultValue?: number; + validation?: DateValidation; +}; -// useless? -// export type FieldVariants = typeof nextFieldVariants[K]; -// export function getVariants(kind: K): FieldVariants { -// return nextFieldVariants[kind]; -// } +export type FileField = BaseField & { + kind: "file"; + variant: FrameworkFieldVariants[F]; + defaultValue?: File; + validation?: DateValidation; +}; -// TODO: implement helpers, useless? -// type TextVariantValues = ExtractVariantValue; -// type NextTextFieldVariants = FrameworkFieldVariants<"next", "text">; +export type EnumField = BaseField & { + kind: "enum"; + variant: FrameworkFieldVariants[F]; + defaultValue?: string[]; + enumName?: string; + enumValues?: EnumValue[]; + validation?: EnumValidation; +}; -// Validation types export type TextValidation = { min?: number; max?: number; @@ -245,6 +84,10 @@ export type NumberValidation = { allowDecimal?: boolean; }; +export type BooleanValidation = { + required?: boolean; +}; + export type DateValidation = { minDate?: string | Date; maxDate?: string | Date; @@ -259,11 +102,6 @@ export type FileValidation = { minFiles?: number; }; -export type EnumValidation = { - minSelect?: number; - maxSelect?: number; - unique?: boolean; -}; export type EnumValue = { label: string; value: string; @@ -272,60 +110,8 @@ export type EnumValue = { description?: string; }; -export type BooleanValidation = { - required?: boolean; -}; - -export type TextField = BaseField & { - kind: "text"; - variant: FrameworkFieldVariants; - defaultValue?: string; - validation?: TextValidation; -}; -export type NumberField = BaseField & { - kind: "number"; - variant: FrameworkFieldVariants; - defaultValue?: number; - validation?: NumberValidation; -}; -export type BooleanField = BaseField & { - kind: "boolean"; - variant: FrameworkFieldVariants; - defaultValue?: boolean; - validation?: BooleanValidation; -}; -export type DateField = BaseField & { - kind: "date"; - variant: FrameworkFieldVariants; - defaultValue?: number; - validation?: DateValidation; -}; -export type FileField = BaseField & { - kind: "file"; - variant: FrameworkFieldVariants; - defaultValue?: File; - validation?: DateValidation; -}; -export type EnumField = BaseField & { - kind: "enum"; - variant: FrameworkFieldVariants; - defaultValue?: string[]; - enumName?: string - enumValues?: EnumValue[] - validation?: DateValidation; -}; -export type FormField = - | TextField - | NumberField - | BooleanField - | DateField - | FileField - | EnumField; - -export type FieldVariant = - | TextField - | NumberField - | BooleanField - | DateField - | FileField - | EnumField; \ No newline at end of file +export type EnumValidation = { + minSelect?: number; + maxSelect?: number; + unique?: boolean; +}; \ No newline at end of file diff --git a/packages/core/src/types/fieldVariants.ts b/packages/core/src/types/fieldVariants.ts new file mode 100644 index 0000000..0839614 --- /dev/null +++ b/packages/core/src/types/fieldVariants.ts @@ -0,0 +1,177 @@ +export const nextFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +export const reactFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +export const vueFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +export const svelteFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +export const solidFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +export const astroFieldKinds = [ + "text", + "number", + "boolean", + "date", + "file", + "enum", +] as const; + +const textVariants = [ + { label: "Input", value: "input" }, + { label: "Textarea", value: "textarea" }, + { label: "Rich Text Editor", value: "rich-text" }, + { label: "Markdown Editor", value: "markdown" }, + { label: "Code Editor", value: "code" }, + { label: "Password Input", value: "password" }, + { label: "Auto-resizing Textarea", value: "autosize-textarea" }, + { label: "OTP Input", value: "input-otp" }, + { label: "Input Mask", value: "input-mask" } +] as const; + +const numberVariants = [ + { label: "Number Input", value: "input" }, + { label: "Slider", value: "slider" }, + { label: "Rating", value: "rating" }, + { label: "OTP", value: "otp" }, + { label: "Stepper", value: "stepper" } +] as const; + +const booleanVariants = [ + { label: "Checkbox", value: "checkbox" }, + { label: "Switch", value: "switch" }, + { label: "Toggle", value: "toggle" }, +] as const; + +const dateVariants = [ + { label: "Date Picker", value: "date" }, + { label: "Date & Time Picker", value: "datetime" }, + { label: "Time Picker", value: "time" }, + { label: "Date Range Picker", value: "daterange" }, + { label: "Calendar Picker", value: "calendar-picker" }, + { label: "Date Range Calendar", value: "date-range-picker" }, +] as const; + +const fileVariants = [ + { label: "Single File Upload", value: "single" }, + { label: "Multiple File Upload", value: "multiple" }, + { label: "Drag & Drop Zone", value: "drag-drop" }, + { label: "Avatar Upload", value: "avatar" }, + { label: "Image Gallery", value: "image-gallery" }, +] as const; + +const enumVariants = [ + { label: "Select Dropdown", value: "select" }, + { label: "Combobox", value: "combobox" }, + { label: "Radio Group", value: "radio" }, + { label: "Checkbox Group", value: "checkbox" }, + { label: "Segmented Control", value: "segmented" }, + { label: "Chips Input", value: "chips" }, +] as const; + +export const nextFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +export const reactFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +export const vueFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +export const svelteFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +export const solidFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +export const astroFieldVariants = { + text: textVariants, + number: numberVariants, + boolean: booleanVariants, + date: dateVariants, + file: fileVariants, + enum: enumVariants, +} as const; + +export type FrameworkFieldKinds = { + next: typeof nextFieldKinds[number]; + react: typeof reactFieldKinds[number]; + vue: typeof vueFieldKinds[number]; + svelte: typeof svelteFieldKinds[number]; + solid: typeof solidFieldKinds[number]; + astro: typeof astroFieldKinds[number]; +}; + +export type FrameworkFieldVariants = { + next: (typeof nextFieldVariants)[keyof typeof nextFieldVariants][number]['value']; + react: (typeof reactFieldVariants)[keyof typeof reactFieldVariants][number]['value']; + vue: (typeof vueFieldVariants)[keyof typeof vueFieldVariants][number]['value']; + svelte: (typeof svelteFieldVariants)[keyof typeof svelteFieldVariants][number]['value']; + solid: (typeof solidFieldVariants)[keyof typeof solidFieldVariants][number]['value']; + astro: (typeof astroFieldVariants)[keyof typeof astroFieldVariants][number]['value']; +}; \ No newline at end of file diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts index e1f1ec2..45d2e4e 100644 --- a/packages/core/src/types/index.ts +++ b/packages/core/src/types/index.ts @@ -1,5 +1,6 @@ import type { Prettify } from "./prettify"; -import type { FormField, FrameworkFieldKinds, FrameworkFieldKinds } from "./field"; +import type { FormField } from "./field"; +import type { FrameworkFieldVariants, FrameworkFieldKinds } from "./fieldVariants"; export type FormFramework = | "next" @@ -11,7 +12,7 @@ export type FormFramework = export type ChosenField = { kind: FrameworkFieldKinds[F]; - variant: FrameworkFieldKinds; + variant: FrameworkFieldVariants[F]; }; export type FormSchema = Prettify<{ @@ -19,11 +20,7 @@ export type FormSchema = Prettify<{ name: string; framework: F; fields: FormField[][]; - settings: Settings & { - frameworkSettings?: { - [K in F]: FrameworkSettings[K]; - }; - }; + settings: Settings }>; export type Settings = { @@ -36,11 +33,11 @@ export type Settings = { export type FrameworkSettings = { next?: { - useServerActions: boolean; + useServerActions?: boolean; apiRoute?: string; }; react?: { - stateManager: "context" | "redux" | "none"; + stateManager?: "context" | "redux" | "none"; }; svelte?: { kit: boolean; From 5bbcf07de63e83a5d3c8422210bb57b0b37d50de Mon Sep 17 00:00:00 2001 From: KryptXBSA Date: Tue, 31 Dec 2024 19:42:06 +0300 Subject: [PATCH 08/22] version core --- .changeset/cuddly-mails-stare.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cuddly-mails-stare.md diff --git a/.changeset/cuddly-mails-stare.md b/.changeset/cuddly-mails-stare.md new file mode 100644 index 0000000..3bee2e6 --- /dev/null +++ b/.changeset/cuddly-mails-stare.md @@ -0,0 +1,5 @@ +--- +"formbuilder-core": minor +--- + +Refactor Core Type Definitions From 4905d221ac874b7959c74daaf4e1b3182568715b Mon Sep 17 00:00:00 2001 From: KryptXBSA Date: Tue, 31 Dec 2024 23:59:37 +0300 Subject: [PATCH 09/22] fix ui and build errs --- apps/web/package.json | 2 +- .../AddField/AddFieldAccordion.tsx | 30 ++++---- .../builder/_components/AddField/index.tsx | 21 +----- .../builder/_components/AddNewFieldArrows.tsx | 1 - .../app/builder/_components/SortableGrid.tsx | 17 +++-- apps/web/src/app/builder/page.tsx | 8 ++- apps/web/src/core/Preview/Preview.tsx | 2 - .../utils/findKindByVariantAndFramework.ts | 18 +++++ bun.lockb | Bin 150360 -> 150360 bytes packages/core/src/index.ts | 3 +- packages/core/src/types/fieldVariants.ts | 66 ++++++++++++++++++ 11 files changed, 123 insertions(+), 45 deletions(-) create mode 100644 apps/web/src/utils/findKindByVariantAndFramework.ts diff --git a/apps/web/package.json b/apps/web/package.json index 799afd6..81c9874 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -44,7 +44,7 @@ "json-schema-to-zod": "^2.5.0", "lucide-react": "0.462.0", "nanostores": "^0.11.3", - "next": "15.1.2", + "next": "15.1.3", "next-themes": "^0.4.4", "ramda": "^0.30.1", "react": "^18.3.1", diff --git a/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx b/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx index 4c0ebfe..1ee7057 100644 --- a/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx +++ b/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx @@ -1,16 +1,6 @@ "use client"; import * as React from "react"; import { useAppState } from "@/state/state"; -import { - textFieldVariants, - numberFieldVariants, - booleanFieldVariants, - dateFieldVariants, - fileFieldVariants, - selectionFieldVariants, - type FieldKind, - FormFramework, -} from "formbuilder-core"; import { Accordion, AccordionContent, @@ -20,18 +10,23 @@ import { import { cn } from "@/lib/utils"; import { ChevronDown, XIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { getVariantMap } from "@/utils/getVariantMap"; +import { + allFieldVariants, + type FormFramework, + type FrameworkFieldKinds, +} from "formbuilder-core"; +import { findKindByVariantAndFramework } from "@/utils/findKindByVariantAndFramework"; -export function AddFieldAccordion({ +export function AddFieldAccordion({ field, }: { field: { label: string; - kind: FieldKind; + kind: FrameworkFieldKinds[F]; }; }) { const state = useAppState(); - const variantMap = getVariantMap(state.currentForm.framework); + const variantMap = allFieldVariants[state.currentForm.framework]; const [isOpen, setIsOpen] = React.useState(false); return ( ( {!state.renderContent && - state.chosenField?.variant === variant.value ? ( + state.chosenField?.variant === variant.value && + state.chosenField?.kind === + findKindByVariantAndFramework( + state.chosenField?.variant, + state.currentForm.framework, + ) ? (
); diff --git a/apps/web/src/app/builder/_components/SortableGrid.tsx b/apps/web/src/app/builder/_components/SortableGrid.tsx index 11a6f95..a16113c 100644 --- a/apps/web/src/app/builder/_components/SortableGrid.tsx +++ b/apps/web/src/app/builder/_components/SortableGrid.tsx @@ -19,7 +19,13 @@ import { import { SortableItem } from "./SortableItem"; import { MouseSensor } from "./CustomSensor"; import { useAppState } from "@/state/state"; -import type { FieldVariant, FormFramework, FrameworkFieldKinds, FrameworkFieldVariants } from "formbuilder-core"; +import { + allFieldVariants, + allFieldVariantsByKind, + type FormFramework, + type FrameworkFieldKinds, + type FrameworkFieldVariants, +} from "formbuilder-core"; export const SortableGrid = () => { const [activeId, setActiveId] = useState(); @@ -107,6 +113,7 @@ export const SortableGrid = () => { id={formField.id} label={findLabel( formField.variant, + formField.kind )} /> ))} @@ -130,9 +137,11 @@ export const SortableGrid = () => { ); }; -function findLabel(variant: FrameworkFieldVariants>): string { - variant - const foundVariant = allFieldVariants.find( +function findLabel( + variant: FrameworkFieldVariants[F], + kind: FrameworkFieldKinds[F], +): string { + const foundVariant = allFieldVariantsByKind[kind].find( (field) => field.value === variant, ); return foundVariant?.label!; diff --git a/apps/web/src/app/builder/page.tsx b/apps/web/src/app/builder/page.tsx index 569080a..8d501d8 100644 --- a/apps/web/src/app/builder/page.tsx +++ b/apps/web/src/app/builder/page.tsx @@ -8,12 +8,14 @@ import { SortableGrid } from "./_components/SortableGrid"; import { AddField } from "./_components/AddField"; import { SettingsToggle } from "./_components/FormSettings/SettingsToggle"; import SettingsForm from "./_components/FormSettings"; -import { fieldKind } from "formbuilder-core"; +import { allFieldKinds } from "formbuilder-core"; +import { useAppState } from "@/state/state"; + export default function Builder() { const [showSettings, setShowSettings] = useState(false); - const [isOpen, setIsOpen] = useState(false); + const state = useAppState(); - const fields = fieldKind.map((v) => ({ + const fields = allFieldKinds[state.currentForm.framework].map((v) => ({ label: v.charAt(0).toUpperCase() + v.slice(1), kind: v, })); diff --git a/apps/web/src/core/Preview/Preview.tsx b/apps/web/src/core/Preview/Preview.tsx index a726381..ae5e51e 100644 --- a/apps/web/src/core/Preview/Preview.tsx +++ b/apps/web/src/core/Preview/Preview.tsx @@ -107,7 +107,6 @@ export function Preview() { } } // formSchema = z.object({}) - console.log("ffff", form.getValues(), formSchema.strip()); }, [selectedForm]); const form = useForm>({ @@ -118,7 +117,6 @@ export function Preview() { function onSubmit() { const values = form.getValues(); let result = "Submitted Values:\n"; - console.log("fff", form.getValues()); for (const key in values) { // biome-ignore lint/suspicious/noPrototypeBuiltins: diff --git a/apps/web/src/utils/findKindByVariantAndFramework.ts b/apps/web/src/utils/findKindByVariantAndFramework.ts new file mode 100644 index 0000000..1842caf --- /dev/null +++ b/apps/web/src/utils/findKindByVariantAndFramework.ts @@ -0,0 +1,18 @@ +import { allFieldVariantsWithKinds, type FormFramework, type FrameworkFieldVariants } from "formbuilder-core"; +import type { FrameworkFieldKinds } from "formbuilder-core"; + +export function findKindByVariantAndFramework( + variant: FrameworkFieldVariants[F], + framework: F +): FrameworkFieldKinds[F] | undefined { + const variantMap = allFieldVariantsWithKinds[framework]; // Adjust as necessary + + for (const kind in variantMap) { + const variants = variantMap[kind]; + // Check if any variant's value matches the provided variant's value + if (variants.variant.some(v => v.value === variant)) { + return kind as FrameworkFieldKinds[F]; + } + } + return undefined; // Return undefined if no match is found +} \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 655ee6920d2f2ccb4637daba3fd5b06ef98f1e2a..41f032fe42ca02e5dca0fe0287fbcea5ab598886 100755 GIT binary patch delta 1207 zcmcaHjq}Dd&Ix)7)mI*P^?y+0eP-W%qAs!Nre@#5uVqyQ=9ll!>ueMeKEKg#m*3t1(ecQe?aKX7J&hbAOkk<2&!$Yf2N_9f4GLhBaFA_ zMdP9Ttw)@LSNA2j|P`Wr^i4?g``y z?J(tGWME)qU}0!rU|@I%#4y0}QpT(A=lhVuHs3gANc3i%eDBA1AYe&Jdsx5z3VuT= z$%(cT4?8+qNcJAM-6F$vA@iuwg7n=_&oo!w*yWruV{>=(Q^!T&y@If^wbZw z?hP8b*UJ{?-7jg*yfu5$f-22rk&}9Etv^@2YmQfz+r?>7S!t|G|6I7|p%7u0%8>HG zJ#E^7%TU(|0G;(1<}@J9lDIu8$)-KK{k(SL#7Dl<8%s-;pS~R&GP8a6`xl-SdGWqK zrz|r3_DphxcGHflrs?goRZcI<{I>a}L(^@uPOjs{{gZ32O4Nt6*Vj*bYPoT@QhNdS z#MSlBX4pIL@m%xu@A4^k+r?H~_Xyx@b?m(=@Tote=l=fM*1sq7vx;ZmSlj46;Vs{} z=_kRiW8eU}?kTeCs^Z!LB6fHw+?aFZ@{~!ghW>4Ge@p@z(`!F}T_wGG&D|{3*n=yV zOq2|=WDa5tecbZv+#FNgbL)OcH-^x`-~IaU7BAsr+lx`iWbY}ch_<{5)KVJ z7NE$b?2b@z$+6!!an8jA$+qK4 zVpC)^%Gui6L%LNCKG6-oV;=V>$EIoW-D?sK+5EPbqaIHT@OqZ^>RD^~jjknX{7sJQ zW(90cH9zx-Gi6Q1pCi(hntTqdlU&ZVmv>&8U#Yp1`(#4#rVy^I)HgbtO|M^Iba0Li zKgiG)tzcdytl3`b_rq_a$j8H)7mp@&OO@SaeSeTIx5vM$SLpJ^zX55+bIf-|o|n^E zbnNrRRRuwwd`dfyZ+5+TWhbYox;XJBZ! z-Xyk>^Rf;L3!|Z_o}r%c^bJ4-ra%QJfC?ml3U2=XevIqw<5rjg28IVf8A+hb`uh48 zLY(hsz+|TXgPJYu&cGlARN!-SWu7sIZrZdSi$-~72rYFcUaZHbtXOf1Cfwi$sUn0+>j4t+Fo=Fi&%t?Vs6^T1h Z0V=!wk^)mJ)E)f47{#{tDlwhb000Ri>`edw delta 1207 zcmcaHjq}Dd&Ix)7`?(BRR^D!uvv}8idgrW11vPTVF1`)$y*9zfQ15%~{q&7~yZk1f zkT#fX;V%HDE1>iaDE$LUTLeJmq4W+Y{R2X8wg?Ph1R1b-M^J5J{hUqC;k)9@^|vgj zOiNCR>^GKIKcIa1d+a@f4W3i>q$%&5=OSM?C5vy1?_IxawOdntIqxYvoOyO%H^;3f z?{1ypWMp7qWME-vU|?W)2*fbJa_EP(QTX$$b|w3rHq(W!KRUaJW#$gH%}FK^p@ID~ zcI_)_VREl?*(15)^F1C{_D@@slAW*Ldw>2d|A!*Jp8X3aF-$&tMWX(TVyRul`YyYF z#)Z2d#bp0{pZKbGhDrNwr;CPBYrO*2DQ_k-80It}%~Df%o$a-Vn?aiSQpcd%yZ+C%E>Lfo^spiSTVzM#LEV6G z1DPHCQ#2)w4}ZV&>W_+T`ji*X{!aP3!O>E#d%}hIg#nXmuS(SOm-8|o&hRgrrl`7X z@y4T1e3mT!%UAMvU+4;z1+y3V`Mo?@d;ahK|4ZA$n!1 zw5&P{b{zu;$aPPVUDx}!W8;GzkD!#Z=Y>mkxXUlU*~k8N6(f(& z;iV0d+$Gu!m$;6(YuNgp6qEmY>0$h=W@hnA;aZb*uSwKzX%2mxImg%i$Aa_s%**t4 zfB)0V=k$V8uX6IK3Z^&zmfx+dtFE~HL&@&-c9(`%YBfJUYvhLC5sm)!x_RZrV@+Jp zuwwzb>^ZXQ@~@nIw|?>SfB)7bJrrS$>{#Tcz_M=f>99SW$7DTO%lLqK<6-+5WkyuV(-LDP}u=;r*UP(V|;Q [ + kind, { + kind, + variant: nextFieldVariants[kind], + } + ])), + react: Object.fromEntries(reactFieldKinds.map(kind => [ + kind, { + kind, + variant: reactFieldVariants[kind], + } + ])), + vue: Object.fromEntries(vueFieldKinds.map(kind => [ + kind, { + kind, + variant: vueFieldVariants[kind], + } + ])), + svelte: Object.fromEntries(svelteFieldKinds.map(kind => [ + kind, { + kind, + variant: svelteFieldVariants[kind], + } + ])), + solid: Object.fromEntries(solidFieldKinds.map(kind => [ + kind, { + kind, + variant: solidFieldVariants[kind], + } + ])), + astro: Object.fromEntries(astroFieldKinds.map(kind => [ + kind, { + kind, + variant: astroFieldVariants[kind], + } + ])), +} as const; + +export const allFieldVariantsByKind = { + ...nextFieldVariants, + ...reactFieldVariants, + ...vueFieldVariants, + ...svelteFieldVariants, + ...solidFieldVariants, + ...astroFieldVariants, +} as const; + +export const allFieldVariants = { + next: nextFieldVariants, + react: reactFieldVariants, + vue: vueFieldVariants, + svelte: svelteFieldVariants, + solid: solidFieldVariants, + astro: astroFieldVariants, +} as const; + +export const allFieldKinds = { + next: nextFieldKinds, + react: reactFieldKinds, + vue: vueFieldKinds, + svelte: svelteFieldKinds, + solid: solidFieldKinds, + astro: astroFieldKinds, +} as const; + export type FrameworkFieldKinds = { next: typeof nextFieldKinds[number]; react: typeof reactFieldKinds[number]; From 42b702e7f6de0039c76db4e8fe27238e26ef3875 Mon Sep 17 00:00:00 2001 From: KryptXBSA Date: Wed, 1 Jan 2025 22:22:01 +0300 Subject: [PATCH 10/22] per file framework variant --- .../AddField/AddFieldAccordion.tsx | 8 +- .../builder/_components/FormFieldContent.tsx | 2 + .../app/builder/_components/SortableGrid.tsx | 5 +- apps/web/src/mock/mockForm.ts | 16 +- .../utils/findKindByVariantAndFramework.ts | 18 -- packages/core/src/index.ts | 2 +- packages/core/src/types/fieldVariants.ts | 243 ------------------ .../src/types/fieldVariants/astroVariant.ts | 60 +++++ .../src/types/fieldVariants/fieldVariants.ts | 170 ++++++++++++ .../core/src/types/fieldVariants/index.ts | 1 + .../src/types/fieldVariants/nextVariant.ts | 60 +++++ .../src/types/fieldVariants/reactVariant.ts | 60 +++++ .../src/types/fieldVariants/solidVariant.ts | 60 +++++ .../src/types/fieldVariants/svelteVariant.ts | 60 +++++ .../src/types/fieldVariants/vueVariant.ts | 60 +++++ 15 files changed, 547 insertions(+), 278 deletions(-) delete mode 100644 apps/web/src/utils/findKindByVariantAndFramework.ts delete mode 100644 packages/core/src/types/fieldVariants.ts create mode 100644 packages/core/src/types/fieldVariants/astroVariant.ts create mode 100644 packages/core/src/types/fieldVariants/fieldVariants.ts create mode 100644 packages/core/src/types/fieldVariants/index.ts create mode 100644 packages/core/src/types/fieldVariants/nextVariant.ts create mode 100644 packages/core/src/types/fieldVariants/reactVariant.ts create mode 100644 packages/core/src/types/fieldVariants/solidVariant.ts create mode 100644 packages/core/src/types/fieldVariants/svelteVariant.ts create mode 100644 packages/core/src/types/fieldVariants/vueVariant.ts diff --git a/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx b/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx index 1ee7057..bb1068f 100644 --- a/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx +++ b/apps/web/src/app/builder/_components/AddField/AddFieldAccordion.tsx @@ -15,7 +15,6 @@ import { type FormFramework, type FrameworkFieldKinds, } from "formbuilder-core"; -import { findKindByVariantAndFramework } from "@/utils/findKindByVariantAndFramework"; export function AddFieldAccordion({ field, @@ -56,12 +55,7 @@ export function AddFieldAccordion({ {variantMap[field.kind]?.map((variant) => ( {!state.renderContent && - state.chosenField?.variant === variant.value && - state.chosenField?.kind === - findKindByVariantAndFramework( - state.chosenField?.variant, - state.currentForm.framework, - ) ? ( + state.chosenField?.variant === variant.value ? (