From 82051632b942f8200948f8465023fe73464e6142 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 8 Oct 2026 22:09:16 +0000 Subject: [PATCH 1/3] refactor(plugin-designer): the designer forms pick with the shared Select (objectui#11865) FieldDesigner's type filter, DataModelDesigner's field-type picker, BrandingEditor's font family and AppCreationWizard's template were browser-native selects. Each is now the shared Radix Select through a module-private picker whose items carry option indexes, so an option whose value is '' needs no empty SelectItem. A stored value none of the options carries is shown as itself. DataModelDesigner's shortcut guard also skips a combobox trigger, so Delete or Escape on the field-type picker stays the picker's own key, as it was on the native control. Claude-Session: https://claude.ai/code/session_01MgfduSkFrfM3eorB3UGfAU Co-authored-by: Claude --- .../plugin-designer/src/AppCreationWizard.tsx | 82 ++++++++++++--- .../plugin-designer/src/BrandingEditor.tsx | 84 +++++++++++++--- .../plugin-designer/src/DataModelDesigner.tsx | 86 +++++++++++++--- .../plugin-designer/src/FieldDesigner.tsx | 99 ++++++++++++++++--- ...dDesigner.choiceTypesHidden-11253.test.tsx | 16 +-- ...eldDesigner.formulaControlRetired.test.tsx | 20 ++-- 6 files changed, 321 insertions(+), 66 deletions(-) diff --git a/packages/plugin-designer/src/AppCreationWizard.tsx b/packages/plugin-designer/src/AppCreationWizard.tsx index c3e4ffb529..dc8916e906 100644 --- a/packages/plugin-designer/src/AppCreationWizard.tsx +++ b/packages/plugin-designer/src/AppCreationWizard.tsx @@ -42,6 +42,7 @@ import { Globe, X, } from 'lucide-react'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@object-ui/components'; import { clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; import { resolveNavItemLabel, type NavTargetLabelResolver } from '@object-ui/layout'; @@ -280,6 +281,69 @@ function StepIndicator({ steps, currentIndex, onStepClick }: StepIndicatorProps) // Step 1: Basic Info // ============================================================================ +/** The item a template none of the options carries is shown by. */ +const OUTSIDE_OPTIONS = 'outside'; + +/** + * objectui#11865 — the basic step's template picker, drawn with the shared + * `Select`, the control the rest of the designer picks with. It used to be a + * browser-native ` { + // `undefined` for the outside item: it is the draft's own template, so there is nothing to write. + const picked = options[Number(token)]; + if (picked) onPick(picked.value); + }} + disabled={disabled} + > + + + + + {at === -1 && {value}} + {options.map((o, i) => ( + + {o.label} + + ))} + + + ); +} + interface BasicInfoStepProps { draft: AppWizardDraft; templates: Array<{ id: string; label: string; description?: string }>; @@ -385,20 +449,16 @@ function BasicInfoStep({ draft, templates, readOnly, onChange, t }: BasicInfoSte - + /> )} diff --git a/packages/plugin-designer/src/BrandingEditor.tsx b/packages/plugin-designer/src/BrandingEditor.tsx index b7203986e8..06738fff6f 100644 --- a/packages/plugin-designer/src/BrandingEditor.tsx +++ b/packages/plugin-designer/src/BrandingEditor.tsx @@ -31,6 +31,7 @@ import { Upload, Globe, } from 'lucide-react'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@object-ui/components'; import { clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; import { useDesignerTranslation } from './hooks/useDesignerTranslation'; @@ -98,6 +99,70 @@ const FONT_FAMILIES = [ 'system-ui', ]; +/** The item a font none of the options carries is shown by. */ +const OUTSIDE_OPTIONS = 'outside'; + +/** + * objectui#11865 — the font family picker, drawn with the shared `Select`, the + * control the rest of the designer picks with. It used to be a browser-native + * ` { + // `undefined` for the outside item: it is the branding's own font, so there is nothing to write. + const picked = options[Number(token)]; + if (picked) onPick(picked.value); + }} + disabled={disabled} + > + + + + + {at === -1 && {value}} + {options.map((o, i) => ( + + {o.label} + + ))} + + + ); +} + // ============================================================================ // BrandingEditor Component // ============================================================================ @@ -398,21 +463,16 @@ export function BrandingEditor({
- + />
diff --git a/packages/plugin-designer/src/DataModelDesigner.tsx b/packages/plugin-designer/src/DataModelDesigner.tsx index 932dd89a75..3ecdbba4d3 100644 --- a/packages/plugin-designer/src/DataModelDesigner.tsx +++ b/packages/plugin-designer/src/DataModelDesigner.tsx @@ -9,6 +9,7 @@ import React, { useState, useCallback, useEffect, useRef } from 'react'; import type { DataModelEntity, DataModelField, DataModelRelationship, DesignerCanvasConfig } from '@object-ui/types'; import { Database, Plus, Trash2, Link2, Undo2, Redo2, Grid3X3, ZoomIn, ZoomOut, RotateCcw, ChevronDown, ChevronRight, Copy, Clipboard, Users } from 'lucide-react'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@object-ui/components'; import { clsx } from 'clsx'; import { twMerge } from 'tailwind-merge'; import { useDesignerHistory } from './hooks/useDesignerHistory'; @@ -34,6 +35,68 @@ const DATA_MODEL_FIELD_TYPES = [ 'multiselect', 'lookup', 'attachment', 'formula', 'autonumber', ] as const; +/** The item a type none of the options carries is shown by. */ +const OUTSIDE_OPTIONS = 'outside'; + +/** Stops a click inside the type picker from reaching the entity card, which selects on click. */ +const keepClickInPicker = (e: React.MouseEvent) => e.stopPropagation(); + +/** + * objectui#11865 — a field row's type picker on an entity card, drawn with the + * shared `Select`, the control the rest of the designer picks with. It used to + * be a browser-native ` { + // `undefined` for the outside item: it is the field's own type, so there is nothing to write. + const picked = DATA_MODEL_FIELD_TYPES[Number(token)]; + if (picked) onPick(picked); + }} + > + + + + + {outside && {value}} + {DATA_MODEL_FIELD_TYPES.map((type, i) => ( + + {type} + + ))} + + + ); +} + /** Arrange entities in a grid layout */ function calculateGridAutoLayout( entities: DataModelEntity[], @@ -578,8 +641,12 @@ export function DataModelDesigner({ const el = containerRef.current; if (!el) return; const handleKeyDown = (e: KeyboardEvent) => { - const tag = (e.target as HTMLElement).tagName; + const target = e.target as HTMLElement; + const tag = target.tagName; if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return; + // A field row's type picker (objectui#11865) is a `