betterstart-cli 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/assets/adapters/next/plugins/blog/schemas/menus.json +1 -0
  2. package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +132 -99
  3. package/dist/assets/adapters/next/plugins/portfolio/plugin.ts +17 -0
  4. package/dist/assets/adapters/next/plugins/portfolio/schemas/portfolio.json +128 -0
  5. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +2 -2
  6. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/snippets-tab.tsx +1 -0
  7. package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +1 -1
  8. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-dialog.tsx +1 -1
  9. package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-drawer.tsx +1 -1
  10. package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-item.tsx +17 -7
  11. package/dist/assets/adapters/next/templates/init/data/navigation.ts +2 -1
  12. package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-source-mode.tsx +3 -3
  13. package/dist/assets/adapters/next/templates/init/hooks/use-media.ts +2 -1
  14. package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +3 -3
  15. package/dist/assets/shared-assets/react-admin/custom/content-editor/media-gallery-block.tsx +2 -2
  16. package/dist/assets/shared-assets/react-admin/custom/content-editor/table-add-controls.tsx +2 -2
  17. package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +219 -40
  18. package/dist/assets/shared-assets/react-admin/custom/media-field-empty-state.tsx +99 -0
  19. package/dist/assets/shared-assets/react-admin/custom/media-field.tsx +181 -0
  20. package/dist/assets/shared-assets/react-admin/custom/sortable-gallery-item.tsx +38 -38
  21. package/dist/assets/shared-assets/react-admin/schema.json +64 -2
  22. package/dist/assets/shared-assets/react-admin/ui/pagination.tsx +1 -1
  23. package/dist/cli.js +470 -143
  24. package/dist/cli.js.map +1 -1
  25. package/package.json +1 -1
  26. package/dist/assets/shared-assets/react-admin/custom/gallery-thumbnail.tsx +0 -39
  27. package/dist/assets/shared-assets/react-admin/custom/media-gallery-field.tsx +0 -182
package/dist/cli.js CHANGED
@@ -125,7 +125,7 @@ function createUninstallCommand(runtime) {
125
125
  return new Command("uninstall").description("Remove all Admin files and undo modifications made by betterstart init").option("-f, --force", "Skip all confirmation prompts", false).option("--cwd <path>", "Project root path").action((options) => runtime.runUninstall(options));
126
126
  }
