Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/.source/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ const create = browser<typeof Config, import("fumadocs-mdx/runtime/types").Inter
}
}>();
const browserCollections = {
docs: create.doc("docs", {"conditional-logic.mdx": () => import("../content/docs/conditional-logic.mdx?collection=docs"), "configuration.mdx": () => import("../content/docs/configuration.mdx?collection=docs"), "custom-rendering.mdx": () => import("../content/docs/custom-rendering.mdx?collection=docs"), "form-component.mdx": () => import("../content/docs/form-component.mdx?collection=docs"), "index.mdx": () => import("../content/docs/index.mdx?collection=docs"), "installation.mdx": () => import("../content/docs/installation.mdx?collection=docs"), "quick-start.mdx": () => import("../content/docs/quick-start.mdx?collection=docs"), "schema.mdx": () => import("../content/docs/schema.mdx?collection=docs"), "validation.mdx": () => import("../content/docs/validation.mdx?collection=docs"), "your-first-form.mdx": () => import("../content/docs/your-first-form.mdx?collection=docs"), "fields/types.mdx": () => import("../content/docs/fields/types.mdx?collection=docs"), "fields/data/checkbox.mdx": () => import("../content/docs/fields/data/checkbox.mdx?collection=docs"), "fields/data/date.mdx": () => import("../content/docs/fields/data/date.mdx?collection=docs"), "fields/data/number.mdx": () => import("../content/docs/fields/data/number.mdx?collection=docs"), "fields/data/password.mdx": () => import("../content/docs/fields/data/password.mdx?collection=docs"), "fields/data/radio.mdx": () => import("../content/docs/fields/data/radio.mdx?collection=docs"), "fields/data/select.mdx": () => import("../content/docs/fields/data/select.mdx?collection=docs"), "fields/data/switch.mdx": () => import("../content/docs/fields/data/switch.mdx?collection=docs"), "fields/data/tags.mdx": () => import("../content/docs/fields/data/tags.mdx?collection=docs"), "fields/data/text.mdx": () => import("../content/docs/fields/data/text.mdx?collection=docs"), "fields/data/textarea.mdx": () => import("../content/docs/fields/data/textarea.mdx?collection=docs"), "fields/data/upload.mdx": () => import("../content/docs/fields/data/upload.mdx?collection=docs"), "fields/layout/array.mdx": () => import("../content/docs/fields/layout/array.mdx?collection=docs"), "fields/layout/collapsible.mdx": () => import("../content/docs/fields/layout/collapsible.mdx?collection=docs"), "fields/layout/group.mdx": () => import("../content/docs/fields/layout/group.mdx?collection=docs"), "fields/layout/row.mdx": () => import("../content/docs/fields/layout/row.mdx?collection=docs"), "fields/layout/tabs.mdx": () => import("../content/docs/fields/layout/tabs.mdx?collection=docs"), }),
docs: create.doc("docs", {"conditional-logic.mdx": () => import("../content/docs/conditional-logic.mdx?collection=docs"), "configuration.mdx": () => import("../content/docs/configuration.mdx?collection=docs"), "custom-rendering.mdx": () => import("../content/docs/custom-rendering.mdx?collection=docs"), "form-component.mdx": () => import("../content/docs/form-component.mdx?collection=docs"), "index.mdx": () => import("../content/docs/index.mdx?collection=docs"), "installation.mdx": () => import("../content/docs/installation.mdx?collection=docs"), "quick-start.mdx": () => import("../content/docs/quick-start.mdx?collection=docs"), "schema.mdx": () => import("../content/docs/schema.mdx?collection=docs"), "validation.mdx": () => import("../content/docs/validation.mdx?collection=docs"), "your-first-form.mdx": () => import("../content/docs/your-first-form.mdx?collection=docs"), "fields/types.mdx": () => import("../content/docs/fields/types.mdx?collection=docs"), "fields/layout/array.mdx": () => import("../content/docs/fields/layout/array.mdx?collection=docs"), "fields/layout/collapsible.mdx": () => import("../content/docs/fields/layout/collapsible.mdx?collection=docs"), "fields/layout/group.mdx": () => import("../content/docs/fields/layout/group.mdx?collection=docs"), "fields/layout/row.mdx": () => import("../content/docs/fields/layout/row.mdx?collection=docs"), "fields/layout/tabs.mdx": () => import("../content/docs/fields/layout/tabs.mdx?collection=docs"), "fields/data/checkbox.mdx": () => import("../content/docs/fields/data/checkbox.mdx?collection=docs"), "fields/data/date.mdx": () => import("../content/docs/fields/data/date.mdx?collection=docs"), "fields/data/number.mdx": () => import("../content/docs/fields/data/number.mdx?collection=docs"), "fields/data/password.mdx": () => import("../content/docs/fields/data/password.mdx?collection=docs"), "fields/data/radio.mdx": () => import("../content/docs/fields/data/radio.mdx?collection=docs"), "fields/data/select.mdx": () => import("../content/docs/fields/data/select.mdx?collection=docs"), "fields/data/switch.mdx": () => import("../content/docs/fields/data/switch.mdx?collection=docs"), "fields/data/tags.mdx": () => import("../content/docs/fields/data/tags.mdx?collection=docs"), "fields/data/text.mdx": () => import("../content/docs/fields/data/text.mdx?collection=docs"), "fields/data/textarea.mdx": () => import("../content/docs/fields/data/textarea.mdx?collection=docs"), "fields/data/upload.mdx": () => import("../content/docs/fields/data/upload.mdx?collection=docs"), }),
};
export default browserCollections;
38 changes: 19 additions & 19 deletions apps/web/.source/server.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
// @ts-nocheck
import * as __fd_glob_30 from "../content/docs/fields/layout/tabs.mdx?collection=docs"
import * as __fd_glob_29 from "../content/docs/fields/layout/row.mdx?collection=docs"
import * as __fd_glob_28 from "../content/docs/fields/layout/group.mdx?collection=docs"
import * as __fd_glob_27 from "../content/docs/fields/layout/collapsible.mdx?collection=docs"
import * as __fd_glob_26 from "../content/docs/fields/layout/array.mdx?collection=docs"
import * as __fd_glob_25 from "../content/docs/fields/data/upload.mdx?collection=docs"
import * as __fd_glob_24 from "../content/docs/fields/data/textarea.mdx?collection=docs"
import * as __fd_glob_23 from "../content/docs/fields/data/text.mdx?collection=docs"
import * as __fd_glob_22 from "../content/docs/fields/data/tags.mdx?collection=docs"
import * as __fd_glob_21 from "../content/docs/fields/data/switch.mdx?collection=docs"
import * as __fd_glob_20 from "../content/docs/fields/data/select.mdx?collection=docs"
import * as __fd_glob_19 from "../content/docs/fields/data/radio.mdx?collection=docs"
import * as __fd_glob_18 from "../content/docs/fields/data/password.mdx?collection=docs"
import * as __fd_glob_17 from "../content/docs/fields/data/number.mdx?collection=docs"
import * as __fd_glob_16 from "../content/docs/fields/data/date.mdx?collection=docs"
import * as __fd_glob_15 from "../content/docs/fields/data/checkbox.mdx?collection=docs"
import * as __fd_glob_30 from "../content/docs/fields/data/upload.mdx?collection=docs"
import * as __fd_glob_29 from "../content/docs/fields/data/textarea.mdx?collection=docs"
import * as __fd_glob_28 from "../content/docs/fields/data/text.mdx?collection=docs"
import * as __fd_glob_27 from "../content/docs/fields/data/tags.mdx?collection=docs"
import * as __fd_glob_26 from "../content/docs/fields/data/switch.mdx?collection=docs"
import * as __fd_glob_25 from "../content/docs/fields/data/select.mdx?collection=docs"
import * as __fd_glob_24 from "../content/docs/fields/data/radio.mdx?collection=docs"
import * as __fd_glob_23 from "../content/docs/fields/data/password.mdx?collection=docs"
import * as __fd_glob_22 from "../content/docs/fields/data/number.mdx?collection=docs"
import * as __fd_glob_21 from "../content/docs/fields/data/date.mdx?collection=docs"
import * as __fd_glob_20 from "../content/docs/fields/data/checkbox.mdx?collection=docs"
import * as __fd_glob_19 from "../content/docs/fields/layout/tabs.mdx?collection=docs"
import * as __fd_glob_18 from "../content/docs/fields/layout/row.mdx?collection=docs"
import * as __fd_glob_17 from "../content/docs/fields/layout/group.mdx?collection=docs"
import * as __fd_glob_16 from "../content/docs/fields/layout/collapsible.mdx?collection=docs"
import * as __fd_glob_15 from "../content/docs/fields/layout/array.mdx?collection=docs"
import * as __fd_glob_14 from "../content/docs/fields/types.mdx?collection=docs"
import * as __fd_glob_13 from "../content/docs/your-first-form.mdx?collection=docs"
import * as __fd_glob_12 from "../content/docs/validation.mdx?collection=docs"
Expand All @@ -26,8 +26,8 @@ import * as __fd_glob_7 from "../content/docs/form-component.mdx?collection=docs
import * as __fd_glob_6 from "../content/docs/custom-rendering.mdx?collection=docs"
import * as __fd_glob_5 from "../content/docs/configuration.mdx?collection=docs"
import * as __fd_glob_4 from "../content/docs/conditional-logic.mdx?collection=docs"
import { default as __fd_glob_3 } from "../content/docs/fields/data/meta.json?collection=docs"
import { default as __fd_glob_2 } from "../content/docs/fields/layout/meta.json?collection=docs"
import { default as __fd_glob_3 } from "../content/docs/fields/layout/meta.json?collection=docs"
import { default as __fd_glob_2 } from "../content/docs/fields/data/meta.json?collection=docs"
import { default as __fd_glob_1 } from "../content/docs/fields/meta.json?collection=docs"
import { default as __fd_glob_0 } from "../content/docs/meta.json?collection=docs"
import { server } from 'fumadocs-mdx/runtime/server';
Expand All @@ -38,4 +38,4 @@ const create = server<typeof Config, import("fumadocs-mdx/runtime/types").Intern
}
}>({"doc":{"passthroughs":["extractedReferences"]}});

