@object-ui/plugin-detail 3.0.3 → 3.1.1

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 (141) hide show
  1. package/.turbo/turbo-build.log +45 -8
  2. package/CHANGELOG.md +11 -0
  3. package/dist/AddressField-B1iVr404.js +96 -0
  4. package/dist/AutoNumberField-BxnFqllo.js +8 -0
  5. package/dist/AvatarField-Duw4xOLZ.js +82 -0
  6. package/dist/BooleanField-CZ4axVeq.js +37 -0
  7. package/dist/CodeField-BSz-mk2v.js +21 -0
  8. package/dist/ColorField-B522ad8m.js +42 -0
  9. package/dist/CurrencyField-Cwr3_pow.js +43 -0
  10. package/dist/DateField-DCo6dxud.js +21 -0
  11. package/dist/DateTimeField-BWfBuANO.js +28 -0
  12. package/dist/EmailField-CpwbdVCU.js +31 -0
  13. package/dist/FileField-DVAUAJ8e.js +133 -0
  14. package/dist/FormulaField-CJkkwIK8.js +9 -0
  15. package/dist/GeolocationField-DNCKitgo.js +123 -0
  16. package/dist/GridField-DSblZNfp.js +30 -0
  17. package/dist/ImageField-DBAlnMon.js +90 -0
  18. package/dist/LocationField-DsHsXA6R.js +31 -0
  19. package/dist/LookupField-CsT0QQz2.js +96 -0
  20. package/dist/MasterDetailField-Db8b7Gqs.js +108 -0
  21. package/dist/NumberField-0IGp7lcA.js +26 -0
  22. package/dist/ObjectField-BLApgJtS.js +48 -0
  23. package/dist/PasswordField-pHKyNlmo.js +38 -0
  24. package/dist/PercentField-CwgKmlIb.js +63 -0
  25. package/dist/PhoneField-lKtbYOdN.js +31 -0
  26. package/dist/QRCodeField-BTTasT3w.js +77 -0
  27. package/dist/RatingField-De2X-l44.js +47 -0
  28. package/dist/RichTextField-B5QnvUOr.js +38 -0
  29. package/dist/SelectField-C9AZRHWu.js +26 -0
  30. package/dist/SignatureField-BgcEmYzd.js +85 -0
  31. package/dist/SliderField-BzrttVOY.js +30 -0
  32. package/dist/SummaryField-ugYPYxjP.js +9 -0
  33. package/dist/TextAreaField-DSE_CaU6.js +39 -0
  34. package/dist/TextField-DFQ4T9PR.js +32 -0
  35. package/dist/TimeField-F0cfmsps.js +21 -0
  36. package/dist/UrlField-DLXrFIH-.js +33 -0
  37. package/dist/UserField-PXMmxJY9.js +49 -0
  38. package/dist/VectorField-CKg9jdGa.js +25 -0
  39. package/dist/index-qQ1C-yUR.js +59976 -0
  40. package/dist/index.js +32 -55026
  41. package/dist/index.umd.cjs +41 -30
  42. package/dist/plugin-detail.css +1 -1
  43. package/dist/src/ActivityTimeline.d.ts +20 -0
  44. package/dist/src/ActivityTimeline.d.ts.map +1 -0
  45. package/dist/src/CommentAttachment.d.ts +25 -0
  46. package/dist/src/CommentAttachment.d.ts.map +1 -0
  47. package/dist/src/CommentInput.d.ts +24 -0
  48. package/dist/src/CommentInput.d.ts.map +1 -0
  49. package/dist/src/DetailSection.d.ts +8 -0
  50. package/dist/src/DetailSection.d.ts.map +1 -1
  51. package/dist/src/DetailView.d.ts +4 -0
  52. package/dist/src/DetailView.d.ts.map +1 -1
  53. package/dist/src/DetailView.stories.d.ts +8 -0
  54. package/dist/src/DetailView.stories.d.ts.map +1 -1
  55. package/dist/src/DiffView.d.ts +24 -0
  56. package/dist/src/DiffView.d.ts.map +1 -0
  57. package/dist/src/FieldChangeItem.d.ts +21 -0
  58. package/dist/src/FieldChangeItem.d.ts.map +1 -0
  59. package/dist/src/HeaderHighlight.d.ts +18 -0
  60. package/dist/src/HeaderHighlight.d.ts.map +1 -0
  61. package/dist/src/InlineCreateRelated.d.ts +32 -0
  62. package/dist/src/InlineCreateRelated.d.ts.map +1 -0
  63. package/dist/src/MentionAutocomplete.d.ts +43 -0
  64. package/dist/src/MentionAutocomplete.d.ts.map +1 -0
  65. package/dist/src/PointInTimeRestore.d.ts +28 -0
  66. package/dist/src/PointInTimeRestore.d.ts.map +1 -0
  67. package/dist/src/ReactionPicker.d.ts +25 -0
  68. package/dist/src/ReactionPicker.d.ts.map +1 -0
  69. package/dist/src/RecordActivityTimeline.d.ts +49 -0
  70. package/dist/src/RecordActivityTimeline.d.ts.map +1 -0
  71. package/dist/src/RecordChatterPanel.d.ts +48 -0
  72. package/dist/src/RecordChatterPanel.d.ts.map +1 -0
  73. package/dist/src/RecordComments.d.ts +20 -0
  74. package/dist/src/RecordComments.d.ts.map +1 -0
  75. package/dist/src/RecordNavigationEnhanced.d.ts +18 -0
  76. package/dist/src/RecordNavigationEnhanced.d.ts.map +1 -0
  77. package/dist/src/RelatedList.d.ts +20 -0
  78. package/dist/src/RelatedList.d.ts.map +1 -1
  79. package/dist/src/RelationshipGraph.d.ts +23 -0
  80. package/dist/src/RelationshipGraph.d.ts.map +1 -0
  81. package/dist/src/RichTextCommentInput.d.ts +24 -0
  82. package/dist/src/RichTextCommentInput.d.ts.map +1 -0
  83. package/dist/src/SectionGroup.d.ts +21 -0
  84. package/dist/src/SectionGroup.d.ts.map +1 -0
  85. package/dist/src/SubscriptionToggle.d.ts +22 -0
  86. package/dist/src/SubscriptionToggle.d.ts.map +1 -0
  87. package/dist/src/ThreadedReplies.d.ts +26 -0
  88. package/dist/src/ThreadedReplies.d.ts.map +1 -0
  89. package/dist/src/autoLayout.d.ts +34 -0
  90. package/dist/src/autoLayout.d.ts.map +1 -0
  91. package/dist/src/index.d.ts +40 -0
  92. package/dist/src/index.d.ts.map +1 -1
  93. package/dist/src/useDetailTranslation.d.ts +34 -0
  94. package/dist/src/useDetailTranslation.d.ts.map +1 -0
  95. package/package.json +8 -7
  96. package/src/ActivityTimeline.tsx +184 -0
  97. package/src/CommentAttachment.tsx +192 -0
  98. package/src/CommentInput.tsx +81 -0
  99. package/src/DetailSection.tsx +81 -10
  100. package/src/DetailView.stories.tsx +76 -0
  101. package/src/DetailView.tsx +519 -66
  102. package/src/DiffView.tsx +231 -0
  103. package/src/FieldChangeItem.tsx +46 -0
  104. package/src/HeaderHighlight.tsx +67 -0
  105. package/src/InlineCreateRelated.tsx +291 -0
  106. package/src/MentionAutocomplete.tsx +123 -0
  107. package/src/PointInTimeRestore.tsx +261 -0
  108. package/src/ReactionPicker.tsx +106 -0
  109. package/src/RecordActivityTimeline.tsx +429 -0
  110. package/src/RecordChatterPanel.tsx +202 -0
  111. package/src/RecordComments.tsx +215 -0
  112. package/src/RecordNavigationEnhanced.tsx +211 -0
  113. package/src/RelatedList.tsx +314 -19
  114. package/src/RelationshipGraph.tsx +286 -0
  115. package/src/RichTextCommentInput.tsx +348 -0
  116. package/src/SectionGroup.tsx +101 -0
  117. package/src/SubscriptionToggle.tsx +60 -0
  118. package/src/ThreadedReplies.tsx +161 -0
  119. package/src/__tests__/ActivityTimeline.test.tsx +119 -0
  120. package/src/__tests__/ActivityTimelineFiltering.test.tsx +143 -0
  121. package/src/__tests__/CommentInput.test.tsx +57 -0
  122. package/src/__tests__/DetailSection.test.tsx +320 -0
  123. package/src/__tests__/DetailView.test.tsx +415 -1
  124. package/src/__tests__/FieldChangeItem.test.tsx +119 -0
  125. package/src/__tests__/HeaderHighlight.test.tsx +68 -0
  126. package/src/__tests__/MentionAutocomplete.test.tsx +97 -0
  127. package/src/__tests__/ReactionPicker.test.tsx +113 -0
  128. package/src/__tests__/RecordActivityTimeline.test.tsx +395 -0
  129. package/src/__tests__/RecordChatterPanel.test.tsx +227 -0
  130. package/src/__tests__/RecordComments.test.tsx +96 -0
  131. package/src/__tests__/RecordCommentsPinSearch.test.tsx +133 -0
  132. package/src/__tests__/RelatedList.test.tsx +160 -0
  133. package/src/__tests__/SectionGroup.test.tsx +101 -0
  134. package/src/__tests__/SubscriptionToggle.test.tsx +84 -0
  135. package/src/__tests__/ThreadedReplies.test.tsx +212 -0
  136. package/src/__tests__/autoLayout.test.ts +184 -0
  137. package/src/__tests__/phase12-features.test.tsx +583 -0
  138. package/src/__tests__/roadmap-features.test.tsx +478 -0
  139. package/src/autoLayout.ts +111 -0
  140. package/src/index.tsx +50 -0
  141. package/src/useDetailTranslation.ts +114 -0