127
127
  function createUpdateCommand(runtime) {
128
- return new Command("update").alias("update-component").description("Update Admin components from the latest CLI templates").argument("[components...]", "Component names to update (e.g., media-gallery-field button)").option("--list", "List all available components").option("--all", "Update all components").option("--shadcn-preset <preset>", "Apply a shadcn preset to Admin UI components").option("--only <parts>", "Apply only supported shadcn preset parts: theme").option("--cwd <path>", "Project root path").action(
128
+ return new Command("update").alias("update-component").description("Update Admin components from the latest CLI templates").argument("[components...]", "Component names to update (e.g., media-field button)").option("--list", "List all available components").option("--all", "Update all components").option("--shadcn-preset <preset>", "Apply a shadcn preset to Admin UI components").option("--only <parts>", "Apply only supported shadcn preset parts: theme").option("--cwd <path>", "Project root path").action(
129
129
  (components, options) => runtime.runUpdate(components, options)
130
130
  );
131
131
  }
@@ -867,9 +867,27 @@ var blogPlugin = {
867
867
  configure: "none"
868
868
  };
869
869
 
870
+ // adapters/next/plugins/portfolio/plugin.ts
871
+ var portfolioPlugin = {
872
+ id: "portfolio",
873
+ title: "Portfolio",
874
+ description: "Portfolio content module",
875
+ kind: "content",
876
+ version: "1.0.0",
877
+ dependencies: [],
878
+ conflicts: [],
879
+ schemaFiles: ["portfolio.json"],
880
+ files: [],
881
+ packageDependencies: [],
882
+ devDependencies: [],
883
+ envSections: [],
884
+ configure: "none"
885
+ };
886
+
870
887
  // adapters/next/plugin-registry.ts
871
888
  var pluginRegistry = {
872
- blog: blogPlugin
889
+ blog: blogPlugin,
890
+ portfolio: portfolioPlugin
873
891
  };
874
892
  function listPluginDefinitions() {
875
893
  return Object.values(pluginRegistry);
@@ -1724,6 +1742,7 @@ import path11 from "path";
1724
1742
  // core-engine/schema/constants.ts
1725
1743
  var FIELD_TYPES = {
1726
1744
  STRING: "string",
1745
+ EMAIL: "email",
1727
1746
  NUMBER: "number",
1728
1747
  BOOLEAN: "boolean",
1729
1748
  TEXT: "text",
@@ -1796,6 +1815,18 @@ var FORM_FIELD_TYPES = {
1796
1815
  LIST: "list",
1797
1816
  DYNAMIC_FIELDS: "dynamicFields"
1798
1817
  };
1818
+ var AUTO_FIELDS = {
1819
+ ID: "id",
1820
+ CREATED_AT: "createdAt",
1821
+ UPDATED_AT: "updatedAt",
1822
+ PUBLISHED: "published",
1823
+ SLUG: "slug"
1824
+ };
1825
+ var FORM_SUBMISSION_FIELDS = {
1826
+ SUBMITTED_AT: "submittedAt",
1827
+ IP_ADDRESS: "ipAddress",
1828
+ USER_AGENT: "userAgent"
1829
+ };
1799
1830
  function isFieldType(value) {
1800
1831
  return Object.values(FIELD_TYPES).includes(value);
1801
1832
  }
@@ -1956,6 +1987,11 @@ function loadSchema(schemasDir, name) {
1956
1987
  const parsed = parseJson(content, filePath);
1957
1988
  const obj = parsed;
1958
1989
  const type = obj.type;
1990
+ if (type === void 0) {
1991
+ throw new Error(
1992
+ `Schema "${name}" is missing required top-level "type". Expected "entity", "single", or "form".`
1993
+ );
1994
+ }
1959
1995
  if (type === "form") {
1960
1996
  return { type: "form", schema: parsed, filePath };
1961
1997
  }
@@ -1996,6 +2032,14 @@ var ENTITY_SLUG_FIELD_NAME = "slug";
1996
2032
  var ENTITY_SLUG_FIELD_TYPES = /* @__PURE__ */ new Set(["string", "varchar", "text"]);
1997
2033
  var GENERATED_SCHEMA_TITLE_FIELD_TYPES = /* @__PURE__ */ new Set(["string", "varchar", "text"]);
1998
2034
  var FORM_SCHEMA_TITLE_FIELD_TYPES = /* @__PURE__ */ new Set(["text"]);
2035
+ var FORM_SUBMISSION_COLUMN_NAMES = [
2036
+ AUTO_FIELDS.ID,
2037
+ FORM_SUBMISSION_FIELDS.SUBMITTED_AT,
2038
+ FORM_SUBMISSION_FIELDS.IP_ADDRESS,
2039
+ FORM_SUBMISSION_FIELDS.USER_AGENT,
2040
+ AUTO_FIELDS.CREATED_AT,
2041
+ AUTO_FIELDS.UPDATED_AT
2042
+ ];
1999
2043
  function isEntitySlugField(field) {
2000
2044
  return field.name === ENTITY_SLUG_FIELD_NAME;
2001
2045
  }
@@ -2039,52 +2083,84 @@ function createGeneratedSlugField(fields) {
2039
2083
  ...sourceField ? { defaultValueFrom: sourceField } : {}
2040
2084
  };
2041
2085
  }
2042
- function normalizeEntitySlugFields(fields, sourceField) {
2043
- let hasSlugField = false;
2044
- const normalizedFields = fields.map((field) => {
2086
+ function normalizeEntitySlugField(field, sourceField) {
2087
+ return {
2088
+ ...field,
2089
+ required: true,
2090
+ ...sourceField && !field.hidden ? { defaultValueFrom: sourceField } : {}
2091
+ };
2092
+ }
2093
+ function createGeneratedSlugFieldForTitle(fields, titleField) {
2094
+ const slugField = createGeneratedSlugField(fields);
2095
+ return titleField.slot ? { ...slugField, slot: titleField.slot } : slugField;
2096
+ }
2097
+ function orderEntityIdentityFields(fields) {
2098
+ const titleIndex = fields.findIndex(isGeneratedSchemaTitleField);
2099
+ const slugIndex = fields.findIndex(isEntitySlugField);
2100
+ if (titleIndex < 0 || slugIndex < 0 || titleIndex === 0 && slugIndex === 1) return fields;
2101
+ const titleField = fields[titleIndex];
2102
+ const slugField = fields[slugIndex];
2103
+ return [
2104
+ titleField,
2105
+ slugField,
2106
+ ...fields.filter((_, index) => index !== titleIndex && index !== slugIndex)
2107
+ ];
2108
+ }
2109
+ function normalizeEntityIdentityFields(fields, rootFields, state) {
2110
+ const normalizedFields = [];
2111
+ for (const field of fields) {
2045
2112
  if (isEntitySlugField(field)) {
2046
- hasSlugField = true;
2047
- return {
2048
- ...field,
2049
- required: true,
2050
- ...sourceField && !field.hidden ? { defaultValueFrom: sourceField } : {}
2051
- };
2113
+ if (state.keptAuthoredSlugField) continue;
2114
+ state.keptAuthoredSlugField = true;
2115
+ normalizedFields.push(normalizeEntitySlugField(field, state.sourceField));
2116
+ continue;
2052
2117
  }
2053
2118
  if ((field.type === "group" || field.type === "section") && field.fields) {
2054
- const nested = normalizeEntitySlugFields(field.fields, sourceField);
2055
- hasSlugField ||= nested.hasSlugField;
2056
- return { ...field, fields: nested.fields };
2119
+ normalizedFields.push({
2120
+ ...field,
2121
+ fields: normalizeEntityIdentityFields(field.fields, rootFields, state)
2122
+ });
2123
+ continue;
2057
2124
  }
2058
2125
  if (field.type === "tabs" && field.tabs) {
2059
- const tabs = field.tabs.map((tab) => {
2060
- const nested = normalizeEntitySlugFields(tab.fields ?? [], sourceField);
2061
- hasSlugField ||= nested.hasSlugField;
2062
- return { ...tab, fields: nested.fields };
2126
+ normalizedFields.push({
2127
+ ...field,
2128
+ tabs: field.tabs.map((tab) => ({
2129
+ ...tab,
2130
+ fields: normalizeEntityIdentityFields(tab.fields ?? [], rootFields, state)
2131
+ }))
2063
2132
  });
2064
- return { ...field, tabs };
2133
+ continue;
2065
2134
  }
2066
- return field;
2067
- });
2068
- return { fields: normalizedFields, hasSlugField };
2135
+ normalizedFields.push(field);
2136
+ if (!state.hasAuthoredSlugField && !state.generatedSlugInserted && isGeneratedSchemaTitleField(field)) {
2137
+ normalizedFields.push(createGeneratedSlugFieldForTitle(rootFields, field));
2138
+ state.generatedSlugInserted = true;
2139
+ }
2140
+ }
2141
+ return orderEntityIdentityFields(normalizedFields);
2069
2142
  }
2070
2143
  function ensureEntitySlugField(schema) {
2071
2144
  const sourceField = hasPersistedTitleField(schema.fields) ? "title" : void 0;
2072
- const normalized = normalizeEntitySlugFields(schema.fields, sourceField);
2073
- if (normalized.hasSlugField) {
2145
+ const hasAuthoredSlugField = flattenFields(schema.fields).some(isEntitySlugField);
2146
+ const state = {
2147
+ sourceField,
2148
+ hasAuthoredSlugField,
2149
+ keptAuthoredSlugField: false,
2150
+ generatedSlugInserted: false
2151
+ };
2152
+ const normalizedFields = normalizeEntityIdentityFields(schema.fields, schema.fields, state);
2153
+ if (hasAuthoredSlugField || state.generatedSlugInserted) {
2074
2154
  return {
2075
2155
  ...schema,
2076
- fields: normalized.fields
2156
+ fields: normalizedFields
2077
2157
  };
2078
2158
  }
2079
- const slugField = createGeneratedSlugField(schema.fields);
2080
- const titleIndex = schema.fields.findIndex(
2081
- (field) => field.name === GENERATED_SCHEMA_TITLE_FIELD_NAME && isStringLikeSlugField(field)
2082
- );
2083
- const insertIndex = titleIndex >= 0 ? titleIndex + 1 : Math.min(schema.fields.length, 1);
2159
+ const insertIndex = Math.min(normalizedFields.length, 1);
2084
2160
  const fields = [
2085
- ...schema.fields.slice(0, insertIndex),
2086
- slugField,
2087
- ...schema.fields.slice(insertIndex)
2161
+ ...normalizedFields.slice(0, insertIndex),
2162
+ createGeneratedSlugField(schema.fields),
2163
+ ...normalizedFields.slice(insertIndex)
2088
2164
  ];
2089
2165
  return {
2090
2166
  ...schema,
@@ -2198,6 +2274,23 @@ function collectInvalidFormColumnTypeErrors(schema, errors) {
2198
2274
  }
2199
2275
  }
2200
2276
  }
2277
+ function collectInvalidFormColumnReferenceErrors(schema, errors) {
2278
+ const fieldNames = new Set(
2279
+ getAllFormSchemaFields(schema).map((field) => field.name).filter((name) => typeof name === "string" && name.length > 0)
2280
+ );
2281
+ for (const columnName of FORM_SUBMISSION_COLUMN_NAMES) {
2282
+ fieldNames.add(columnName);
2283
+ }
2284
+ for (const column of schema.columns || []) {
2285
+ if (!column.accessorKey || !column.header) {
2286
+ errors.push(`Form column is missing required properties: ${JSON.stringify(column)}`);
2287
+ } else if (!fieldNames.has(column.accessorKey)) {
2288
+ errors.push(
2289
+ `Form column accessorKey "${column.accessorKey}" does not match any field or generated submission property. Available: ${Array.from(fieldNames).join(", ")}`
2290
+ );
2291
+ }
2292
+ }
2293
+ }
2201
2294
  function validateEntitySchema(schema) {
2202
2295
  const errors = [];
2203
2296
  collectSchemaSlotLayoutErrors(schema, errors);
@@ -2217,6 +2310,9 @@ function validateEntitySchema(schema) {
2217
2310
  if (!Array.isArray(normalizedSchema.fields) || normalizedSchema.fields.length === 0) {
2218
2311
  errors.push('Schema must have at least one field via "fields" or "slot"');
2219
2312
  }
2313
+ if (normalizedSchema.type !== "entity") {
2314
+ errors.push('Entity schema must have `type: "entity"`');
2315
+ }
2220
2316
  if (!Array.isArray(normalizedSchema.columns) || normalizedSchema.columns.length === 0) {
2221
2317
  errors.push("Schema must have at least one column");
2222
2318
  }
@@ -2279,6 +2375,9 @@ function validateSingleSchema(schema) {
2279
2375
  if (!Array.isArray(normalizedSchema.fields) || normalizedSchema.fields.length === 0) {
2280
2376
  errors.push('Schema must have at least one field via "fields" or "slot"');
2281
2377
  }
2378
+ if (normalizedSchema.type !== "single") {
2379
+ errors.push('Single schema must have `type: "single"`');
2380
+ }
2282
2381
  for (const field of normalizedSchema.fields || []) {
2283
2382
  if (field.type === "separator") continue;
2284
2383
  if (!field.name || !field.type) {
@@ -2338,6 +2437,7 @@ function validateFormSchema(schema) {
2338
2437
  }
2339
2438
  collectMissingFormSchemaTitleErrors(schema, errors);
2340
2439
  collectInvalidFormColumnTypeErrors(schema, errors);
2440
+ collectInvalidFormColumnReferenceErrors(schema, errors);
2341
2441
  return errors;
2342
2442
  }
2343
2443
  function validateLoadedSchema(loaded) {
@@ -3715,6 +3815,7 @@ function toDrizzleType(field, requiredImports) {
3715
3815
  requiredImports.add("uuid");
3716
3816
  return "uuid().defaultRandom()";
3717
3817
  case "string":
3818
+ case "email":
3718
3819
  case "varchar":
3719
3820
  requiredImports.add("varchar");
3720
3821
  return field.length ? `varchar({ length: ${field.length} })` : `varchar({ length: ${DRIZZLE_DEFAULTS.VARCHAR_LENGTH} })`;
@@ -3871,6 +3972,7 @@ function toTypeScriptType(field, mode = "output") {
3871
3972
  case "boolean":
3872
3973
  return "boolean";
3873
3974
  case "string":
3975
+ case "email":
3874
3976
  case "varchar":
3875
3977
  case "text":
3876
3978
  case "markdown":
@@ -4045,7 +4147,7 @@ function generateFieldMapping(field, source = "input") {
4045
4147
  if ((field.type === "date" || field.type === "timestamp" || field.type === "time") && !field.required) {
4046
4148
  return `${source}.${field.name} && ${source}.${field.name} !== '' ? ${source}.${field.name} : null`;
4047
4149
  }
4048
- if (!field.required && ["string", "varchar", "text", "select"].includes(field.type)) {
4150
+ if (!field.required && ["string", "email", "varchar", "text", "select"].includes(field.type)) {
4049
4151
  return `${source}.${field.name} && ${source}.${field.name} !== '' ? ${source}.${field.name} : null`;
4050
4152
  }
4051
4153
  return `${source}.${field.name}`;
@@ -4311,6 +4413,7 @@ ${blocks.join("\n\n")}
4311
4413
  // adapters/next/generators/entity-filters.ts
4312
4414
  var FILTERABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
4313
4415
  "string",
4416
+ "email",
4314
4417
  "varchar",
4315
4418
  "text",
4316
4419
  "boolean",
@@ -4552,6 +4655,8 @@ function sampleSchemaFieldValue(field) {
4552
4655
  switch (field.type) {
4553
4656
  case "boolean":
4554
4657
  return true;
4658
+ case "email":
4659
+ return "developer@example.com";
4555
4660
  case "number":
4556
4661
  case "decimal":
4557
4662
  return 1;
@@ -5323,21 +5428,19 @@ function buildFormDevModeSnippets(schema, actionImportPath, adminImportAlias) {
5323
5428
  const fields = getAllFormSchemaFields(schema);
5324
5429
  const submissionInput = createFormInputObject(fields);
5325
5430
  const actionModulePath = resolveActionModulePath(actionImportPath, adminImportAlias);
5431
+ const formComponentModulePath = `${adminImportAlias ?? DEFAULT_ADMIN_IMPORT_ALIAS}/components/forms/${toKebabCase(schema.name)}-form`;
5326
5432
  const submissionData = createFormSubmissionObject(fields);
5327
5433
  const submitResponse = { success: true, submission: submissionData };
5328
5434
  const submissionsResponse = { submissions: [submissionData], total: 1 };
5329
5435
  const deleteResponse = { success: true };
5330
5436
  const formLabel2 = schema.label;
5331
- return [
5332
- {
5333
- id: "submit",
5334
- label: `Submit ${formLabel2}`,
5335
- kind: "mutation",
5336
- icon: "send",
5337
- language: "tsx",
5338
- payloadExample: createPayloadExample(submissionInput),
5339
- responseExample: createResponseExample(submitResponse),
5340
- code: `'use client'
5437
+ const formComponentCode = `import { ${pascal}Form } from '${formComponentModulePath}'
5438
+
5439
+ export default function ${pascal}Page() {
5440
+ return <${pascal}Form />
5441
+ }
5442
+ `;
5443
+ const submitActionCode = `'use client'
5341
5444
 
5342
5445
  import * as React from 'react'
5343
5446
  import {
@@ -5364,7 +5467,25 @@ export function ${pascal}SubmitButton() {
5364
5467
  </button>
5365
5468
  )
5366
5469
  }
5367
- `
5470
+ `;
5471
+ return [
5472
+ {
5473
+ id: "jsx",
5474
+ label: `${formLabel2} Component`,
5475
+ kind: "jsx",
5476
+ icon: "file-code",
5477
+ language: "tsx",
5478
+ code: formComponentCode
5479
+ },
5480
+ {
5481
+ id: "submit",
5482
+ label: `Submit ${formLabel2}`,
5483
+ kind: "mutation",
5484
+ icon: "send",
5485
+ language: "tsx",
5486
+ payloadExample: createPayloadExample(submissionInput),
5487
+ responseExample: createResponseExample(submitResponse),
5488
+ code: submitActionCode
5368
5489
  },
5369
5490
  {
5370
5491
  id: "submissions",
@@ -5458,7 +5579,7 @@ function generateColumnDef(col) {
5458
5579
  case "email":
5459
5580
  cellDef = `cell: ({ row }) => {
5460
5581
  const email = row.getValue('${col.accessorKey}') as string
5461
- return email ? <a href={\`mailto:\${email}\`} className="text-primary hover:underline">{email}</a> : '-'
5582
+ return <div className="text-sm font-medium">{email || '-'}</div>
5462
5583
  }`;
5463
5584
  break;
5464
5585
  case "date":
@@ -7267,7 +7388,7 @@ export { CACHE_TAG, CACHE_TAG_BY_ID } from './types'
7267
7388
  // adapters/next/generators/form-pipeline/form-component-shared.ts
7268
7389
  import fs16 from "fs";
7269
7390
  import path20 from "path";
7270
- var ADMIN_CUSTOM_COMPONENTS = /* @__PURE__ */ new Set(["media-gallery-field"]);
7391
+ var ADMIN_CUSTOM_COMPONENTS = /* @__PURE__ */ new Set(["media-field"]);
7271
7392
  function resolveUiImport(cwd, componentName) {
7272
7393
  const componentDirectory = ADMIN_CUSTOM_COMPONENTS.has(componentName) ? "components/custom" : "components/ui";
7273
7394
  const locations = [componentDirectory, `src/${componentDirectory}`];
@@ -7348,6 +7469,26 @@ ${decls}
7348
7469
  function getListFields(fields) {
7349
7470
  return fields.filter((f) => f.name && f.type === "list" && f.fields && f.fields.length > 0);
7350
7471
  }
7472
+ function hasFormDescriptionFields(fields) {
7473
+ return fields.some((field) => {
7474
+ if (field.hint) return true;
7475
+ if ((field.type === "group" || field.type === "list") && field.fields) {
7476
+ return hasFormDescriptionFields(field.fields);
7477
+ }
7478
+ return false;
7479
+ });
7480
+ }
7481
+ function hasSelectFields(fields) {
7482
+ return fields.some((field) => {
7483
+ if (field.type === "select" && field.options && field.options.length > 0) {
7484
+ return true;
7485
+ }
7486
+ if ((field.type === "group" || field.type === "list") && field.fields) {
7487
+ return hasSelectFields(field.fields);
7488
+ }
7489
+ return false;
7490
+ });
7491
+ }
7351
7492
  function renderFieldsJSX(fields) {
7352
7493
  return fields.filter((f) => !f.hidden).map((f) => {
7353
7494
  if (f.type === "dynamicFields") return "";
@@ -7599,7 +7740,7 @@ function generateFileUploadFieldJSX(field, name, label, hintJSX, requiredStar) {
7599
7740
  <FormItem${formItemProps(field.height)}>
7600
7741
  ${formLabelWithDescriptionLine(name, label, requiredStar, hintJSX, " ")}
7601
7742
  <FormControl>
7602
- <MediaGalleryField
7743
+ <MediaField
7603
7744
  value={field.value}
7604
7745
  onChange={field.onChange}
7605
7746
  onBlur={field.onBlur}
@@ -7767,13 +7908,15 @@ ${fieldsJSX}
7767
7908
  const successHandler = redirectUrl ? `window.location.href = ${JSON.stringify(redirectUrl)}` : "setSubmitted(true)";
7768
7909
  const hasRadio = allFields.some((f) => f.type === "radio");
7769
7910
  const hasFileUpload = allFields.some((f) => f.type === "file" || f.type === "upload");
7911
+ const hasFormDescriptions = hasFormDescriptionFields(allFields);
7912
+ const hasSelectControls = hasSelectFields(allFields);
7770
7913
  const buttonImport = resolveUiImport(cwd, "button");
7771
7914
  const formImport = resolveUiImport(cwd, "form");
7772
7915
  const inputImport = resolveUiImport(cwd, "input");
7773
7916
  const textareaImport = resolveUiImport(cwd, "textarea");
7774
- const selectImport = resolveUiImport(cwd, "select");
7917
+ const selectImport = hasSelectControls ? resolveUiImport(cwd, "select") : "";
7775
7918
  const radioGroupImport = resolveUiImport(cwd, "radio-group");
7776
- const mediaUploadImport = resolveUiImport(cwd, "media-gallery-field");
7919
+ const mediaUploadImport = resolveUiImport(cwd, "media-field");
7777
7920
  const content = buildComponentSource({
7778
7921
  pascal,
7779
7922
  kebab,
@@ -7781,6 +7924,8 @@ ${fieldsJSX}
7781
7924
  hasRadio,
7782
7925
  hasFileUpload,
7783
7926
  hasListFields,
7927
+ hasFormDescriptions,
7928
+ hasSelectControls,
7784
7929
  actionImportPath,
7785
7930
  buttonImport,
7786
7931
  formImport,
@@ -7850,17 +7995,16 @@ import { Button } from '${p15.buttonImport}'
7850
7995
  import {
7851
7996
  Form,
7852
7997
  FormControl,
7853
- FormDescription,
7854
- FormField,
7998
+ ${p15.hasFormDescriptions ? " FormDescription,\n" : ""} FormField,
7855
7999
  FormItem,
7856
8000
  FormLabel,
7857
8001
  FormMessage,
7858
8002
  } from '${p15.formImport}'
7859
8003
  import { Input } from '${p15.inputImport}'${p15.hasFileUpload ? `
7860
- import { MediaGalleryField } from '${p15.mediaUploadImport}'` : ""}
8004
+ import { MediaField } from '${p15.mediaUploadImport}'` : ""}
7861
8005
  ${p15.hasRadio ? `import { RadioGroup, RadioGroupItem } from '${p15.radioGroupImport}'
7862
8006
  ` : ""}
7863
- import { Textarea } from '${p15.textareaImport}'
8007
+ import { Textarea } from '${p15.textareaImport}'${p15.hasSelectControls ? `
7864
8008
  import {
7865
8009
  Select,
7866
8010
  SelectContent,
@@ -7868,6 +8012,7 @@ import {
7868
8012
  SelectTrigger,
7869
8013
  SelectValue,
7870
8014
  } from '${p15.selectImport}'
8015
+ ` : ""}
7871
8016
 
7872
8017
  const formSchema = z.object({
7873
8018
  ${p15.zodFields}
@@ -8015,13 +8160,15 @@ ${buildFieldArrayDecls(listFields)}
8015
8160
  ` : "";
8016
8161
  const hasRadio = fields.some((f) => f.type === "radio");
8017
8162
  const hasFileUpload = fields.some((f) => f.type === "file" || f.type === "upload");
8163
+ const hasFormDescriptions = hasFormDescriptionFields(rawFields);
8164
+ const hasSelectControls = hasSelectFields(rawFields);
8018
8165
  const buttonImport = resolveUiImport(cwd, "button");
8019
8166
  const formImport = resolveUiImport(cwd, "form");
8020
8167
  const inputImport = resolveUiImport(cwd, "input");
8021
8168
  const textareaImport = resolveUiImport(cwd, "textarea");
8022
- const selectImport = resolveUiImport(cwd, "select");
8169
+ const selectImport = hasSelectControls ? resolveUiImport(cwd, "select") : "";
8023
8170
  const radioGroupImport = resolveUiImport(cwd, "radio-group");
8024
- const mediaUploadImport = resolveUiImport(cwd, "media-gallery-field");
8171
+ const mediaUploadImport = resolveUiImport(cwd, "media-field");
8025
8172
  const queryClientImport = hasFileUpload ? `
8026
8173
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query'` : "";
8027
8174
  const queryClientSetup = hasFileUpload ? `
@@ -8050,16 +8197,15 @@ import { Button } from '${buttonImport}'
8050
8197
  import {
8051
8198
  Form,
8052
8199
  FormControl,
8053
- FormDescription,
8054
- FormField,
8200
+ ${hasFormDescriptions ? " FormDescription,\n" : ""} FormField,
8055
8201
  FormItem,
8056
8202
  FormLabel,
8057
8203
  FormMessage,
8058
8204
  } from '${formImport}'
8059
8205
  import { Input } from '${inputImport}'${hasFileUpload ? `
8060
- import { MediaGalleryField } from '${mediaUploadImport}'` : ""}${hasRadio ? `
8206
+ import { MediaField } from '${mediaUploadImport}'` : ""}${hasRadio ? `
8061
8207
  import { RadioGroup, RadioGroupItem } from '${radioGroupImport}'` : ""}
8062
- import { Textarea } from '${textareaImport}'
8208
+ import { Textarea } from '${textareaImport}'${hasSelectControls ? `
8063
8209
  import {
8064
8210
  Select,
8065
8211
  SelectContent,
@@ -8067,6 +8213,7 @@ import {
8067
8213
  SelectTrigger,
8068
8214
  SelectValue,
8069
8215
  } from '${selectImport}'
8216
+ ` : ""}
8070
8217
 
8071
8218
  const formSchema = z.object({
8072
8219
  ${zodFields}
@@ -9435,7 +9582,7 @@ ${updateBeforePublishBlock}
9435
9582
  (typeof value === 'number' && Number.isNaN(value))
9436
9583
  ? null
9437
9584
  : value
9438
- } else if (!field.required && ['date', 'timestamp', 'time', 'string', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
9585
+ } else if (!field.required && ['date', 'timestamp', 'time', 'string', 'email', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
9439
9586
  processedData[key] = value && value !== '' ? value : null
9440
9587
  } else {
9441
9588
  processedData[key] = value
@@ -10676,7 +10823,7 @@ function buildEntityContext(schema, nextMajorVersion) {
10676
10823
  );
10677
10824
  const hasCreatableSelectFields = creatableSelectFields.length > 0;
10678
10825
  const selectFields = collectReadSelectFields(allDbFields);
10679
- const hasSelectFields = selectFields.length > 0;
10826
+ const hasSelectFields2 = selectFields.length > 0;
10680
10827
  const dateRangeFilterFields = new Set(
10681
10828
  resolvedFilters.filter((filter) => filter.type === "date-range").map((filter) => filter.field)
10682
10829
  );
@@ -10829,7 +10976,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
10829
10976
  Singular,
10830
10977
  hasListRels
10831
10978
  );
10832
- const selectResultMapping = hasSelectFields ? wrapWithSelectResolution(baseResultMapping, camelPlural, Singular) : baseResultMapping;
10979
+ const selectResultMapping = hasSelectFields2 ? wrapWithSelectResolution(baseResultMapping, camelPlural, Singular) : baseResultMapping;
10833
10980
  const resultMapping = hasMediaFields ? wrapWithMediaResolution(selectResultMapping, camelPlural, Singular) : selectResultMapping;
10834
10981
  const baseListResultMapping = hasHtmlOutput ? buildResultMapping(
10835
10982
  listDbFields,
@@ -10839,7 +10986,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
10839
10986
  Singular,
10840
10987
  hasListRels
10841
10988
  ) : baseResultMapping;
10842
- const selectListResultMapping = hasSelectFields ? wrapWithSelectResolution(baseListResultMapping, camelPlural, Singular) : baseListResultMapping;
10989
+ const selectListResultMapping = hasSelectFields2 ? wrapWithSelectResolution(baseListResultMapping, camelPlural, Singular) : baseListResultMapping;
10843
10990
  const listResultMapping = hasMediaFields ? wrapWithMediaResolution(selectListResultMapping, camelPlural, Singular) : selectListResultMapping;
10844
10991
  const fieldMeta = createFields.map((f) => `{ name: '${f.name}', type: '${f.type}', required: ${f.required ?? false} }`).join(",\n ");
10845
10992
  const createMappings = createFields.map((f) => ` ${f.name}: ${f.name === "slug" ? "resolvedSlug" : generateFieldMapping(f)}`).join(",\n");
@@ -10870,7 +11017,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
10870
11017
  return ${singleRowMapping}`;
10871
11018
  })();
10872
11019
  const singleRowResolvers = [
10873
- ...hasSelectFields ? [`resolve${Singular}SelectFields`] : [],
11020
+ ...hasSelectFields2 ? [`resolve${Singular}SelectFields`] : [],
10874
11021
  ...hasMediaFields ? [`resolve${Singular}MediaFields`] : []
10875
11022
  ];
10876
11023
  const singleRowReturn = singleRowResolvers.length > 0 ? wrapSingleRowReturnWithResolvers(baseSingleRowReturn, singleRowResolvers) : baseSingleRowReturn;
@@ -10904,7 +11051,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
10904
11051
  hasDraft,
10905
11052
  hasSearch,
10906
11053
  hasFilters,
10907
- hasSelectFields,
11054
+ hasSelectFields: hasSelectFields2,
10908
11055
  hasCreatableSelectFields,
10909
11056
  searchFields,
10910
11057
  htmlOutputFields,
@@ -11120,7 +11267,7 @@ function generateSingleActions(schema, actionsDir, options = {}) {
11120
11267
  );
11121
11268
  const hasCreatableSelectFields = creatableSelectFields.length > 0;
11122
11269
  const selectFields = collectReadSelectFields(dbFields);
11123
- const hasSelectFields = selectFields.length > 0;
11270
+ const hasSelectFields2 = selectFields.length > 0;
11124
11271
  const relationshipFields = dbFields.filter(
11125
11272
  (f) => f.type === "relationship" && f.relationship && !f.multiple
11126
11273
  );
@@ -11233,7 +11380,7 @@ ${allUpsertInterfaceFields}
11233
11380
  const m2mReadBlock = hasM2M ? `
11234
11381
  ${m2mReadQueries}
11235
11382
  ${m2mAssignments}` : "";
11236
- const singleRowReturn = hasSelectFields ? `const row = result[0]
11383
+ const singleRowReturn = hasSelectFields2 ? `const row = result[0]
11237
11384
  const singleRow = ${singleRowMapping}${m2mReadBlock}
11238
11385
  const [resolvedSelectRow] = await resolve${Singular}SelectFields([singleRow])
11239
11386
  return resolvedSelectRow` : `const row = result[0]
@@ -11252,7 +11399,7 @@ ${m2mAssignments}` : "";
11252
11399
  ...hasCreatableSelectFields ? [`${tableVar}SelectOptions`] : []
11253
11400
  ])
11254
11401
  ).sort();
11255
- const selectResolutionFunctions = hasSelectFields ? `
11402
+ const selectResolutionFunctions = hasSelectFields2 ? `
11256
11403
  ${generateSingleResolveSelectFieldsFunction(Singular, selectFields, `${tableVar}SelectOptions`, hasCreatableSelectFields)}
11257
11404
  ` : "";
11258
11405
  const getContent = `'use server'
@@ -11263,7 +11410,7 @@ import { eq } from 'drizzle-orm'
11263
11410
  import { alias } from 'drizzle-orm/pg-core'
11264
11411
  ${cacheReadImport}
11265
11412
  import { ${cacheTagsVar} } from './types'
11266
- import type { ${Singular}${hasSelectFields ? ", SelectOptionValue" : ""} } from './types'
11413
+ import type { ${Singular}${hasSelectFields2 ? ", SelectOptionValue" : ""} } from './types'
11267
11414
 
11268
11415
  ${buildAuthorshipAliasBlock()}
11269
11416
 
@@ -11362,7 +11509,7 @@ ${m2mInputDeclarations ? `${m2mInputDeclarations}
11362
11509
  (typeof value === 'number' && Number.isNaN(value))
11363
11510
  ? null
11364
11511
  : value
11365
- } else if (!field.required && ['date', 'timestamp', 'time', 'string', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
11512
+ } else if (!field.required && ['date', 'timestamp', 'time', 'string', 'email', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
11366
11513
  processedData[key] = value && value !== '' ? value : null
11367
11514
  } else {
11368
11515
  processedData[key] = value
@@ -12722,6 +12869,8 @@ function getFormFieldType(field) {
12722
12869
  switch (field.type) {
12723
12870
  case "boolean":
12724
12871
  return "checkbox";
12872
+ case "email":
12873
+ return "email";
12725
12874
  case "text":
12726
12875
  return "textarea";
12727
12876
  case "markdown":
@@ -12753,6 +12902,10 @@ function getZodType(field) {
12753
12902
  return field.required ? `z.number({ message: '${label} is required' })` : "z.number().nullable().optional()";
12754
12903
  case "boolean":
12755
12904
  return "z.boolean()";
12905
+ case "email": {
12906
+ const base = field.required ? `z.string().min(1, '${label} is required').email('Please enter a valid email address')` : `z.string().optional().refine((val) => !val || val.trim() === '' || z.string().email().safeParse(val).success, { message: 'Please enter a valid email address' })`;
12907
+ return field.length ? `${base}.max(${field.length})` : base;
12908
+ }
12756
12909
  case "string":
12757
12910
  case "varchar":
12758
12911
  case "text":
@@ -12891,6 +13044,40 @@ function collectRelationshipFields(fields, _tabFieldNames) {
12891
13044
  collect(fields);
12892
13045
  return result;
12893
13046
  }
13047
+ function getTabNestedFields(tab) {
13048
+ return [
13049
+ ...tab.fields ?? [],
13050
+ ...tab.slot?.main?.fields ?? [],
13051
+ ...tab.slot?.sidebar?.fields ?? []
13052
+ ];
13053
+ }
13054
+ function collectTabIconNames(fields) {
13055
+ const icons = /* @__PURE__ */ new Set();
13056
+ function collect(fieldsToCheck) {
13057
+ for (const field of fieldsToCheck) {
13058
+ if (field.fields) collect(field.fields);
13059
+ if (field.tabs) {
13060
+ for (const tab of field.tabs) {
13061
+ const icon = tab.icon?.trim();
13062
+ if (icon) icons.add(toPascalCase(icon));
13063
+ collect(getTabNestedFields(tab));
13064
+ }
13065
+ }
13066
+ }
13067
+ }
13068
+ collect(fields);
13069
+ return Array.from(icons);
13070
+ }
13071
+ function renderTabsTrigger(tab, indent, options = {}) {
13072
+ const iconName = tab.icon?.trim() ? toPascalCase(tab.icon.trim()) : null;
13073
+ const className = options.className ? ` className="${options.className}"` : "";
13074
+ if (!iconName)
13075
+ return `${indent}<TabsTrigger value="${tab.name}"${className}>${tab.label}</TabsTrigger>`;
13076
+ return `${indent}<TabsTrigger value="${tab.name}"${className}>
13077
+ ${indent} <${iconName} className="size-4 shrink-0" />
13078
+ ${indent} <span>${tab.label}</span>
13079
+ ${indent}</TabsTrigger>`;
13080
+ }
12894
13081
  function selectDefaultValues(f) {
12895
13082
  if (f.default === void 0 || f.default === null || f.default === "") return [];
12896
13083
  const values = Array.isArray(f.default) ? f.default : [f.default];
@@ -13165,9 +13352,7 @@ function buildUiImports(ctx) {
13165
13352
  if (ctx.hasBoolean) uiImports.push("import { Checkbox } from '@admin/components/ui/checkbox'");
13166
13353
  if (ctx.hasTextarea) uiImports.push("import { Textarea } from '@admin/components/ui/textarea'");
13167
13354
  if (ctx.hasImage || ctx.hasVideo || ctx.hasMedia)
13168
- uiImports.push(
13169
- "import { MediaGalleryField } from '@admin/components/custom/media-gallery-field'"
13170
- );
13355
+ uiImports.push("import { MediaField } from '@admin/components/custom/media-field'");
13171
13356
  if (ctx.hasGallery)
13172
13357
  uiImports.push("import { GalleryField } from '@admin/components/custom/gallery-field'");
13173
13358
  if (ctx.hasIcon || ctx.hasIconPostfix)
@@ -13382,7 +13567,7 @@ ${indent} render={({ field: formField }) => (
13382
13567
  ${indent} <FormItem${formItemProps2(field)}>
13383
13568
  ${indent} ${labelWithDescription(formLabel(field, nestedLabel), nestedHint, `${indent} `)}
13384
13569
  ${indent} <FormControl>
13385
- ${indent} <MediaGalleryField value={formField.value} onChange={formField.onChange} onBlur={formField.onBlur} disabled={isPending}${acceptProp} />
13570
+ ${indent} <MediaField value={formField.value} onChange={formField.onChange} onBlur={formField.onBlur} disabled={isPending}${acceptProp} />
13386
13571
  ${indent} </FormControl>
13387
13572
  ${indent} <FormMessage />
13388
13573
  ${indent} </FormItem>
@@ -13677,10 +13862,10 @@ function generateFieldJSXCore(field, indent = " ", context = {}) {
13677
13862
  if (field.type === "separator") return renderSeparatorField(field, indent);
13678
13863
  if (field.type === "boolean") return renderBooleanField(field, indent, label, hintJSX, context);
13679
13864
  if (field.type === "image")
13680
- return renderMediaGalleryField(field, indent, label, hintJSX, context, "image/*");
13865
+ return renderMediaField(field, indent, label, hintJSX, context, "image/*");
13681
13866
  if (field.type === "video")
13682
- return renderMediaGalleryField(field, indent, label, hintJSX, context, "video/*");
13683
- if (field.type === "media") return renderMediaGalleryField(field, indent, label, hintJSX, context);
13867
+ return renderMediaField(field, indent, label, hintJSX, context, "video/*");
13868
+ if (field.type === "media") return renderMediaField(field, indent, label, hintJSX, context);
13684
13869
  if (field.type === "gallery") return renderGalleryField(field, indent, label, hintJSX, context);
13685
13870
  if (field.type === "icon") return renderIconField(field, indent, label, hintJSX);
13686
13871
  if (field.type === "date") return renderDateField(field, indent, label, hintJSX, context);
@@ -13745,7 +13930,7 @@ ${indent} </FormItem>
13745
13930
  ${indent} )}
13746
13931
  ${indent}/>`;
13747
13932
  }
13748
- function renderMediaGalleryField(field, indent, label, hintJSX, context, accept) {
13933
+ function renderMediaField(field, indent, label, hintJSX, context, accept) {
13749
13934
  const acceptProp = accept ? `
13750
13935
  ${indent} accept="${accept}"` : "";
13751
13936
  const defaultValueFromHandler = defaultValueFromSourceHandler(field, context);
@@ -13760,7 +13945,7 @@ ${indent} render={({ field: formField }) => (
13760
13945
  ${indent} <FormItem${formItemProps2(field)}>
13761
13946
  ${indent} ${formLabelWithDescription(field, label, hintJSX, `${indent} `)}
13762
13947
  ${indent} <FormControl>
13763
- ${indent} <MediaGalleryField
13948
+ ${indent} <MediaField
13764
13949
  ${indent} value={formField.value}
13765
13950
  ${indent} onChange={${onChange}}
13766
13951
  ${indent} onBlur={formField.onBlur}
@@ -14481,6 +14666,23 @@ ${indent}/>`;
14481
14666
  }
14482
14667
 
14483
14668
  // adapters/next/generators/form/form-entity.ts
14669
+ function escapeRegExp(value) {
14670
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14671
+ }
14672
+ function usesIdentifier(source, identifier) {
14673
+ return new RegExp(`\\b${escapeRegExp(identifier)}\\b`).test(source);
14674
+ }
14675
+ function filterNamedImportsByUsage(importDeclarations, source) {
14676
+ return importDeclarations.flatMap((declaration) => {
14677
+ const match = declaration.match(/^import\s+\{\s*([\s\S]*?)\s*\}\s+from\s+(.+)$/);
14678
+ if (!match) return [declaration];
14679
+ const importedNames = match[1].split(",").map((name) => name.trim()).filter(Boolean);
14680
+ const usedNames = importedNames.filter((name) => usesIdentifier(source, name));
14681
+ if (usedNames.length === 0) return [];
14682
+ if (usedNames.length === importedNames.length) return [declaration];
14683
+ return [`import { ${usedNames.join(", ")} } from ${match[2]}`];
14684
+ });
14685
+ }
14484
14686
  function collectSidebarSectionHeadingFields(fields) {
14485
14687
  const headingFields = [];
14486
14688
  for (const field of fields) {
@@ -14641,10 +14843,25 @@ function ${slugValueHelperName}(value: unknown): string {
14641
14843
  slugValueHelperName: hasSlugTitleSource ? slugValueHelperName : void 0
14642
14844
  });
14643
14845
  const cardFieldIndent = " ";
14846
+ function hasRenderableTabsField(fields, excludedField) {
14847
+ for (const field of fields) {
14848
+ if (field.hidden) continue;
14849
+ if (field.type === "tabs" && field !== excludedField) return true;
14850
+ if ((field.type === "group" || field.type === "section") && field.fields) {
14851
+ if (hasRenderableTabsField(field.fields, excludedField)) return true;
14852
+ }
14853
+ if (field.type === "tabs" && field.tabs) {
14854
+ for (const tab of field.tabs) {
14855
+ if (hasRenderableTabsField(tab.fields ?? [], excludedField)) return true;
14856
+ }
14857
+ }
14858
+ }
14859
+ return false;
14860
+ }
14644
14861
  const renderNestedTabsField = (f, childContext = {}) => {
14645
14862
  const indent = cardFieldIndent;
14646
14863
  if (!f.tabs) return "";
14647
- const tabsList = f.tabs.map((t) => `${indent} <TabsTrigger value="${t.name}">${t.label}</TabsTrigger>`).join("\n");
14864
+ const tabsList = f.tabs.map((t) => renderTabsTrigger(t, `${indent} `)).join("\n");
14648
14865
  const tabsContent = f.tabs.map((t) => {
14649
14866
  const tabFields = (t.fields || []).map(
14650
14867
  (tf) => generateFieldJSX2(tf, `${indent} `, {
@@ -14732,7 +14949,7 @@ ${indent} </CardHeader>`;
14732
14949
  if (headingJSX) {
14733
14950
  return `${indent}<Card className="gap-0">
14734
14951
  ${headingJSX}
14735
- ${indent} <CardContent className="space-y-4 p-5">
14952
+ ${indent} <CardContent className="space-y-5 p-5">
14736
14953
  ${fieldsJSX}
14737
14954
  ${indent} </CardContent>
14738
14955
  ${indent}</Card>`;
@@ -14784,42 +15001,63 @@ ${cards.join("\n")}
14784
15001
  ).filter(Boolean).join("\n");
14785
15002
  return [commonMainFieldsJSX, tabFieldsJSX].filter(Boolean).join("\n");
14786
15003
  };
14787
- const renderTopLevelTabContent = (tab, commonMainFieldsJSX, commonSidebarEntries) => {
15004
+ const renderTopLevelTabFormBody = (tab, commonMainFieldsJSX, commonSidebarEntries) => {
14788
15005
  const tabMainFieldsJSX = renderTabMainFields(tab, commonMainFieldsJSX);
14789
15006
  const tabSidebarEntries = [
14790
15007
  ...commonSidebarEntries,
14791
15008
  ...collectSidebarFields(tab.fields || [], tab.name)
14792
15009
  ].map((entry) => ({ ...entry, jsx: renderStaticSidebarEntry(entry) })).filter((entry) => entry.jsx.length > 0);
14793
- const sidebarColumn = renderSidebarColumn(tabSidebarEntries, { scrollable: true });
14794
- return ` <TabsContent value="${tab.name}" className="min-h-0 w-full flex-1 overflow-hidden">
14795
- <div className="grid grid-cols-1 ${tabSidebarEntries.length > 0 ? "lg:grid-cols-[1fr_360px] " : ""}h-full min-h-0 gap-4 items-stretch">
14796
- <div className="h-full min-h-0 min-w-0 pb-4">
14797
- <Card className="relative min-h-0 min-w-0 space-y-4 overflow-y-auto p-8 gap-0 h-full">
15010
+ const sidebarColumn = renderSidebarColumn(tabSidebarEntries);
15011
+ return ` <div className="grid grid-cols-1 ${tabSidebarEntries.length > 0 ? "lg:grid-cols-[1fr_360px] " : ""}h-full min-h-0 gap-4 items-stretch">
15012
+ <div className="h-full min-h-0 min-w-0">
15013
+ <Card className="min-h-0 min-w-0 h-full">
15014
+ <CardContent className="space-y-5 gap-0 h-full min-h-0">
14798
15015
  ${tabMainFieldsJSX}
14799
- </Card>
14800
- </div>
15016
+ </CardContent>
15017
+ </Card>
15018
+ </div>
14801
15019
  ${sidebarColumn}
14802
- </div>
14803
- </TabsContent>`;
15020
+ </div>`;
14804
15021
  };
14805
- const renderTopLevelTabsLayout = (tabsField2, commonFields) => {
14806
- if (!tabsField2.tabs) return "";
15022
+ const topLevelTabComponentName = (tab) => `${Singular}FormTab${toPascalCase(tab.name)}`;
15023
+ const topLevelTabFileBaseName = (tab) => `${schema.name}-form-tab-${toKebabCase(tab.name)}`;
15024
+ const renderTopLevelTabComponentSpecs = (tabsField2, commonFields) => {
15025
+ if (!tabsField2.tabs) return [];
14807
15026
  const commonMainFieldsJSX = commonFields.map(renderMainField).filter(Boolean).join("\n");
14808
15027
  const commonSidebarEntries = collectSidebarFields(commonFields);
14809
- const tabsList = tabsField2.tabs.map((tab) => ` <TabsTrigger value="${tab.name}">${tab.label}</TabsTrigger>`).join("\n");
14810
- const tabsContent = tabsField2.tabs.map((tab) => renderTopLevelTabContent(tab, commonMainFieldsJSX, commonSidebarEntries)).join("\n");
14811
- return ` <Tabs value={activeTab} onValueChange={(value) => void setActiveTab(value as TabValue)} className="flex h-full min-h-0 w-full flex-col gap-4">
14812
- <TabsList className="sticky top-0 z-10 shrink-0 bg-background">
14813
- ${tabsList}
14814
- </TabsList>
14815
- ${tabsContent}
14816
- </Tabs>`;
15028
+ return tabsField2.tabs.map((tab) => ({
15029
+ tab,
15030
+ componentName: topLevelTabComponentName(tab),
15031
+ fileBaseName: topLevelTabFileBaseName(tab),
15032
+ body: renderTopLevelTabFormBody(tab, commonMainFieldsJSX, commonSidebarEntries)
15033
+ }));
14817
15034
  };
15035
+ const renderTopLevelTabComponentSwitch = (spec) => ` {tab === ${JSON.stringify(spec.tab.name)} ? <${spec.componentName} state={state} /> : null}`;
14818
15036
  const visibleFields = allFormFields.filter((f) => !(hasDraft && f.name === "published"));
14819
15037
  const topLevelTabsField = visibleFields.find(
14820
15038
  (field) => field.type === "tabs" && (field.slot ?? "main") === "main" && field.tabs?.length
14821
15039
  );
14822
15040
  const topLevelCommonFields = topLevelTabsField ? visibleFields.filter((field) => field !== topLevelTabsField) : visibleFields;
15041
+ const topLevelTabComponents = topLevelTabsField ? renderTopLevelTabComponentSpecs(topLevelTabsField, topLevelCommonFields) : [];
15042
+ const contentUsesTabsField = topLevelTabsField ? hasRenderableTabsField(topLevelCommonFields, topLevelTabsField) || topLevelTabsField.tabs?.some(
15043
+ (tab) => hasRenderableTabsField(tab.fields ?? [], topLevelTabsField)
15044
+ ) === true : hasTabsField;
15045
+ const topLevelTabChildFields = topLevelTabsField?.tabs?.flatMap((tab) => [
15046
+ ...tab.fields ?? [],
15047
+ ...tab.slot?.main?.fields ?? [],
15048
+ ...tab.slot?.sidebar?.fields ?? []
15049
+ ]) ?? [];
15050
+ const contentTabIconNames = collectTabIconNames(
15051
+ topLevelTabsField ? [...topLevelCommonFields, ...topLevelTabChildFields] : allFormFields
15052
+ );
15053
+ const topLevelTabIconNames = Array.from(
15054
+ new Set(
15055
+ topLevelTabsField?.tabs?.flatMap((tab) => {
15056
+ const icon = tab.icon?.trim();
15057
+ return icon ? [toPascalCase(icon)] : [];
15058
+ }) ?? []
15059
+ )
15060
+ );
14823
15061
  const mainFieldsJSX = topLevelCommonFields.map(renderMainField).filter(Boolean).join("\n");
14824
15062
  const sidebarEntries = collectSidebarFields(topLevelCommonFields).map((entry) => ({ ...entry, jsx: renderConditionalSidebarEntry(entry) })).filter((entry) => entry.jsx.length > 0);
14825
15063
  const hasSidebarFields = sidebarEntries.length > 0;
@@ -14842,10 +15080,10 @@ ${tabsContent}
14842
15080
  const sidebarCardJSX = !hasSidebarFields ? "" : shouldGuardSidebarByTab ? ` {hasVisibleSidebarFields ? (
14843
15081
  ${renderSidebarColumn(sidebarEntries, { conditionalTabs: true })}
14844
15082
  ) : null}` : renderSidebarColumn(sidebarEntries, { conditionalTabs: true });
14845
- const formBodyJSX = topLevelTabsField ? renderTopLevelTabsLayout(topLevelTabsField, topLevelCommonFields) : `${formGridOpen}
15083
+ const formBodyJSX = topLevelTabsField ? topLevelTabComponents.map(renderTopLevelTabComponentSwitch).join("\n") : `${formGridOpen}
14846
15084
  <div className="h-full min-w-0">
14847
15085
  <Card className="min-w-0 h-full">
14848
- <CardContent className="space-y-4 gap-0 h-full">
15086
+ <CardContent className="space-y-5 gap-0 h-full">
14849
15087
  ${mainFieldsJSX}
14850
15088
  </CardContent>
14851
15089
  </Card>
@@ -14933,7 +15171,7 @@ ${sidebarCardJSX}
14933
15171
  hasSelectCombobox,
14934
15172
  hasCreatableSelect,
14935
15173
  hasMultiSelect: multiSelectFields.length > 0,
14936
- hasTabsField,
15174
+ hasTabsField: contentUsesTabsField,
14937
15175
  hasRelationship,
14938
15176
  hasSimpleList,
14939
15177
  hasNestedList,
@@ -14951,7 +15189,7 @@ ${sidebarCardJSX}
14951
15189
  if (!lucideIcons.includes(icon)) lucideIcons.push(icon);
14952
15190
  }
14953
15191
  };
14954
- addLucideIcons(...sectionHeadingIconNames);
15192
+ addLucideIcons(...sectionHeadingIconNames, ...contentTabIconNames);
14955
15193
  if (hasRelationship || hasSelectCombobox) addLucideIcons("Check", "ChevronsUpDown");
14956
15194
  if (hasCreatableSelect) addLucideIcons("Plus");
14957
15195
  if (hasNestedList) {
@@ -15094,12 +15332,12 @@ ${sidebarCardJSX}
15094
15332
  "handleErrors"
15095
15333
  ]);
15096
15334
  const hookReturnObject = hookReturnNames.map((name) => ` ${name}`).join(",\n");
15097
- const contentUsesInitialData = hasDraft || hasSidebarFields && showMetadataWidget || topLevelTabsField && showMetadataWidget;
15098
- const contentStateNames = uniqueNames([
15335
+ const contentUsesInitialData = hasDraft || !topLevelTabsField && hasSidebarFields && showMetadataWidget;
15336
+ const standardContentStateNames = uniqueNames([
15099
15337
  ...contentUsesInitialData ? ["initialData"] : [],
15100
15338
  "form",
15101
15339
  ...contentUsesPendingState ? ["isPending"] : [],
15102
- ...hasTabsField ? ["activeTab", "setActiveTab"] : [],
15340
+ ...contentUsesTabsField ? ["activeTab", "setActiveTab"] : [],
15103
15341
  ...relationshipHookReturnNames,
15104
15342
  ...creatableSelectReturnNames,
15105
15343
  ...staticMultiSelectReturnNames,
@@ -15108,8 +15346,15 @@ ${sidebarCardJSX}
15108
15346
  "onSubmit",
15109
15347
  "handleErrors"
15110
15348
  ]);
15349
+ const topLevelTabRouterStateNames = uniqueNames([
15350
+ ...hasDraft ? ["initialData"] : [],
15351
+ "form",
15352
+ "onSubmit",
15353
+ "handleErrors"
15354
+ ]);
15355
+ const contentStateNames = topLevelTabsField ? topLevelTabRouterStateNames : standardContentStateNames;
15111
15356
  const contentStateDestructure = contentStateNames.map((name) => ` ${name}`).join(",\n");
15112
- const needsCn = hasRelationship || hasSelectCombobox || shouldGuardSidebarByTab || Boolean(topLevelTabsField && showMetadataWidget);
15357
+ const needsCn = hasRelationship || hasSelectCombobox || shouldGuardSidebarByTab;
15113
15358
  const metadataOffsetConst = showMetadataWidget ? "const SCROLL_BORDER_OFFSET = 8;\n\n" : "";
15114
15359
  const tabParserConst = hasTabsField ? `const TAB_VALUES = [${tabNames.map((name) => JSON.stringify(name)).join(", ")}] as const
15115
15360
  const TAB_PARSER = parseAsStringLiteral(TAB_VALUES).withDefault(TAB_VALUES[0])
@@ -15141,6 +15386,32 @@ export type TabValue = (typeof TAB_VALUES)[number]
15141
15386
  )}
15142
15387
 
15143
15388
  ` : "";
15389
+ const topLevelTabsClassName = showMetadataWidget ? `className={cn('min-h-[calc(100svh-56px)] w-full gap-0', {
15390
+ 'min-h-[calc(100svh-56px-56px)]': initialData?.id != null
15391
+ })}` : 'className="h-[calc(100svh-56px)] min-h-0 w-full gap-0"';
15392
+ const topLevelTabsShell = topLevelTabsField?.tabs ? ` <Tabs value={entityForm.activeTab} onValueChange={(value) => void entityForm.setActiveTab(value as TabValue)} ${topLevelTabsClassName}>
15393
+ <div className="flex h-14 shrink-0 w-full bg-background border-b border-border/80 px-4 items-center">
15394
+ <TabsList className="bg-transparent border-none">
15395
+ ${topLevelTabsField.tabs.map((tab) => renderTabsTrigger(tab, " ", { className: "px-3" })).join("\n")}
15396
+ </TabsList>
15397
+ </div>
15398
+
15399
+ <main className="w-full flex-1 min-h-0 p-4">
15400
+ ${topLevelTabsField.tabs.map(
15401
+ (tab) => ` <TabsContent value="${tab.name}" className="h-full min-h-0 w-full flex-1">
15402
+ <Form {...entityForm.form}>
15403
+ <${contentComponentName} state={entityForm} tab="${tab.name}" />
15404
+ </Form>
15405
+ </TabsContent>`
15406
+ ).join("\n")}
15407
+ </main>
15408
+ </Tabs>` : "";
15409
+ const defaultShellBody = ` <main className="w-full p-4 h-full">
15410
+ <Form {...entityForm.form}>
15411
+ <${contentComponentName} state={entityForm} />
15412
+ </Form>
15413
+ </main>`;
15414
+ const shellBody = topLevelTabsField ? topLevelTabsShell : defaultShellBody;
15144
15415
  const hookContent = `'use client'
15145
15416
 
15146
15417
  import * as React from 'react'
@@ -15260,27 +15531,63 @@ ${hookReturnObject}
15260
15531
  }
15261
15532
  }
15262
15533
  `;
15534
+ const topLevelTabComponentImports = topLevelTabComponents.map((spec) => `import { ${spec.componentName} } from './${spec.fileBaseName}'`).join("\n");
15535
+ const contentImports = topLevelTabsField ? `import type { TabValue, ${hookName} } from '${hookImportPath}'${topLevelTabComponentImports ? `
15536
+ ${topLevelTabComponentImports}` : ""}` : `${lucideIcons.length > 0 ? `import { ${lucideIcons.join(", ")} } from 'lucide-react'
15537
+ ` : ""}${hasTabsField ? `import type { TabValue, ${hookName} } from '${hookImportPath}'` : `import type { ${hookName} } from '${hookImportPath}'`}${needsCn ? "\nimport { cn } from '@admin/utils/shared/cn'" : ""}
15538
+ ${uiImports.join("\n")}`;
15539
+ const contentSidebarVisibilityState = topLevelTabsField ? "" : sidebarVisibilityState;
15540
+ const renderTopLevelTabComponentFile = (spec) => {
15541
+ const body = spec.body;
15542
+ const tabStateNames = hookReturnNames.filter((name) => usesIdentifier(body, name));
15543
+ const tabStateDestructure = tabStateNames.length > 0 ? ` const {
15544
+ ${tabStateNames.map((name) => ` ${name}`).join(",\n")}
15545
+ } = state
15546
+
15547
+ ` : "";
15548
+ const tabLucideIcons = lucideIcons.filter((icon) => usesIdentifier(body, icon));
15549
+ const tabNeedsCn = /\bcn\s*\(/.test(body);
15550
+ const tabImports = [
15551
+ tabLucideIcons.length > 0 ? `import { ${tabLucideIcons.join(", ")} } from 'lucide-react'` : "",
15552
+ `import type { ${hookName} } from '${hookImportPath}'`,
15553
+ tabNeedsCn ? "import { cn } from '@admin/utils/shared/cn'" : "",
15554
+ ...filterNamedImportsByUsage(uiImports, body)
15555
+ ].filter(Boolean).join("\n");
15556
+ return createGeneratedFile(
15557
+ `${pagesDir}/${schema.name}/${spec.fileBaseName}.tsx`,
15558
+ `'use client'
15559
+
15560
+ ${tabImports}
15561
+
15562
+ interface ${spec.componentName}Props {
15563
+ state: ReturnType<typeof ${hookName}>
15564
+ }
15565
+
15566
+ export function ${spec.componentName}({ state }: ${spec.componentName}Props) {
15567
+ ${tabStateDestructure} return (
15568
+ ${body}
15569
+ )
15570
+ }
15571
+ `
15572
+ );
15573
+ };
15574
+ const topLevelTabComponentFiles = topLevelTabComponents.map(renderTopLevelTabComponentFile);
15263
15575
  const content = `'use client'
15264
- ${lucideIcons.length > 0 ? `
15265
- import { ${lucideIcons.join(", ")} } from 'lucide-react'` : ""}${hasTabsField ? `
15266
- import type { TabValue, ${hookName} } from '${hookImportPath}'` : `
15267
- import type { ${hookName} } from '${hookImportPath}'`}${needsCn ? "\nimport { cn } from '@admin/utils/shared/cn'" : ""}
15268
- ${uiImports.join("\n")}
15576
+
15577
+ ${contentImports}
15269
15578
 
15270
15579
  interface ${contentComponentName}Props {
15271
15580
  state: ReturnType<typeof ${hookName}>
15581
+ ${topLevelTabsField ? " tab: TabValue\n" : ""}
15272
15582
  }
15273
15583
 
15274
- export function ${contentComponentName}({ state }: ${contentComponentName}Props) {
15584
+ export function ${contentComponentName}({ state${topLevelTabsField ? ", tab" : ""} }: ${contentComponentName}Props) {
15275
15585
  const {
15276
15586
  ${contentStateDestructure}
15277
15587
  } = state
15278
- ${sidebarVisibilityState}
15588
+ ${contentSidebarVisibilityState}
15279
15589
  return (
15280
- <form id="${schema.name}-form" onSubmit={form.handleSubmit((values) => onSubmit(values${hasDraft ? ", initialData?.published ?? false" : ""}), handleErrors)} ${topLevelTabsField ? showMetadataWidget ? `className={cn('min-h-0', {
15281
- 'h-[calc(100svh-56px-56px-32px)]': initialData?.id != null,
15282
- 'h-[calc(100svh-56px-32px)]': initialData?.id == null
15283
- })}` : 'className="h-[calc(100svh-56px-32px)] min-h-0"' : 'className="space-y-6 h-full"'}>
15590
+ <form id="${schema.name}-form" onSubmit={form.handleSubmit((values) => onSubmit(values${hasDraft ? ", initialData?.published ?? false" : ""}), handleErrors)} ${topLevelTabsField ? 'className="h-full"' : 'className="space-y-6 h-full"'}>
15284
15591
  ${formBodyJSX}
15285
15592
  </form>
15286
15593
  )
@@ -15288,11 +15595,15 @@ ${formBodyJSX}
15288
15595
  `;
15289
15596
  const shellContent = `'use client'
15290
15597
  ${showMetadataWidget ? "\nimport { usePageScrollThreshold } from '@admin/hooks/use-page-scroll-threshold'" : ""}
15598
+ ${topLevelTabIconNames.length > 0 ? `import { ${topLevelTabIconNames.join(", ")} } from 'lucide-react'
15599
+ ` : ""}
15291
15600
  import { Button } from '@admin/components/ui/button'
15292
15601
  import { Form } from '@admin/components/ui/form'
15293
- import { PageHeader } from '@admin/components/shared/page-header'${showMetadataWidget ? "\nimport { EntityMetadata } from '@admin/components/shared/entity-metadata'\nimport { cn } from '@admin/utils/shared/cn'" : ""}
15602
+ ${topLevelTabsField ? "import { Tabs, TabsContent, TabsList, TabsTrigger } from '@admin/components/ui/tabs'\n" : ""}import { PageHeader } from '@admin/components/shared/page-header'${showMetadataWidget ? "\nimport { EntityMetadata } from '@admin/components/shared/entity-metadata'\nimport { cn } from '@admin/utils/shared/cn'" : ""}
15294
15603
  import type { ${Singular} } from '@admin/actions/${actionImportPath}'
15295
15604
  ${showMetadataWidget ? `import { getVersionsBy${Singular}Id, restore${Singular}Version } from '@admin/actions/${actionImportPath}'` : ""}
15605
+ ${topLevelTabsField ? `import type { TabValue } from '${hookImportPath}'
15606
+ ` : ""}
15296
15607
  import { ${hookName} } from '${hookImportPath}'
15297
15608
  import { ${contentComponentName} } from './${schema.name}-form-content'
15298
15609
 
@@ -15340,11 +15651,7 @@ ${hasDraft ? ` <div className="flex items-center gap-1">
15340
15651
  </Button>`}
15341
15652
  }
15342
15653
  />
15343
- ${metadataStrip} <main className="w-full p-4 h-full">
15344
- <Form {...entityForm.form}>
15345
- <${contentComponentName} state={entityForm} />
15346
- </Form>
15347
- </main>
15654
+ ${metadataStrip}${shellBody}
15348
15655
  </>
15349
15656
  )
15350
15657
  }
@@ -15353,6 +15660,7 @@ ${metadataStrip} <main className="w-full p-4 h-full">
15353
15660
  files: [
15354
15661
  createGeneratedFile(hookPath, hookContent),
15355
15662
  createGeneratedFile(`${pagesDir}/${schema.name}/${schema.name}-form-content.tsx`, content),
15663
+ ...topLevelTabComponentFiles,
15356
15664
  createGeneratedFile(`${pagesDir}/${schema.name}/${schema.name}-form.tsx`, shellContent)
15357
15665
  ]
15358
15666
  };
@@ -15481,7 +15789,7 @@ function buildGroupFieldsJSX(group2, analysis, defaultValueFromSourceNames, crea
15481
15789
  const indent = " ";
15482
15790
  return group2.fields.map((f) => {
15483
15791
  if (f.type === "tabs" && f.tabs) {
15484
- const tabsList = f.tabs.map((t) => ` <TabsTrigger value="${t.name}">${t.label}</TabsTrigger>`).join("\n");
15792
+ const tabsList = f.tabs.map((t) => renderTabsTrigger(t, " ")).join("\n");
15485
15793
  const tabsContent = f.tabs.map((t) => {
15486
15794
  const tabFields = (t.fields || []).map(
15487
15795
  (tf) => generateFieldJSX2(tf, " ", {
@@ -15704,7 +16012,7 @@ ${creatableSelectHandlers ? `${creatableSelectHandlers}
15704
16012
  <CardHeader>
15705
16013
  <CardTitle>${group2.title}</CardTitle>${descriptionLine}
15706
16014
  </CardHeader>
15707
- <CardContent className="space-y-6">
16015
+ <CardContent className="space-y-5">
15708
16016
  ${fieldsJSX}
15709
16017
  </CardContent>
15710
16018
  <CardFooter>${group2.hint ? `
@@ -15785,6 +16093,7 @@ function generateSingleForm(schema, pagesDir, options = {}) {
15785
16093
  (group2) => analyzeGroup(group2.fields).hasNestedObjectList
15786
16094
  );
15787
16095
  const hasSimpleList = hasList && hasSimpleListField2(allFormFields);
16096
+ const tabIconNames = collectTabIconNames(allFormFields);
15788
16097
  const allRelFields = [];
15789
16098
  for (const g of cardGroups) {
15790
16099
  const a = analyzeGroup(g.fields);
@@ -15830,12 +16139,17 @@ function generateSingleForm(schema, pagesDir, options = {}) {
15830
16139
  CardHeader,
15831
16140
  CardTitle
15832
16141
  } from '@admin/components/ui/card'`);
15833
- const lucideIcons = ["LoaderCircle"];
15834
- if (hasRelationship || hasSelectCombobox) lucideIcons.push("Check", "ChevronsUpDown");
15835
- if (hasCreatableSelect) lucideIcons.push("Plus");
16142
+ const lucideIcons = [];
16143
+ const addLucideIcons = (...icons) => {
16144
+ for (const icon of icons) {
16145
+ if (!lucideIcons.includes(icon)) lucideIcons.push(icon);
16146
+ }
16147
+ };
16148
+ addLucideIcons("LoaderCircle", ...tabIconNames);
16149
+ if (hasRelationship || hasSelectCombobox) addLucideIcons("Check", "ChevronsUpDown");
16150
+ if (hasCreatableSelect) addLucideIcons("Plus");
15836
16151
  if (hasNestedList) {
15837
- if (!lucideIcons.includes("Plus")) lucideIcons.push("Plus");
15838
- if (!lucideIcons.includes("X")) lucideIcons.push("X");
16152
+ addLucideIcons("Plus", "X");
15839
16153
  }
15840
16154
  const relHookImports = allRelFields.map((f) => {
15841
16155
  const relPlural = toPascalCase(pluralize(f.relationship || ""));
@@ -18612,6 +18926,7 @@ import fs24 from "fs";
18612
18926
  import path29 from "path";
18613
18927
  var ENTITY_SINGLE_ADD_FIELD_TYPES = [
18614
18928
  "string",
18929
+ "email",
18615
18930
  "varchar",
18616
18931
  "text",
18617
18932
  "markdown",
@@ -18714,6 +19029,11 @@ function loadAuthoredGeneratedSchema(schemasDir, name) {
18714
19029
  const filePath = filePaths[0];
18715
19030
  const parsed = parseJsonObject(fs24.readFileSync(filePath, "utf-8"), filePath);
18716
19031
  const type = parsed.type;
19032
+ if (type === void 0) {
19033
+ throw new Error(
19034
+ `Schema "${name}" is missing required top-level "type". Expected "entity", "single", or "form".`
19035
+ );
19036
+ }
18717
19037
  if (type === "form") {
18718
19038
  return {
18719
19039
  kind: "form",
@@ -20388,7 +20708,7 @@ async function promptEntityIntegrationOptions(schema, field) {
20388
20708
  summary.push(`table column: ${field.name}`);
20389
20709
  }
20390
20710
  }
20391
- if (["string", "varchar", "text", "markdown", "richtext"].includes(field.type)) {
20711
+ if (["string", "email", "varchar", "text", "markdown", "richtext"].includes(field.type)) {
20392
20712
  const addSearch = await promptConfirm2("Add this field to search?", false);
20393
20713
  if (addSearch) {
20394
20714
  schema.search = schema.search ?? { fields: [] };
@@ -20448,6 +20768,8 @@ function schemaColumnTypeForField(field) {
20448
20768
  return "boolean";
20449
20769
  case "select":
20450
20770
  return "badge";
20771
+ case "email":
20772
+ return "email";
20451
20773
  case "string":
20452
20774
  case "varchar":
20453
20775
  case "text":
@@ -24884,7 +25206,7 @@ var STATIC_CUSTOM_DEPENDENCIES = {
24884
25206
  "content-editor-rich-extensions",
24885
25207
  "content-editor-editor-view-utils",
24886
25208
  "markdown-utils",
24887
- "media-gallery-field",
25209
+ "media-field",
24888
25210
  "use-content-editor",
24889
25211
  "use-content-editor-table-add-controls",
24890
25212
  "use-content-editor-source-mode",
@@ -24974,7 +25296,7 @@ var STATIC_CUSTOM_DEPENDENCIES = {
24974
25296
  "content-editor/use-tiptap-editor",
24975
25297
  "tiptap-utils"
24976
25298
  ],
24977
- "content-editor/media-gallery-block": ["media-gallery-field"],
25299
+ "content-editor/media-gallery-block": ["media-field"],
24978
25300
  "content-editor/mobile-toolbar-content": [
24979
25301
  "content-editor/math-popover",
24980
25302
  "content-editor/use-tiptap-editor",
@@ -24999,20 +25321,25 @@ var STATIC_CUSTOM_DEPENDENCIES = {
24999
25321
  ],
25000
25322
  "date-range-picker": ["date-utils"],
25001
25323
  "gallery-field": [
25002
- "gallery-thumbnail",
25324
+ "card",
25325
+ "carousel",
25326
+ "media-field",
25327
+ "media-field-empty-state",
25003
25328
  "media-gallery-dialog",
25329
+ "skeleton",
25004
25330
  "sortable-gallery-item",
25005
25331
  "use-media"
25006
25332
  ],
25007
25333
  "icon-picker": ["icons-column-skeleton", "icons-data"],
25008
- "sortable-gallery-item": ["gallery-thumbnail"],
25009
- "media-gallery-field": [
25334
+ "media-field": [
25335
+ "media-field-empty-state",
25010
25336
  "media-gallery-dialog",
25011
25337
  "media-preview",
25012
25338
  "media-fallback-utils",
25013
25339
  "use-copy-to-clipboard",
25014
25340
  "use-media"
25015
25341
  ],
25342
+ "media-field-empty-state": ["button", "card", "cn", "media-gallery-dialog"],
25016
25343
  "nested-object-list-field": ["form-list-field-utils"],
25017
25344
  "progressive-blur": ["use-page-boundary-blur-visibility"],
25018
25345
  "upload-dropzone": ["upload-utils"]
@@ -25045,7 +25372,7 @@ var TIPTAP_TEMPLATE_DEPENDENCIES = [
25045
25372
  "content-editor/source-mode",
25046
25373
  "tiptap-utils",
25047
25374
  "content-editor/media-gallery-block",
25048
- "media-gallery-field"
25375
+ "media-field"
25049
25376
  ];
25050
25377
  var TIPTAP_CONTENT_EDITOR_DIRECTORIES = ["tiptap-extension", "tiptap-node", "tiptap-ui"];
25051
25378
  var CODEMIRROR_PACKAGE_DEPS = [
@@ -25455,7 +25782,7 @@ var TEMPLATE_REGISTRY = {
25455
25782
  relPath: "hooks/content-editor/use-content-editor-source-mode.tsx",
25456
25783
  content: () => readTemplate("hooks/content-editor/use-content-editor-source-mode.tsx"),
25457
25784
  dependencies: [
25458
- "media-gallery-field",
25785
+ "media-field",
25459
25786
  "use-admin-theme",
25460
25787
  "markdown-utils",
25461
25788
  "content-editor-source-media-utils",
@@ -25476,7 +25803,7 @@ var TEMPLATE_REGISTRY = {
25476
25803
  "tiptap",
25477
25804
  "tiptap-utils",
25478
25805
  "content-editor/media-gallery-block",
25479
- "media-gallery-field"
25806
+ "media-field"
25480
25807
  ]
25481
25808
  },
25482
25809
  "use-content-editor-media-insertion": {