@object-ui/plugin-detail 3.0.3 → 3.1.0

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 (131) hide show
  1. package/.turbo/turbo-build.log +45 -8
  2. package/dist/AddressField-C07oUOY6.js +96 -0
  3. package/dist/AutoNumberField-BxnFqllo.js +8 -0
  4. package/dist/AvatarField-VThNABzo.js +82 -0
  5. package/dist/BooleanField-CGHKBzAi.js +37 -0
  6. package/dist/CodeField-Co_muhRR.js +21 -0
  7. package/dist/ColorField-DLid_tFz.js +42 -0
  8. package/dist/CurrencyField-Bw-LqANM.js +43 -0
  9. package/dist/DateField-BNHAzMB2.js +21 -0
  10. package/dist/DateTimeField-DjAyn_DQ.js +28 -0
  11. package/dist/EmailField-xoNcSppb.js +31 -0
  12. package/dist/FileField-DbNJwjU2.js +133 -0
  13. package/dist/FormulaField-CJkkwIK8.js +9 -0
  14. package/dist/GeolocationField-C1AnS6VV.js +123 -0
  15. package/dist/GridField-DATAHIKf.js +30 -0
  16. package/dist/ImageField-CEKJpyJp.js +90 -0
  17. package/dist/LocationField-jDWXjlpx.js +31 -0
  18. package/dist/LookupField-DQ08L9UQ.js +96 -0
  19. package/dist/MasterDetailField-Dbk529Ea.js +108 -0
  20. package/dist/NumberField-BVroN9aV.js +26 -0
  21. package/dist/ObjectField-CT3l_IHW.js +48 -0
  22. package/dist/PasswordField-DweVLEE0.js +38 -0
  23. package/dist/PercentField-ZpWUK97K.js +63 -0
  24. package/dist/PhoneField-mw-9fqZ_.js +31 -0
  25. package/dist/QRCodeField-Cbb9ck59.js +77 -0
  26. package/dist/RatingField-CSqgLS6t.js +47 -0
  27. package/dist/RichTextField-BpfBOd99.js +38 -0
  28. package/dist/SelectField-B9Ei-5jl.js +26 -0
  29. package/dist/SignatureField-DgGpHnQ8.js +85 -0
  30. package/dist/SliderField-C6HvOHd8.js +30 -0
  31. package/dist/SummaryField-ugYPYxjP.js +9 -0
  32. package/dist/TextAreaField-BK3RgzY3.js +39 -0
  33. package/dist/TextField-Bvzx3atT.js +32 -0
  34. package/dist/TimeField-Cuz9-Uai.js +21 -0
  35. package/dist/UrlField-B6XHTV73.js +33 -0
  36. package/dist/UserField-ooTul2d6.js +49 -0
  37. package/dist/VectorField-CKg9jdGa.js +25 -0
  38. package/dist/index-CnlyRfY_.js +59461 -0
  39. package/dist/index.js +30 -55026
  40. package/dist/index.umd.cjs +41 -30
  41. package/dist/plugin-detail.css +1 -1
  42. package/dist/src/ActivityTimeline.d.ts +20 -0
  43. package/dist/src/ActivityTimeline.d.ts.map +1 -0
  44. package/dist/src/CommentAttachment.d.ts +25 -0
  45. package/dist/src/CommentAttachment.d.ts.map +1 -0
  46. package/dist/src/CommentInput.d.ts +24 -0
  47. package/dist/src/CommentInput.d.ts.map +1 -0
  48. package/dist/src/DetailSection.d.ts +6 -0
  49. package/dist/src/DetailSection.d.ts.map +1 -1
  50. package/dist/src/DetailView.d.ts +4 -0
  51. package/dist/src/DetailView.d.ts.map +1 -1
  52. package/dist/src/DetailView.stories.d.ts +8 -0
  53. package/dist/src/DetailView.stories.d.ts.map +1 -1
  54. package/dist/src/DiffView.d.ts +24 -0
  55. package/dist/src/DiffView.d.ts.map +1 -0
  56. package/dist/src/FieldChangeItem.d.ts +21 -0
  57. package/dist/src/FieldChangeItem.d.ts.map +1 -0
  58. package/dist/src/InlineCreateRelated.d.ts +32 -0
  59. package/dist/src/InlineCreateRelated.d.ts.map +1 -0
  60. package/dist/src/MentionAutocomplete.d.ts +43 -0
  61. package/dist/src/MentionAutocomplete.d.ts.map +1 -0
  62. package/dist/src/PointInTimeRestore.d.ts +28 -0
  63. package/dist/src/PointInTimeRestore.d.ts.map +1 -0
  64. package/dist/src/ReactionPicker.d.ts +25 -0
  65. package/dist/src/ReactionPicker.d.ts.map +1 -0
  66. package/dist/src/RecordActivityTimeline.d.ts +49 -0
  67. package/dist/src/RecordActivityTimeline.d.ts.map +1 -0
  68. package/dist/src/RecordChatterPanel.d.ts +48 -0
  69. package/dist/src/RecordChatterPanel.d.ts.map +1 -0
  70. package/dist/src/RecordComments.d.ts +20 -0
  71. package/dist/src/RecordComments.d.ts.map +1 -0
  72. package/dist/src/RecordNavigationEnhanced.d.ts +18 -0
  73. package/dist/src/RecordNavigationEnhanced.d.ts.map +1 -0
  74. package/dist/src/RelatedList.d.ts +4 -0
  75. package/dist/src/RelatedList.d.ts.map +1 -1
  76. package/dist/src/RelationshipGraph.d.ts +23 -0
  77. package/dist/src/RelationshipGraph.d.ts.map +1 -0
  78. package/dist/src/RichTextCommentInput.d.ts +24 -0
  79. package/dist/src/RichTextCommentInput.d.ts.map +1 -0
  80. package/dist/src/SubscriptionToggle.d.ts +22 -0
  81. package/dist/src/SubscriptionToggle.d.ts.map +1 -0
  82. package/dist/src/ThreadedReplies.d.ts +26 -0
  83. package/dist/src/ThreadedReplies.d.ts.map +1 -0
  84. package/dist/src/autoLayout.d.ts +34 -0
  85. package/dist/src/autoLayout.d.ts.map +1 -0
  86. package/dist/src/index.d.ts +36 -0
  87. package/dist/src/index.d.ts.map +1 -1
  88. package/dist/src/useDetailTranslation.d.ts +34 -0
  89. package/dist/src/useDetailTranslation.d.ts.map +1 -0
  90. package/package.json +8 -7
  91. package/src/ActivityTimeline.tsx +184 -0
  92. package/src/CommentAttachment.tsx +192 -0
  93. package/src/CommentInput.tsx +81 -0
  94. package/src/DetailSection.tsx +74 -9
  95. package/src/DetailView.stories.tsx +76 -0
  96. package/src/DetailView.tsx +270 -27
  97. package/src/DiffView.tsx +231 -0
  98. package/src/FieldChangeItem.tsx +46 -0
  99. package/src/InlineCreateRelated.tsx +291 -0
  100. package/src/MentionAutocomplete.tsx +123 -0
  101. package/src/PointInTimeRestore.tsx +261 -0
  102. package/src/ReactionPicker.tsx +106 -0
  103. package/src/RecordActivityTimeline.tsx +429 -0
  104. package/src/RecordChatterPanel.tsx +202 -0
  105. package/src/RecordComments.tsx +215 -0
  106. package/src/RecordNavigationEnhanced.tsx +211 -0
  107. package/src/RelatedList.tsx +37 -8
  108. package/src/RelationshipGraph.tsx +286 -0
  109. package/src/RichTextCommentInput.tsx +348 -0
  110. package/src/SubscriptionToggle.tsx +60 -0
  111. package/src/ThreadedReplies.tsx +161 -0
  112. package/src/__tests__/ActivityTimeline.test.tsx +119 -0
  113. package/src/__tests__/ActivityTimelineFiltering.test.tsx +143 -0
  114. package/src/__tests__/CommentInput.test.tsx +57 -0
  115. package/src/__tests__/DetailSection.test.tsx +320 -0
  116. package/src/__tests__/DetailView.test.tsx +415 -1
  117. package/src/__tests__/FieldChangeItem.test.tsx +119 -0
  118. package/src/__tests__/MentionAutocomplete.test.tsx +97 -0
  119. package/src/__tests__/ReactionPicker.test.tsx +113 -0
  120. package/src/__tests__/RecordActivityTimeline.test.tsx +395 -0
  121. package/src/__tests__/RecordChatterPanel.test.tsx +227 -0
  122. package/src/__tests__/RecordComments.test.tsx +96 -0
  123. package/src/__tests__/RecordCommentsPinSearch.test.tsx +133 -0
  124. package/src/__tests__/RelatedList.test.tsx +66 -0
  125. package/src/__tests__/SubscriptionToggle.test.tsx +84 -0
  126. package/src/__tests__/ThreadedReplies.test.tsx +212 -0
  127. package/src/__tests__/autoLayout.test.ts +184 -0
  128. package/src/__tests__/phase12-features.test.tsx +583 -0
  129. package/src/autoLayout.ts +111 -0
  130. package/src/index.tsx +46 -0
  131. package/src/useDetailTranslation.ts +103 -0