@@ -25,21 +25,39 @@ import {
25
25
  } from '@object-ui/components';
26
26
  import { ChevronDown, ChevronRight, Copy, Check } from 'lucide-react';
27
27
  import { SchemaRenderer } from '@object-ui/react';
28
- import type { DetailViewSection as DetailViewSectionType } from '@object-ui/types';
28
+ import { getCellRenderer } from '@object-ui/fields';
29
+ import type { DetailViewSection as DetailViewSectionType, DetailViewField, FieldMetadata } from '@object-ui/types';
30
+ import { applyDetailAutoLayout } from './autoLayout';
31
+ import { useDetailTranslation } from './useDetailTranslation';
32
+ import { useSafeFieldLabel } from '@object-ui/react';
29
33
 
30
34
  export interface DetailSectionProps {
31
35
  section: DetailViewSectionType;
32
36
  data?: any;
33
37
  className?: string;
38
+ /** Object schema from DataSource for field type enrichment */
39
+ objectSchema?: any;
40
+ /** Object name for i18n field label resolution */
41
+ objectName?: string;
42
+ /** Whether inline editing is active */
43
+ isEditing?: boolean;
44
+ /** Callback when a field value changes during inline editing */
45
+ onFieldChange?: (field: string, value: any) => void;
34
46
  }