export const docs = await create.docs("docs", "content/docs", {"meta.json": __fd_glob_0, "fields/meta.json": __fd_glob_1, "fields/layout/meta.json": __fd_glob_2, "fields/data/meta.json": __fd_glob_3, }, {"conditional-logic.mdx": __fd_glob_4, "configuration.mdx": __fd_glob_5, "custom-rendering.mdx": __fd_glob_6, "form-component.mdx": __fd_glob_7, "index.mdx": __fd_glob_8, "installation.mdx": __fd_glob_9, "quick-start.mdx": __fd_glob_10, "schema.mdx": __fd_glob_11, "validation.mdx": __fd_glob_12, "your-first-form.mdx": __fd_glob_13, "fields/types.mdx": __fd_glob_14, "fields/data/checkbox.mdx": __fd_glob_15, "fields/data/date.mdx": __fd_glob_16, "fields/data/number.mdx": __fd_glob_17, "fields/data/password.mdx": __fd_glob_18, "fields/data/radio.mdx": __fd_glob_19, "fields/data/select.mdx": __fd_glob_20, "fields/data/switch.mdx": __fd_glob_21, "fields/data/tags.mdx": __fd_glob_22, "fields/data/text.mdx": __fd_glob_23, "fields/data/textarea.mdx": __fd_glob_24, "fields/data/upload.mdx": __fd_glob_25, "fields/layout/array.mdx": __fd_glob_26, "fields/layout/collapsible.mdx": __fd_glob_27, "fields/layout/group.mdx": __fd_glob_28, "fields/layout/row.mdx": __fd_glob_29, "fields/layout/tabs.mdx": __fd_glob_30, });
export const docs = await create.docs("docs", "content/docs", {"meta.json": __fd_glob_0, "fields/meta.json": __fd_glob_1, "fields/data/meta.json": __fd_glob_2, "fields/layout/meta.json": __fd_glob_3, }, {"conditional-logic.mdx": __fd_glob_4, "configuration.mdx": __fd_glob_5, "custom-rendering.mdx": __fd_glob_6, "form-component.mdx": __fd_glob_7, "index.mdx": __fd_glob_8, "installation.mdx": __fd_glob_9, "quick-start.mdx": __fd_glob_10, "schema.mdx": __fd_glob_11, "validation.mdx": __fd_glob_12, "your-first-form.mdx": __fd_glob_13, "fields/types.mdx": __fd_glob_14, "fields/layout/array.mdx": __fd_glob_15, "fields/layout/collapsible.mdx": __fd_glob_16, "fields/layout/group.mdx": __fd_glob_17, "fields/layout/row.mdx": __fd_glob_18, "fields/layout/tabs.mdx": __fd_glob_19, "fields/data/checkbox.mdx": __fd_glob_20, "fields/data/date.mdx": __fd_glob_21, "fields/data/number.mdx": __fd_glob_22, "fields/data/password.mdx": __fd_glob_23, "fields/data/radio.mdx": __fd_glob_24, "fields/data/select.mdx": __fd_glob_25, "fields/data/switch.mdx": __fd_glob_26, "fields/data/tags.mdx": __fd_glob_27, "fields/data/text.mdx": __fd_glob_28, "fields/data/textarea.mdx": __fd_glob_29, "fields/data/upload.mdx": __fd_glob_30, });
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client";