@@ -25,21 +25,34 @@ 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';
29
32
 
30
33
  export interface DetailSectionProps {
31
34
  section: DetailViewSectionType;
32
35
  data?: any;
33
36
  className?: string;
37
+ /** Object schema from DataSource for field type enrichment */
38
+ objectSchema?: any;
39
+ /** Whether inline editing is active */
40
+ isEditing?: boolean;
41
+ /** Callback when a field value changes during inline editing */
42
+ onFieldChange?: (field: string, value: any) => void;
34
43
  }
35
44
 
36
45
  export const DetailSection: React.FC<DetailSectionProps> = ({
37
46
  section,
38
47
  data,
39
48
  className,
49
+ objectSchema,
50
+ isEditing = false,
51
+ onFieldChange,
40
52
  }) => {
41
53
  const [isCollapsed, setIsCollapsed] = React.useState(section.defaultCollapsed ?? false);
42
54
  const [copiedField, setCopiedField] = React.useState<string | null>(null);
55
+ const { t } = useDetailTranslation();
43
56
 
44
57
  const handleCopyField = React.useCallback((fieldName: string, value: any) => {
45
58
  const textValue = value !== null && value !== undefined ? String(value) : '';
@@ -49,7 +62,7 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
49
62
  });
50
63
  }, []);