35
47
 
36
48
  export const DetailSection: React.FC<DetailSectionProps> = ({
37
49
  section,
38
50
  data,
39
51
  className,
52
+ objectSchema,
53
+ objectName,
54
+ isEditing = false,
55
+ onFieldChange,
40
56
  }) => {
41
57
  const [isCollapsed, setIsCollapsed] = React.useState(section.defaultCollapsed ?? false);
42
58
  const [copiedField, setCopiedField] = React.useState<string | null>(null);
59
+ const { t } = useDetailTranslation();
60
+ const { fieldLabel } = useSafeFieldLabel();
43
61
 
44
62
  const handleCopyField = React.useCallback((fieldName: string, value: any) => {
45
63
  const textValue = value !== null && value !== undefined ? String(value) : '';
@@ -49,7 +67,7 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
49
67
  });
50
68
  }, []);
51
69
 
52
- const renderField = (field: any) => {
70
+ const renderField = (field: DetailViewField) => {
53
71
  const value = data?.[field.name] ?? field.value;
54
72
 
55
73
  // If custom renderer provided
@@ -65,7 +83,32 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
65
83
  field.span === 5 ? 'col-span-5' :
66
84
  field.span === 6 ? 'col-span-6' : '';
67
85
 
68
- const displayValue = value !== null && value !== undefined ? String(value) : '-';
86
+ const displayValue = (() => {
87
+ if (value === null || value === undefined) return <span className="text-muted-foreground/50 text-xs italic">—</span>;
88
+ // Enrich field with objectSchema metadata — merge missing properties
89
+ // even when field.type is explicitly set (e.g., type: 'lookup' without reference_to)
90
+ const objectDefField = objectSchema?.fields?.[field.name];
91
+ const resolvedType = field.type || objectDefField?.type;
92
+ const enrichedField: Record<string, any> = { ...field };
93
+ if (objectDefField) {
94
+ if (!field.type && objectDefField.type) enrichedField.type = objectDefField.type;
95
+ if (objectDefField.options && !enrichedField.options) enrichedField.options = objectDefField.options;
96
+ if (objectDefField.currency && !enrichedField.currency) enrichedField.currency = objectDefField.currency;
97
+ if (objectDefField.precision !== undefined && enrichedField.precision === undefined) enrichedField.precision = objectDefField.precision;
98
+ if (objectDefField.format && !enrichedField.format) enrichedField.format = objectDefField.format;
99
+ const refTarget = objectDefField.reference_to || objectDefField.reference;
100
+ if (refTarget && !enrichedField.reference_to) enrichedField.reference_to = refTarget;
101
+ if (objectDefField.reference_field && !enrichedField.reference_field) enrichedField.reference_field = objectDefField.reference_field;
102
+ }
103
+ // Use type-aware cell renderer when field type is available (explicit or enriched)
104
+ if (resolvedType) {
105
+ const CellRenderer = getCellRenderer(resolvedType);
106
+ if (CellRenderer) {
107
+ return <CellRenderer value={value} field={enrichedField as unknown as FieldMetadata} />;
108
+ }
109
+ }
110
+ return String(value);
111
+ })();
69
112
  const canCopy = value !== null && value !== undefined && value !== '';
70
113
  const isCopied = copiedField === field.name;
71
114
 
@@ -73,8 +116,18 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
73
116
  return (
74
117
  <div key={field.name} className={cn("space-y-1.5 group", spanClass)}>
75
118
  <div className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
76
- {field.label || field.name}
119
+ {fieldLabel(objectName || '', field.name, field.label || field.name)}
77
120
  </div>
121
+ {isEditing && !field.readonly ? (
122
+ <div className="min-h-[44px] sm:min-h-0">
123
+ <input
124
+ type={field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'}
125
+ className="w-full px-2 py-1.5 text-sm border rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring"
126
+ value={value != null ? String(value) : ''}
127
+ onChange={(e) => onFieldChange?.(field.name, e.target.value)}
128
+ />
129
+ </div>
130
+ ) : (
78
131
  <div
79
132
  className={cn(
80
133
  "flex items-start justify-between gap-2 min-h-[44px] sm:min-h-0 rounded-md",
@@ -114,27 +167,45 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
114
167
  </Button>
115
168
  </TooltipTrigger>
116
169
  <TooltipContent>
117
- {isCopied ? 'Copied!' : 'Copy to clipboard'}
170
+ {isCopied ? t('detail.copied') : t('detail.copyToClipboard')}
118
171
  </TooltipContent>
119
172
  </Tooltip>
120
173
  </TooltipProvider>
121
174
  )}
122
175
  </div>
176
+ )}
123
177
  </div>
124
178
  );
125
179
  };
126
180
 
181
+ // Filter out empty fields when hideEmpty is set
182
+ const visibleFields = section.hideEmpty
183
+ ? section.fields.filter((field) => {
184
+ const value = data?.[field.name] ?? field.value;
185
+ return value !== null && value !== undefined && value !== '';
186
+ })
187
+ : section.fields;
188
+
189
+ // Hide entire section when all fields are empty
190
+ if (visibleFields.length === 0) return null;
191
+
192
+ // Apply auto-layout: infer columns and auto-span wide fields
193
+ const { fields: layoutFields, columns: effectiveColumns } = applyDetailAutoLayout(
194
+ visibleFields,
195
+ section.columns
196
+ );
197
+
127
198
  const content = (
128
199
  <div
129
200
  className={cn(
130
201
  "grid gap-3 sm:gap-4",
131
- section.columns === 1 ? "grid-cols-1" :
132
- section.columns === 2 ? "grid-cols-1 sm:grid-cols-2" :
133
- section.columns === 3 ? "grid-cols-1 sm:grid-cols-2 md:grid-cols-3" :
134
- "grid-cols-1 sm:grid-cols-2 md:grid-cols-3"
202
+ effectiveColumns === 1 ? "grid-cols-1" :
203
+ effectiveColumns === 2 ? "grid-cols-1 md:grid-cols-2" :
204
+ effectiveColumns === 3 ? "grid-cols-1 md:grid-cols-2 lg:grid-cols-3" :
205
+ "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
135
206
  )}
136
207
  >
137
- {section.fields.map(renderField)}
208
+ {layoutFields.map(renderField)}
138
209
  </div>
139
210
  );
140
211
 
@@ -256,3 +256,79 @@ export const WithFieldCounts: Story = {
256
256
  ],
257
257
  } as any,
258
258
  };
