diff --git a/.changeset/11865-j-designer-forms-shared-select.md b/.changeset/11865-j-designer-forms-shared-select.md new file mode 100644 index 0000000000..fff82dc706 --- /dev/null +++ b/.changeset/11865-j-designer-forms-shared-select.md @@ -0,0 +1,13 @@ +--- +'@object-ui/plugin-designer': patch +--- + +The designer forms pick with the shared `Select`, the control the rest of the console picks with (objectui#11865, the designer forms' part of that card): `FieldDesigner`'s type filter, `DataModelDesigner`'s field-type picker on each field row, `BrandingEditor`'s font family and `AppCreationWizard`'s template. + +The four pickers were browser-native selects, so they looked and behaved differently from the console's other dropdowns. They now use the shared Radix `Select`: the same trigger, dropdown and keyboard behaviour. The type filter keeps its category headings (Text, Number, Date & Time, Choice, Relation, Advanced) as groups of the dropdown. + +What they write is unchanged. Each option gives the form the same value as before: "All Types" still clears the type filter, "Default (System)" still clears the branding's `fontFamily`, and "None" still sets the draft's `template` to an empty string. Re-picking the current option writes nothing. The font family and template pickers keep the accessible name their label gave the native select ("Font Family", "Template") and show read-only mode as disabled. The type filter stays enabled in read-only mode, as before, and a read-only data model still shows each field's type as text. On a data model entity card, a click on the type picker still does not select the card, and Delete or Escape on it still does not act on the canvas. + +One display change: a value none of a picker's options carries now shows as itself. The native select showed its first option instead (`text` for a field type, "Default (System)" for a font, "None" for a template), which is not what the form holds. + +**Clause-②: no.** No published face moves: the package entry exports the same names, the four components take the same props, and no i18n key is added. What moves is the four controls' own markup, described above. 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 `