@object-ui/plugin-detail 3.0.2 → 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.
- package/.turbo/turbo-build.log +45 -8
- package/CHANGELOG.md +9 -0
- package/dist/AddressField-C07oUOY6.js +96 -0
- package/dist/AutoNumberField-BxnFqllo.js +8 -0
- package/dist/AvatarField-VThNABzo.js +82 -0
- package/dist/BooleanField-CGHKBzAi.js +37 -0
- package/dist/CodeField-Co_muhRR.js +21 -0
- package/dist/ColorField-DLid_tFz.js +42 -0
- package/dist/CurrencyField-Bw-LqANM.js +43 -0
- package/dist/DateField-BNHAzMB2.js +21 -0
- package/dist/DateTimeField-DjAyn_DQ.js +28 -0
- package/dist/EmailField-xoNcSppb.js +31 -0
- package/dist/FileField-DbNJwjU2.js +133 -0
- package/dist/FormulaField-CJkkwIK8.js +9 -0
- package/dist/GeolocationField-C1AnS6VV.js +123 -0
- package/dist/GridField-DATAHIKf.js +30 -0
- package/dist/ImageField-CEKJpyJp.js +90 -0
- package/dist/LocationField-jDWXjlpx.js +31 -0
- package/dist/LookupField-DQ08L9UQ.js +96 -0
- package/dist/MasterDetailField-Dbk529Ea.js +108 -0
- package/dist/NumberField-BVroN9aV.js +26 -0
- package/dist/ObjectField-CT3l_IHW.js +48 -0
- package/dist/PasswordField-DweVLEE0.js +38 -0
- package/dist/PercentField-ZpWUK97K.js +63 -0
- package/dist/PhoneField-mw-9fqZ_.js +31 -0
- package/dist/QRCodeField-Cbb9ck59.js +77 -0
- package/dist/RatingField-CSqgLS6t.js +47 -0
- package/dist/RichTextField-BpfBOd99.js +38 -0
- package/dist/SelectField-B9Ei-5jl.js +26 -0
- package/dist/SignatureField-DgGpHnQ8.js +85 -0
- package/dist/SliderField-C6HvOHd8.js +30 -0
- package/dist/SummaryField-ugYPYxjP.js +9 -0
- package/dist/TextAreaField-BK3RgzY3.js +39 -0
- package/dist/TextField-Bvzx3atT.js +32 -0
- package/dist/TimeField-Cuz9-Uai.js +21 -0
- package/dist/UrlField-B6XHTV73.js +33 -0
- package/dist/UserField-ooTul2d6.js +49 -0
- package/dist/VectorField-CKg9jdGa.js +25 -0
- package/dist/index-CnlyRfY_.js +59461 -0
- package/dist/index.js +30 -55026
- package/dist/index.umd.cjs +41 -30
- package/dist/plugin-detail.css +1 -1
- package/dist/src/ActivityTimeline.d.ts +20 -0
- package/dist/src/ActivityTimeline.d.ts.map +1 -0
- package/dist/src/CommentAttachment.d.ts +25 -0
- package/dist/src/CommentAttachment.d.ts.map +1 -0
- package/dist/src/CommentInput.d.ts +24 -0
- package/dist/src/CommentInput.d.ts.map +1 -0
- package/dist/src/DetailSection.d.ts +6 -0
- package/dist/src/DetailSection.d.ts.map +1 -1
- package/dist/src/DetailView.d.ts +4 -0
- package/dist/src/DetailView.d.ts.map +1 -1
- package/dist/src/DetailView.stories.d.ts +8 -0
- package/dist/src/DetailView.stories.d.ts.map +1 -1
- package/dist/src/DiffView.d.ts +24 -0
- package/dist/src/DiffView.d.ts.map +1 -0
- package/dist/src/FieldChangeItem.d.ts +21 -0
- package/dist/src/FieldChangeItem.d.ts.map +1 -0
- package/dist/src/InlineCreateRelated.d.ts +32 -0
- package/dist/src/InlineCreateRelated.d.ts.map +1 -0
- package/dist/src/MentionAutocomplete.d.ts +43 -0
- package/dist/src/MentionAutocomplete.d.ts.map +1 -0
- package/dist/src/PointInTimeRestore.d.ts +28 -0
- package/dist/src/PointInTimeRestore.d.ts.map +1 -0
- package/dist/src/ReactionPicker.d.ts +25 -0
- package/dist/src/ReactionPicker.d.ts.map +1 -0
- package/dist/src/RecordActivityTimeline.d.ts +49 -0
- package/dist/src/RecordActivityTimeline.d.ts.map +1 -0
- package/dist/src/RecordChatterPanel.d.ts +48 -0
- package/dist/src/RecordChatterPanel.d.ts.map +1 -0
- package/dist/src/RecordComments.d.ts +20 -0
- package/dist/src/RecordComments.d.ts.map +1 -0
- package/dist/src/RecordNavigationEnhanced.d.ts +18 -0
- package/dist/src/RecordNavigationEnhanced.d.ts.map +1 -0
- package/dist/src/RelatedList.d.ts +4 -0
- package/dist/src/RelatedList.d.ts.map +1 -1
- package/dist/src/RelationshipGraph.d.ts +23 -0
- package/dist/src/RelationshipGraph.d.ts.map +1 -0
- package/dist/src/RichTextCommentInput.d.ts +24 -0
- package/dist/src/RichTextCommentInput.d.ts.map +1 -0
- package/dist/src/SubscriptionToggle.d.ts +22 -0
- package/dist/src/SubscriptionToggle.d.ts.map +1 -0
- package/dist/src/ThreadedReplies.d.ts +26 -0
- package/dist/src/ThreadedReplies.d.ts.map +1 -0
- package/dist/src/autoLayout.d.ts +34 -0
- package/dist/src/autoLayout.d.ts.map +1 -0
- package/dist/src/index.d.ts +36 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/useDetailTranslation.d.ts +34 -0
- package/dist/src/useDetailTranslation.d.ts.map +1 -0
- package/package.json +8 -7
- package/src/ActivityTimeline.tsx +184 -0
- package/src/CommentAttachment.tsx +192 -0
- package/src/CommentInput.tsx +81 -0
- package/src/DetailSection.tsx +74 -9
- package/src/DetailView.stories.tsx +76 -0
- package/src/DetailView.tsx +270 -27
- package/src/DiffView.tsx +231 -0
- package/src/FieldChangeItem.tsx +46 -0
- package/src/InlineCreateRelated.tsx +291 -0
- package/src/MentionAutocomplete.tsx +123 -0
- package/src/PointInTimeRestore.tsx +261 -0
- package/src/ReactionPicker.tsx +106 -0
- package/src/RecordActivityTimeline.tsx +429 -0
- package/src/RecordChatterPanel.tsx +202 -0
- package/src/RecordComments.tsx +215 -0
- package/src/RecordNavigationEnhanced.tsx +211 -0
- package/src/RelatedList.tsx +37 -8
- package/src/RelationshipGraph.tsx +286 -0
- package/src/RichTextCommentInput.tsx +348 -0
- package/src/SubscriptionToggle.tsx +60 -0
- package/src/ThreadedReplies.tsx +161 -0
- package/src/__tests__/ActivityTimeline.test.tsx +119 -0
- package/src/__tests__/ActivityTimelineFiltering.test.tsx +143 -0
- package/src/__tests__/CommentInput.test.tsx +57 -0
- package/src/__tests__/DetailSection.test.tsx +320 -0
- package/src/__tests__/DetailView.test.tsx +415 -1
- package/src/__tests__/FieldChangeItem.test.tsx +119 -0
- package/src/__tests__/MentionAutocomplete.test.tsx +97 -0
- package/src/__tests__/ReactionPicker.test.tsx +113 -0
- package/src/__tests__/RecordActivityTimeline.test.tsx +395 -0
- package/src/__tests__/RecordChatterPanel.test.tsx +227 -0
- package/src/__tests__/RecordComments.test.tsx +96 -0
- package/src/__tests__/RecordCommentsPinSearch.test.tsx +133 -0
- package/src/__tests__/RelatedList.test.tsx +66 -0
- package/src/__tests__/SubscriptionToggle.test.tsx +84 -0
- package/src/__tests__/ThreadedReplies.test.tsx +212 -0
- package/src/__tests__/autoLayout.test.ts +184 -0
- package/src/__tests__/phase12-features.test.tsx +583 -0
- package/src/autoLayout.ts +111 -0
- package/src/index.tsx +46 -0
- package/src/useDetailTranslation.ts +103 -0
package/src/DetailSection.tsx
CHANGED
|
@@ -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
|
|
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:
|
|
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 =
|
|
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 ? '
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"grid-cols-1
|
|
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
|
-
{
|
|
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
|
+
};
|