259
+
260
+ /**
261
+ * Primary Field + Summary Badges — record name as header, key attributes as badges
262
+ */
263
+ export const PrimaryFieldWithBadges: Story = {
264
+ render: renderStory,
265
+ args: {
266
+ type: 'detail-view',
267
+ title: 'Contact',
268
+ primaryField: 'name',
269
+ summaryFields: ['status', 'department'],
270
+ objectName: 'Contact',
271
+ resourceId: 'CNT-042',
272
+ showBack: true,
273
+ showEdit: true,
274
+ data: {
275
+ name: 'Sarah Johnson',
276
+ email: 'sarah@example.com',
277
+ phone: '+1 (555) 123-4567',
278
+ status: 'Active',
279
+ department: 'Engineering',
280
+ title: 'Senior Engineer',
281
+ },
282
+ fields: [
283
+ { name: 'email', label: 'Email' },
284
+ { name: 'phone', label: 'Phone' },
285
+ { name: 'title', label: 'Job Title' },
286
+ { name: 'status', label: 'Status' },
287
+ { name: 'department', label: 'Department' },
288
+ ],
289
+ } as any,
290
+ };
291
+
292
+ /**
293
+ * Hide Empty Fields — sections with hideEmpty filter out null/undefined/empty fields
294
+ */
295
+ export const HideEmptyFields: Story = {
296
+ render: renderStory,
297
+ args: {
298
+ type: 'detail-view',
299
+ title: 'Contact',
300
+ primaryField: 'name',
301
+ showBack: true,
302
+ data: {
303
+ name: 'Mike Ross',
304
+ email: 'mike@techcorp.io',
305
+ phone: null,
306
+ department: '',
307
+ title: 'VP Engineering',
308
+ website: undefined,
309
+ linkedin: 'linkedin.com/in/mikeross',
310
+ },
311
+ sections: [
312
+ {
313
+ title: 'Contact Info',
314
+ hideEmpty: true,
315
+ fields: [
316
+ { name: 'email', label: 'Email' },
317
+ { name: 'phone', label: 'Phone' },
318
+ { name: 'title', label: 'Job Title' },
319
+ { name: 'department', label: 'Department' },
320
+ { name: 'website', label: 'Website' },
321
+ { name: 'linkedin', label: 'LinkedIn' },
322
+ ],
323
+ },
324
+ {
325
+ title: 'Empty Section (hidden)',
326
+ hideEmpty: true,
327
+ fields: [
328
+ { name: 'fax', label: 'Fax' },
329
+ { name: 'pager', label: 'Pager' },
330
+ ],
331
+ },
332
+ ],
333
+ } as any,
334
+ };