@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.
- package/.turbo/turbo-build.log +45 -8
- package/CHANGELOG.md +11 -0
- package/dist/AddressField-B1iVr404.js +96 -0
- package/dist/AutoNumberField-BxnFqllo.js +8 -0
- package/dist/AvatarField-Duw4xOLZ.js +82 -0
- package/dist/BooleanField-CZ4axVeq.js +37 -0
- package/dist/CodeField-BSz-mk2v.js +21 -0
- package/dist/ColorField-B522ad8m.js +42 -0
- package/dist/CurrencyField-Cwr3_pow.js +43 -0
- package/dist/DateField-DCo6dxud.js +21 -0
- package/dist/DateTimeField-BWfBuANO.js +28 -0
- package/dist/EmailField-CpwbdVCU.js +31 -0
- package/dist/FileField-DVAUAJ8e.js +133 -0
- package/dist/FormulaField-CJkkwIK8.js +9 -0
- package/dist/GeolocationField-DNCKitgo.js +123 -0
- package/dist/GridField-DSblZNfp.js +30 -0
- package/dist/ImageField-DBAlnMon.js +90 -0
- package/dist/LocationField-DsHsXA6R.js +31 -0
- package/dist/LookupField-CsT0QQz2.js +96 -0
- package/dist/MasterDetailField-Db8b7Gqs.js +108 -0
- package/dist/NumberField-0IGp7lcA.js +26 -0
- package/dist/ObjectField-BLApgJtS.js +48 -0
- package/dist/PasswordField-pHKyNlmo.js +38 -0
- package/dist/PercentField-CwgKmlIb.js +63 -0
- package/dist/PhoneField-lKtbYOdN.js +31 -0
- package/dist/QRCodeField-BTTasT3w.js +77 -0
- package/dist/RatingField-De2X-l44.js +47 -0
- package/dist/RichTextField-B5QnvUOr.js +38 -0
- package/dist/SelectField-C9AZRHWu.js +26 -0
- package/dist/SignatureField-BgcEmYzd.js +85 -0
- package/dist/SliderField-BzrttVOY.js +30 -0
- package/dist/SummaryField-ugYPYxjP.js +9 -0
- package/dist/TextAreaField-DSE_CaU6.js +39 -0
- package/dist/TextField-DFQ4T9PR.js +32 -0
- package/dist/TimeField-F0cfmsps.js +21 -0
- package/dist/UrlField-DLXrFIH-.js +33 -0
- package/dist/UserField-PXMmxJY9.js +49 -0
- package/dist/VectorField-CKg9jdGa.js +25 -0
- package/dist/index-qQ1C-yUR.js +59976 -0
- package/dist/index.js +32 -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 +8 -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/HeaderHighlight.d.ts +18 -0
- package/dist/src/HeaderHighlight.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 +20 -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/SectionGroup.d.ts +21 -0
- package/dist/src/SectionGroup.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 +40 -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 +81 -10
- package/src/DetailView.stories.tsx +76 -0
- package/src/DetailView.tsx +519 -66
- package/src/DiffView.tsx +231 -0
- package/src/FieldChangeItem.tsx +46 -0
- package/src/HeaderHighlight.tsx +67 -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 +314 -19
- package/src/RelationshipGraph.tsx +286 -0
- package/src/RichTextCommentInput.tsx +348 -0
- package/src/SectionGroup.tsx +101 -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__/HeaderHighlight.test.tsx +68 -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 +160 -0
- package/src/__tests__/SectionGroup.test.tsx +101 -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/__tests__/roadmap-features.test.tsx +478 -0
- package/src/autoLayout.ts +111 -0
- package/src/index.tsx +50 -0
- package/src/useDetailTranslation.ts +114 -0
package/src/DetailSection.tsx
CHANGED
|
@@ -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
|
|
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:
|
|
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 =
|
|
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 ? '
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"grid-cols-1
|
|
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
|
-
{
|
|
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
|
+
};
|