51
64
 
52
- const renderField = (field: any) => {
65
+ const renderField = (field: DetailViewField) => {
53
66
  const value = data?.[field.name] ?? field.value;
54
67
 
55
68
  // If custom renderer provided
@@ -65,7 +78,31 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
65
78
  field.span === 5 ? 'col-span-5' :
66
79
  field.span === 6 ? 'col-span-6' : '';
67
80
 
68
- const displayValue = value !== null && value !== undefined ? String(value) : '-';
81
+ const displayValue = (() => {
82
+ if (value === null || value === undefined) return <span className="text-muted-foreground/50 text-xs italic">—</span>;
83
+ // Enrich field with objectSchema metadata — merge missing properties
84
+ // even when field.type is explicitly set (e.g., type: 'lookup' without reference_to)
85
+ const objectDefField = objectSchema?.fields?.[field.name];
86
+ const resolvedType = field.type || objectDefField?.type;
87
+ const enrichedField: Record<string, any> = { ...field };
88
+ if (objectDefField) {
89
+ if (!field.type && objectDefField.type) enrichedField.type = objectDefField.type;
90
+ if (objectDefField.options && !enrichedField.options) enrichedField.options = objectDefField.options;
91
+ if (objectDefField.currency && !enrichedField.currency) enrichedField.currency = objectDefField.currency;
92
+ if (objectDefField.precision !== undefined && enrichedField.precision === undefined) enrichedField.precision = objectDefField.precision;
93
+ if (objectDefField.format && !enrichedField.format) enrichedField.format = objectDefField.format;
94
+ if (objectDefField.reference_to && !enrichedField.reference_to) enrichedField.reference_to = objectDefField.reference_to;
95
+ if (objectDefField.reference_field && !enrichedField.reference_field) enrichedField.reference_field = objectDefField.reference_field;
96
+ }
97
+ // Use type-aware cell renderer when field type is available (explicit or enriched)
98
+ if (resolvedType) {
99
+ const CellRenderer = getCellRenderer(resolvedType);
100
+ if (CellRenderer) {
101
+ return <CellRenderer value={value} field={enrichedField as unknown as FieldMetadata} />;
102
+ }
103
+ }
104
+ return String(value);
105
+ })();
69
106
  const canCopy = value !== null && value !== undefined && value !== '';
70
107
  const isCopied = copiedField === field.name;
71
108
 
@@ -75,6 +112,16 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
75
112
  <div className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
76
113
  {field.label || field.name}
77
114
  </div>
115
+ {isEditing && !field.readonly ? (
116
+ <div className="min-h-[44px] sm:min-h-0">
117
+ <input
118
+ type={field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'}
119
+ className="w-full px-2 py-1.5 text-sm border rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring"
120
+ value={value != null ? String(value) : ''}
121
+ onChange={(e) => onFieldChange?.(field.name, e.target.value)}
122
+ />
123
+ </div>
124
+ ) : (
78
125
  <div
79
126
  className={cn(
80
127
  "flex items-start justify-between gap-2 min-h-[44px] sm:min-h-0 rounded-md",
@@ -114,27 +161,45 @@ export const DetailSection: React.FC<DetailSectionProps> = ({
114
161
  </Button>
115
162
  </TooltipTrigger>
116
163
  <TooltipContent>
117
- {isCopied ? 'Copied!' : 'Copy to clipboard'}
164
+ {isCopied ? t('detail.copied') : t('detail.copyToClipboard')}
118
165
  </TooltipContent>
119
166
  </Tooltip>
120
167
  </TooltipProvider>
121
168
  )}
122
169
  </div>
170
+ )}
123
171
  </div>
124
172
  );
125
173
  };
126
174
 
175
+ // Filter out empty fields when hideEmpty is set
176
+ const visibleFields = section.hideEmpty
177
+ ? section.fields.filter((field) => {
178
+ const value = data?.[field.name] ?? field.value;
179
+ return value !== null && value !== undefined && value !== '';
180
+ })
181
+ : section.fields;
182
+
183
+ // Hide entire section when all fields are empty
184
+ if (visibleFields.length === 0) return null;
185
+
186
+ // Apply auto-layout: infer columns and auto-span wide fields
187
+ const { fields: layoutFields, columns: effectiveColumns } = applyDetailAutoLayout(
188
+ visibleFields,
189
+ section.columns
190
+ );
191
+
127
192
  const content = (
128
193
  <div
129
194
  className={cn(
130
195
  "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"
196
+ effectiveColumns === 1 ? "grid-cols-1" :
197
+ effectiveColumns === 2 ? "grid-cols-1 md:grid-cols-2" :
198
+ effectiveColumns === 3 ? "grid-cols-1 md:grid-cols-2 lg:grid-cols-3" :
199
+ "grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
135
200
  )}
136
201
  >
137
- {section.fields.map(renderField)}
202
+ {layoutFields.map(renderField)}
138
203
  </div>
139
204
  );
140
205
 
@@ -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
+ };