import type {
Field,
FieldComponentProps,
SelectField as SelectFieldType,
} from "@buildnbuzz/buzzform";
import { getNestedValue } from "@buildnbuzz/buzzform";
import { SelectField } from "@/components/buzzform/fields/select";

function getSiblingData(
formValues: Record<string, unknown>,
path: string,
): Record<string, unknown> {
const pathParts = path.split(".");
const parentParts = pathParts.slice(0, -1);
if (parentParts.length === 0) return formValues;
const parent = getNestedValue(formValues, parentParts.join("."));
return (parent as Record<string, unknown>) ?? formValues;
}

export function DefaultValueSelect(
props: FieldComponentProps<
NonNullable<SelectFieldType["defaultValue"]>,
Field
>,
) {
const { field, path, form, autoFocus, id, disabled, readOnly, error } = props;

if (field.type !== "select") {
return null;
}

const hasMany = !!form.watch("hasMany");
const options = form.watch("options");

const selectField: SelectFieldType = {
...(field as SelectFieldType),
options: Array.isArray(options) ? options : [],
hasMany,
};

const formValues = form.getValues() as Record<string, unknown>;
const siblingData = getSiblingData(formValues, path);

const label =
field.label === false ? null : (field.label ?? field.name ?? null);

return (
<SelectField
field={selectField}
path={path}
form={form}
autoFocus={autoFocus}
formValues={formValues}
siblingData={siblingData}
fieldId={id}
label={label}
isDisabled={disabled}
isReadOnly={readOnly}
error={error}
/>
);
}
19 changes: 16 additions & 3 deletions apps/web/app/(builder)/components/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,12 +282,13 @@ function DragOverlayItem({ id }: { id: string }) {
return (
<div
className={cn(
"bg-card/95 backdrop-blur-md border-2 shadow-2xl rounded-xl p-3 min-w-52",
"bg-card/95 backdrop-blur-md border-2 shadow-2xl rounded-xl p-3 min-w-80",
"cursor-grabbing animate-in fade-in-0 zoom-in-95 duration-150",
isFromSidebar
? "border-primary/50 shadow-primary/10"
: "border-border shadow-black/10",
)}
style={{ minWidth: 320 }}
>
<div className="flex items-center gap-3">
{/* Icon container */}
Expand All @@ -307,13 +308,25 @@ function DragOverlayItem({ id }: { id: string }) {
<div className="text-sm font-semibold text-foreground truncate">
{isFromSidebar ? `New ${label}` : (fieldName ?? label)}
</div>
<div className="text-xs text-muted-foreground flex items-center gap-1.5">
<div className="text-xs text-muted-foreground flex items-center gap-1.5 whitespace-nowrap">
<HugeiconsIcon
icon={isFromSidebar ? Add01Icon : Move01Icon}
size={10}
strokeWidth={2}
/>
<span>{isFromSidebar ? "Adding new field" : "Moving field"}</span>
<span>
{isFromSidebar
? (
<>
Adding new field, press
<kbd className="ml-1 inline-flex items-center rounded border border-border bg-muted px-1.5 py-0.5 text-xs font-medium text-foreground/80">
Esc
</kbd>
<span className="ml-1">to cancel</span>
</>
)
: "Moving field"}
</span>
</div>
</div>
</div>
Expand Down
39 changes: 39 additions & 0 deletions apps/web/app/(builder)/lib/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,45 @@ export function sanitizeFieldConstraints<T extends Record<string, unknown>>(fiel
return result;
}

/**
* Ensure defaultValue stays in sync with options for option-based fields.
* Clears invalid defaults when options are removed/changed.
*/
export function sanitizeFieldDefaults<T extends Record<string, unknown>>(field: T): T {
const options = (field as Record<string, unknown>).options;
if (!Array.isArray(options)) return field;

const optionValues = options
.map((opt) => (typeof opt === "string" ? opt : (opt as { value?: unknown }).value))
.filter((val) => val !== undefined && val !== null && val !== "");

if (optionValues.length === 0) {
if ("defaultValue" in field) {
(field as Record<string, unknown>).defaultValue = undefined;
}
return field;
}

const defaultValue = (field as Record<string, unknown>).defaultValue;
if (defaultValue === undefined || defaultValue === null || defaultValue === "") return field;

if (Array.isArray(defaultValue)) {
const filtered = defaultValue.filter((val) =>
optionValues.some((opt) => opt === val),
);
(field as Record<string, unknown>).defaultValue =
filtered.length > 0 ? filtered : undefined;
return field;
}

const isValid = optionValues.some((opt) => opt === defaultValue);
if (!isValid) {
(field as Record<string, unknown>).defaultValue = undefined;
}

return field;
}

/**
* Flattens field props to form values for the properties editor.
* Excludes structural props like children, fields, and type.
Expand Down
11 changes: 11 additions & 0 deletions apps/web/app/(builder)/lib/properties/radio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ export const radioFieldProperties: Field[] = [
},
],
},
{
type: "select",
name: "defaultValue",
label: "Default Value",
description: "Preselect an option (optional)",
options: async (context) =>
Array.isArray(context?.data?.options)
? context.data.options
: [],
dependencies: ["options"],
},
{
type: "switch",
name: "hidden",
Expand Down
25 changes: 25 additions & 0 deletions apps/web/app/(builder)/lib/properties/select.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
import { z } from "zod";
import type { Field } from "@buildnbuzz/buzzform";
import { DefaultValueSelect } from "../../components/properties/default-value-select";

const selectDefaultValueSchema = z
.union([
z.string(),
z.number(),
z.boolean(),
z.array(z.union([z.string(), z.number(), z.boolean()])),
])
.optional();

export const selectFieldProperties: Field[] = [
{
Expand Down Expand Up @@ -75,6 +86,20 @@ export const selectFieldProperties: Field[] = [
},
],
},
{
type: "select",
name: "defaultValue",
label: "Default Value",
description: "Preselect a value",
component: DefaultValueSelect,
// @ts-expect-error: schema allows undefined but SelectField["defaultValue"] doesn't (will be fixed in package)
schema: selectDefaultValueSchema,
options: async (context) =>
Array.isArray(context?.data?.options)
? context.data.options
: [],
dependencies: ["options"],
},
{
type: "switch",
name: "hasMany",
Expand Down
7 changes: 5 additions & 2 deletions apps/web/app/(builder)/lib/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export const builderFieldRegistry: BuilderFieldRegistry = {
defaultProps: {
type: 'radio',
label: 'Radio',
defaultValue: 'option1',
options: [
{ label: 'Option 1', value: 'option1' },
{ label: 'Option 2', value: 'option2' },
Expand All @@ -131,8 +132,10 @@ export const builderFieldRegistry: BuilderFieldRegistry = {
},
switch: {
kind: 'data',
sidebar: { label: 'Switch', icon: ToggleOnIcon, category: 'selection' },
defaultProps: { type: 'switch', label: 'Switch' },
sidebar: { label: 'Switch', icon: ToggleOnIcon, category: 'selection', },
defaultProps: {
type: 'switch', label: 'Switch', defaultValue: false,
},
properties: switchFieldProperties,
},

Expand Down
Loading
Loading