@object-ui/plugin-detail 3.1.5 → 3.3.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/CHANGELOG.md +31 -0
- package/README.md +21 -1
- package/dist/AddressField-LgHnO2Lk.js +98 -0
- package/dist/AutoNumberField-xZCrU0eW.js +14 -0
- package/dist/{AvatarField-YGj51ozd.js → AvatarField-Dy2XGlPz.js} +16 -15
- package/dist/{BooleanField-CaA898Tk.js → BooleanField-C0Clfka5.js} +11 -10
- package/dist/CodeField-CHUa07B6.js +23 -0
- package/dist/ColorField-vxHqEhcS.js +38 -0
- package/dist/CurrencyField-DiWjYWDo.js +49 -0
- package/dist/DateField-DGaRPM4P.js +22 -0
- package/dist/DateTimeField-8QnpsI_h.js +30 -0
- package/dist/EmailField-CkVgMbpI.js +26 -0
- package/dist/FileField-5UPV7uek.js +149 -0
- package/dist/FormulaField-BUgt6-Pi.js +17 -0
- package/dist/GeolocationField-D9T_jgG6.js +118 -0
- package/dist/GridField-DE_HwiIN.js +49 -0
- package/dist/ImageField-Dswnqtzf.js +73 -0
- package/dist/LocationField-gjqbE6na.js +36 -0
- package/dist/LookupField-BcS3LRKc.js +901 -0
- package/dist/{MasterDetailField-I1A9oEGC.js → MasterDetailField-BF6_-X3A.js} +20 -19
- package/dist/NumberField-Dj2rYmrS.js +27 -0
- package/dist/ObjectField-BymIojwd.js +50 -0
- package/dist/{PasswordField-DBtluGJ1.js → PasswordField-ED_Xgqz-.js} +8 -7
- package/dist/PercentField-D-JKOxKC.js +61 -0
- package/dist/PhoneField-DSCaGYq7.js +26 -0
- package/dist/QRCodeField-CtcOUapi.js +73 -0
- package/dist/{RatingField-B_Mnr63i.js → RatingField-BDnyQFWy.js} +10 -9
- package/dist/RichTextField-CH6LVZQA.js +33 -0
- package/dist/SelectField-DE4dpkMV.js +36 -0
- package/dist/{SignatureField-CddhEK9u.js → SignatureField-B1wh3f5A.js} +18 -17
- package/dist/{SliderField-Df5hMzNc.js → SliderField-zoTCKh9n.js} +2 -1
- package/dist/SummaryField-BeBVT6VN.js +22 -0
- package/dist/TextAreaField-rfUGrRxh.js +37 -0
- package/dist/TextField-C_yM7ATQ.js +30 -0
- package/dist/TimeField-BcQmBZi9.js +22 -0
- package/dist/UrlField-BakaF6NI.js +31 -0
- package/dist/UserField-zS7y3eKb.js +76 -0
- package/dist/VectorField-CTZ4myDM.js +34 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1741 -1504
- package/dist/index.umd.cjs +43 -51
- package/dist/packages/plugin-detail/src/ActivityTimeline.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/CommentAttachment.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/CommentInput.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailSection.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailTabs.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailView.d.ts +47 -0
- package/dist/packages/plugin-detail/src/DetailView.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailView.stories.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DiffView.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/FieldChangeItem.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/HeaderHighlight.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/InlineCreateRelated.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/MentionAutocomplete.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/PointInTimeRestore.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/ReactionPicker.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordActivityTimeline.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordChatterPanel.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordComments.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordNavigationEnhanced.d.ts.map +1 -0
- package/dist/{src → packages/plugin-detail/src}/RelatedList.d.ts +8 -0
- package/dist/packages/plugin-detail/src/RelatedList.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RelationshipGraph.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RichTextCommentInput.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/SectionGroup.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/SubscriptionToggle.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/ThreadedReplies.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/autoLayout.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/index.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/useDetailTranslation.d.ts.map +1 -0
- package/dist/plugin-detail.css +1 -2
- package/dist/rolldown-runtime-DnwLefa7.js +23 -0
- package/dist/{src-CXr1-vVl.js → src-DyUKLvMN.js} +29788 -37711
- package/dist/useFieldTranslation-BRgjC1oq.js +9 -0
- package/package.json +34 -12
- package/.turbo/turbo-build.log +0 -61
- package/dist/AddressField-DBkEyMcG.js +0 -93
- package/dist/AutoNumberField-Baa191z-.js +0 -14
- package/dist/CodeField-BU51nl1L.js +0 -22
- package/dist/ColorField-Cnf6ZM7c.js +0 -37
- package/dist/CurrencyField-Wg-XOId2.js +0 -51
- package/dist/DateField-Cth1ky_m.js +0 -21
- package/dist/DateTimeField-B0m6FhHL.js +0 -32
- package/dist/EmailField-Do7qT_L_.js +0 -28
- package/dist/FileField-aRJAdbQb.js +0 -151
- package/dist/FormulaField-DTMkagFx.js +0 -14
- package/dist/GeolocationField-RqpHWTEv.js +0 -113
- package/dist/GridField-D4IH0cpo.js +0 -51
- package/dist/ImageField-BYCFajjr.js +0 -75
- package/dist/LocationField-Bi_ew9sd.js +0 -35
- package/dist/LookupField-BjwlDPtt.js +0 -902
- package/dist/NumberField-D_NucQlp.js +0 -26
- package/dist/ObjectField-CG-LaM65.js +0 -52
- package/dist/PercentField-B6sO_J3i.js +0 -63
- package/dist/PhoneField-CcQAWwR6.js +0 -28
- package/dist/QRCodeField-CEjWs-J5.js +0 -72
- package/dist/RichTextField-qOEJl5Ai.js +0 -32
- package/dist/SelectField-C8hWu3gm.js +0 -30
- package/dist/SummaryField-DgiFm-Cr.js +0 -19
- package/dist/TextAreaField-DuriTqsD.js +0 -36
- package/dist/TextField-CGNSl7RU.js +0 -29
- package/dist/TimeField-YO58ctFg.js +0 -21
- package/dist/UrlField-1-BMM1jn.js +0 -33
- package/dist/UserField-B6GqxP_S.js +0 -78
- package/dist/VectorField-BkEjbSt0.js +0 -36
- package/dist/src/ActivityTimeline.d.ts.map +0 -1
- package/dist/src/CommentAttachment.d.ts.map +0 -1
- package/dist/src/CommentInput.d.ts.map +0 -1
- package/dist/src/DetailSection.d.ts.map +0 -1
- package/dist/src/DetailTabs.d.ts.map +0 -1
- package/dist/src/DetailView.d.ts +0 -23
- package/dist/src/DetailView.d.ts.map +0 -1
- package/dist/src/DetailView.stories.d.ts.map +0 -1
- package/dist/src/DiffView.d.ts.map +0 -1
- package/dist/src/FieldChangeItem.d.ts.map +0 -1
- package/dist/src/HeaderHighlight.d.ts.map +0 -1
- package/dist/src/InlineCreateRelated.d.ts.map +0 -1
- package/dist/src/MentionAutocomplete.d.ts.map +0 -1
- package/dist/src/PointInTimeRestore.d.ts.map +0 -1
- package/dist/src/ReactionPicker.d.ts.map +0 -1
- package/dist/src/RecordActivityTimeline.d.ts.map +0 -1
- package/dist/src/RecordChatterPanel.d.ts.map +0 -1
- package/dist/src/RecordComments.d.ts.map +0 -1
- package/dist/src/RecordNavigationEnhanced.d.ts.map +0 -1
- package/dist/src/RelatedList.d.ts.map +0 -1
- package/dist/src/RelationshipGraph.d.ts.map +0 -1
- package/dist/src/RichTextCommentInput.d.ts.map +0 -1
- package/dist/src/SectionGroup.d.ts.map +0 -1
- package/dist/src/SubscriptionToggle.d.ts.map +0 -1
- package/dist/src/ThreadedReplies.d.ts.map +0 -1
- package/dist/src/autoLayout.d.ts.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/useDetailTranslation.d.ts.map +0 -1
- package/src/ActivityTimeline.tsx +0 -184
- package/src/CommentAttachment.tsx +0 -192
- package/src/CommentInput.tsx +0 -81
- package/src/DetailSection.tsx +0 -340
- package/src/DetailTabs.tsx +0 -73
- package/src/DetailView.stories.tsx +0 -334
- package/src/DetailView.tsx +0 -823
- package/src/DiffView.tsx +0 -231
- package/src/FieldChangeItem.tsx +0 -46
- package/src/HeaderHighlight.tsx +0 -88
- package/src/InlineCreateRelated.tsx +0 -291
- package/src/MentionAutocomplete.tsx +0 -123
- package/src/PointInTimeRestore.tsx +0 -261
- package/src/ReactionPicker.tsx +0 -106
- package/src/RecordActivityTimeline.tsx +0 -429
- package/src/RecordChatterPanel.tsx +0 -207
- package/src/RecordComments.tsx +0 -215
- package/src/RecordNavigationEnhanced.tsx +0 -211
- package/src/RelatedList.tsx +0 -413
- package/src/RelationshipGraph.tsx +0 -286
- package/src/RichTextCommentInput.tsx +0 -348
- package/src/SectionGroup.tsx +0 -101
- package/src/SubscriptionToggle.tsx +0 -60
- package/src/ThreadedReplies.tsx +0 -161
- package/src/__tests__/ActivityTimeline.test.tsx +0 -119
- package/src/__tests__/ActivityTimelineFiltering.test.tsx +0 -143
- package/src/__tests__/CommentInput.test.tsx +0 -57
- package/src/__tests__/DetailSection.test.tsx +0 -490
- package/src/__tests__/DetailView.test.tsx +0 -694
- package/src/__tests__/FieldChangeItem.test.tsx +0 -119
- package/src/__tests__/HeaderHighlight.test.tsx +0 -213
- package/src/__tests__/MentionAutocomplete.test.tsx +0 -97
- package/src/__tests__/ReactionPicker.test.tsx +0 -113
- package/src/__tests__/RecordActivityTimeline.test.tsx +0 -395
- package/src/__tests__/RecordChatterPanel.test.tsx +0 -265
- package/src/__tests__/RecordComments.test.tsx +0 -96
- package/src/__tests__/RecordCommentsPinSearch.test.tsx +0 -133
- package/src/__tests__/RelatedList.test.tsx +0 -160
- package/src/__tests__/SectionGroup.test.tsx +0 -101
- package/src/__tests__/SubscriptionToggle.test.tsx +0 -84
- package/src/__tests__/ThreadedReplies.test.tsx +0 -212
- package/src/__tests__/autoLayout.test.ts +0 -228
- package/src/__tests__/phase12-features.test.tsx +0 -583
- package/src/__tests__/roadmap-features.test.tsx +0 -478
- package/src/autoLayout.ts +0 -128
- package/src/index.tsx +0 -149
- package/src/useDetailTranslation.ts +0 -114
- package/tsconfig.json +0 -18
- package/vite.config.ts +0 -56
- package/vitest.config.ts +0 -13
- package/vitest.setup.ts +0 -1
- /package/dist/{src → packages/plugin-detail/src}/ActivityTimeline.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/CommentAttachment.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/CommentInput.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailSection.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailTabs.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailView.stories.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DiffView.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/FieldChangeItem.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/HeaderHighlight.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/InlineCreateRelated.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/MentionAutocomplete.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/PointInTimeRestore.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/ReactionPicker.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordActivityTimeline.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordChatterPanel.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordComments.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordNavigationEnhanced.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RelationshipGraph.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RichTextCommentInput.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/SectionGroup.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/SubscriptionToggle.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/ThreadedReplies.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/autoLayout.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/index.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/useDetailTranslation.d.ts +0 -0
package/src/DetailSection.tsx
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import * as React from 'react';
|
|
10
|
-
import {
|
|
11
|
-
cn,
|
|
12
|
-
Card,
|
|
13
|
-
CardHeader,
|
|
14
|
-
CardTitle,
|
|
15
|
-
CardContent,
|
|
16
|
-
Collapsible,
|
|
17
|
-
CollapsibleTrigger,
|
|
18
|
-
CollapsibleContent,
|
|
19
|
-
Badge,
|
|
20
|
-
Button,
|
|
21
|
-
Tooltip,
|
|
22
|
-
TooltipContent,
|
|
23
|
-
TooltipProvider,
|
|
24
|
-
TooltipTrigger,
|
|
25
|
-
} from '@object-ui/components';
|
|
26
|
-
import { ChevronDown, ChevronRight, Copy, Check } from 'lucide-react';
|
|
27
|
-
import { SchemaRenderer } from '@object-ui/react';
|
|
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';
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Compute responsive col-span classes so that col-span never exceeds the
|
|
36
|
-
* visible column count at each Tailwind breakpoint.
|
|
37
|
-
*
|
|
38
|
-
* For columns=1: no span class (always single column)
|
|
39
|
-
* For columns=2: md:col-span-{min(span,2)}
|
|
40
|
-
* For columns>=3: md:col-span-{min(span,2)} lg:col-span-{min(span,3)}
|
|
41
|
-
*/
|
|
42
|
-
export function getResponsiveSpanClass(span: number | undefined, columns: number): string {
|
|
43
|
-
if (!span || span <= 1 || columns <= 1) return '';
|
|
44
|
-
|
|
45
|
-
if (columns === 2) {
|
|
46
|
-
return span >= 2 ? 'md:col-span-2' : '';
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// columns >= 3: grid-cols-1 md:grid-cols-2 lg:grid-cols-3
|
|
50
|
-
if (span === 2) return 'md:col-span-2';
|
|
51
|
-
if (span >= 3) return 'md:col-span-2 lg:col-span-3';
|
|
52
|
-
|
|
53
|
-
return '';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface VirtualScrollOptions {
|
|
57
|
-
/** Enable virtual scrolling for large field sets */
|
|
58
|
-
enabled?: boolean;
|
|
59
|
-
/** Height of each field row in px (default: 60) */
|
|
60
|
-
itemHeight?: number;
|
|
61
|
-
/** Number of fields to render in the initial batch before revealing all (default: 20) */
|
|
62
|
-
batchSize?: number;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface DetailSectionProps {
|
|
66
|
-
section: DetailViewSectionType;
|
|
67
|
-
data?: any;
|
|
68
|
-
className?: string;
|
|
69
|
-
/** Object schema from DataSource for field type enrichment */
|
|
70
|
-
objectSchema?: any;
|
|
71
|
-
/** Object name for i18n field label resolution */
|
|
72
|
-
objectName?: string;
|
|
73
|
-
/** Whether inline editing is active */
|
|
74
|
-
isEditing?: boolean;
|
|
75
|
-
/** Callback when a field value changes during inline editing */
|
|
76
|
-
onFieldChange?: (field: string, value: any) => void;
|
|
77
|
-
/** Virtual scrolling configuration for sections with many fields */
|
|
78
|
-
virtualScroll?: VirtualScrollOptions;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export const DetailSection: React.FC<DetailSectionProps> = ({
|
|
82
|
-
section,
|
|
83
|
-
data,
|
|
84
|
-
className,
|
|
85
|
-
objectSchema,
|
|
86
|
-
objectName,
|
|
87
|
-
isEditing = false,
|
|
88
|
-
onFieldChange,
|
|
89
|
-
virtualScroll,
|
|
90
|
-
}) => {
|
|
91
|
-
const [isCollapsed, setIsCollapsed] = React.useState(section.defaultCollapsed ?? false);
|
|
92
|
-
const [copiedField, setCopiedField] = React.useState<string | null>(null);
|
|
93
|
-
const [visibleCount, setVisibleCount] = React.useState<number | undefined>(undefined);
|
|
94
|
-
const { t } = useDetailTranslation();
|
|
95
|
-
const { fieldLabel } = useSafeFieldLabel();
|
|
96
|
-
|
|
97
|
-
const handleCopyField = React.useCallback((fieldName: string, value: any) => {
|
|
98
|
-
const textValue = value !== null && value !== undefined ? String(value) : '';
|
|
99
|
-
navigator.clipboard.writeText(textValue).then(() => {
|
|
100
|
-
setCopiedField(fieldName);
|
|
101
|
-
setTimeout(() => setCopiedField(null), 2000);
|
|
102
|
-
});
|
|
103
|
-
}, []);
|
|
104
|
-
|
|
105
|
-
// Filter out empty fields when hideEmpty is set
|
|
106
|
-
const visibleFields = section.hideEmpty
|
|
107
|
-
? section.fields.filter((field) => {
|
|
108
|
-
const value = data?.[field.name] ?? field.value;
|
|
109
|
-
return value !== null && value !== undefined && value !== '';
|
|
110
|
-
})
|
|
111
|
-
: section.fields;
|
|
112
|
-
|
|
113
|
-
// Hide entire section when all fields are empty
|
|
114
|
-
if (visibleFields.length === 0) return null;
|
|
115
|
-
|
|
116
|
-
// Apply auto-layout: infer columns and auto-span wide fields
|
|
117
|
-
const { fields: layoutFields, columns: effectiveColumns } = applyDetailAutoLayout(
|
|
118
|
-
visibleFields,
|
|
119
|
-
section.columns
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
const renderField = (field: DetailViewField) => {
|
|
123
|
-
const value = data?.[field.name] ?? field.value;
|
|
124
|
-
|
|
125
|
-
// If custom renderer provided
|
|
126
|
-
if (field.render) {
|
|
127
|
-
return <SchemaRenderer schema={field.render} data={{ ...data, value }} />;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Calculate responsive span class so col-span never exceeds the visible
|
|
131
|
-
// column count at each breakpoint, preventing implicit columns on mobile.
|
|
132
|
-
const spanClass = getResponsiveSpanClass(field.span, effectiveColumns);
|
|
133
|
-
|
|
134
|
-
const displayValue = (() => {
|
|
135
|
-
if (value === null || value === undefined) return <span className="text-muted-foreground/50 text-xs italic">—</span>;
|
|
136
|
-
// Enrich field with objectSchema metadata — merge missing properties
|
|
137
|
-
// even when field.type is explicitly set (e.g., type: 'lookup' without reference_to)
|
|
138
|
-
const objectDefField = objectSchema?.fields?.[field.name];
|
|
139
|
-
const resolvedType = field.type || objectDefField?.type;
|
|
140
|
-
const enrichedField: Record<string, any> = { ...field };
|
|
141
|
-
if (objectDefField) {
|
|
142
|
-
if (!field.type && objectDefField.type) enrichedField.type = objectDefField.type;
|
|
143
|
-
if (objectDefField.options && !enrichedField.options) enrichedField.options = objectDefField.options;
|
|
144
|
-
if (objectDefField.currency && !enrichedField.currency) enrichedField.currency = objectDefField.currency;
|
|
145
|
-
if (objectDefField.precision !== undefined && enrichedField.precision === undefined) enrichedField.precision = objectDefField.precision;
|
|
146
|
-
if (objectDefField.format && !enrichedField.format) enrichedField.format = objectDefField.format;
|
|
147
|
-
const refTarget = objectDefField.reference_to || objectDefField.reference;
|
|
148
|
-
if (refTarget && !enrichedField.reference_to) enrichedField.reference_to = refTarget;
|
|
149
|
-
if (objectDefField.reference_field && !enrichedField.reference_field) enrichedField.reference_field = objectDefField.reference_field;
|
|
150
|
-
}
|
|
151
|
-
// Use type-aware cell renderer when field type is available (explicit or enriched)
|
|
152
|
-
if (resolvedType) {
|
|
153
|
-
const CellRenderer = getCellRenderer(resolvedType);
|
|
154
|
-
if (CellRenderer) {
|
|
155
|
-
return <CellRenderer value={value} field={enrichedField as unknown as FieldMetadata} />;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return String(value);
|
|
159
|
-
})();
|
|
160
|
-
const canCopy = value !== null && value !== undefined && value !== '';
|
|
161
|
-
const isCopied = copiedField === field.name;
|
|
162
|
-
|
|
163
|
-
// Default field rendering with copy button and touch-friendly targets
|
|
164
|
-
return (
|
|
165
|
-
<div key={field.name} className={cn("space-y-1.5 group", spanClass)}>
|
|
166
|
-
<div className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
|
|
167
|
-
{fieldLabel(objectName || '', field.name, field.label || field.name)}
|
|
168
|
-
</div>
|
|
169
|
-
{isEditing && !field.readonly ? (
|
|
170
|
-
<div className="min-h-[44px] sm:min-h-0">
|
|
171
|
-
<input
|
|
172
|
-
type={field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'}
|
|
173
|
-
className="w-full px-2 py-1.5 text-sm border rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring"
|
|
174
|
-
value={value != null ? String(value) : ''}
|
|
175
|
-
onChange={(e) => onFieldChange?.(field.name, e.target.value)}
|
|
176
|
-
/>
|
|
177
|
-
</div>
|
|
178
|
-
) : (
|
|
179
|
-
<div
|
|
180
|
-
className={cn(
|
|
181
|
-
"flex items-start justify-between gap-2 min-h-[44px] sm:min-h-0 rounded-md",
|
|
182
|
-
canCopy && "cursor-pointer active:bg-muted/60 transition-colors"
|
|
183
|
-
)}
|
|
184
|
-
onClick={canCopy ? () => handleCopyField(field.name, value) : undefined}
|
|
185
|
-
onKeyDown={canCopy ? (e: React.KeyboardEvent) => {
|
|
186
|
-
if (e.key === 'Enter' || e.key === ' ') {
|
|
187
|
-
e.preventDefault();
|
|
188
|
-
handleCopyField(field.name, value);
|
|
189
|
-
}
|
|
190
|
-
} : undefined}
|
|
191
|
-
role={canCopy ? "button" : undefined}
|
|
192
|
-
tabIndex={canCopy ? 0 : undefined}
|
|
193
|
-
>
|
|
194
|
-
<div className="text-sm flex-1 break-words py-1">
|
|
195
|
-
{displayValue}
|
|
196
|
-
</div>
|
|
197
|
-
{canCopy && (
|
|
198
|
-
<TooltipProvider>
|
|
199
|
-
<Tooltip>
|
|
200
|
-
<TooltipTrigger asChild>
|
|
201
|
-
<Button
|
|
202
|
-
variant="ghost"
|
|
203
|
-
size="icon"
|
|
204
|
-
className="h-6 w-6 opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
|
|
205
|
-
onClick={(e) => {
|
|
206
|
-
e.stopPropagation();
|
|
207
|
-
handleCopyField(field.name, value);
|
|
208
|
-
}}
|
|
209
|
-
>
|
|
210
|
-
{isCopied ? (
|
|
211
|
-
<Check className="h-3 w-3 text-green-600" />
|
|
212
|
-
) : (
|
|
213
|
-
<Copy className="h-3 w-3" />
|
|
214
|
-
)}
|
|
215
|
-
</Button>
|
|
216
|
-
</TooltipTrigger>
|
|
217
|
-
<TooltipContent>
|
|
218
|
-
{isCopied ? t('detail.copied') : t('detail.copyToClipboard')}
|
|
219
|
-
</TooltipContent>
|
|
220
|
-
</Tooltip>
|
|
221
|
-
</TooltipProvider>
|
|
222
|
-
)}
|
|
223
|
-
</div>
|
|
224
|
-
)}
|
|
225
|
-
</div>
|
|
226
|
-
);
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// Virtual scroll: progressive batch rendering for large field sets
|
|
230
|
-
const vsEnabled = virtualScroll?.enabled === true;
|
|
231
|
-
const vsBatchSize = virtualScroll?.batchSize ?? 20;
|
|
232
|
-
/** Delay (ms) before revealing remaining fields after the initial batch */
|
|
233
|
-
const VS_REVEAL_DELAY = 100;
|
|
234
|
-
|
|
235
|
-
React.useEffect(() => {
|
|
236
|
-
if (!vsEnabled) {
|
|
237
|
-
setVisibleCount(undefined);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
// Start with a batch, then progressively reveal more
|
|
241
|
-
if (layoutFields.length <= vsBatchSize) {
|
|
242
|
-
setVisibleCount(undefined);
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
setVisibleCount(vsBatchSize);
|
|
246
|
-
const timer = setTimeout(() => setVisibleCount(undefined), VS_REVEAL_DELAY);
|
|
247
|
-
return () => clearTimeout(timer);
|
|
248
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
249
|
-
}, [vsEnabled, layoutFields.length, vsBatchSize]);
|
|
250
|
-
|
|
251
|
-
const renderedFields = visibleCount !== undefined
|
|
252
|
-
? layoutFields.slice(0, visibleCount)
|
|
253
|
-
: layoutFields;
|
|
254
|
-
|
|
255
|
-
const content = (
|
|
256
|
-
<div
|
|
257
|
-
className={cn(
|
|
258
|
-
"grid gap-3 sm:gap-4",
|
|
259
|
-
effectiveColumns === 1 ? "grid-cols-1" :
|
|
260
|
-
effectiveColumns === 2 ? "grid-cols-1 md:grid-cols-2" :
|
|
261
|
-
effectiveColumns === 3 ? "grid-cols-1 md:grid-cols-2 lg:grid-cols-3" :
|
|
262
|
-
"grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
|
|
263
|
-
)}
|
|
264
|
-
>
|
|
265
|
-
{renderedFields.map(renderField)}
|
|
266
|
-
</div>
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
if (!section.collapsible) {
|
|
270
|
-
return (
|
|
271
|
-
<Card className={cn(section.showBorder === false ? 'border-none shadow-none' : '', className)}>
|
|
272
|
-
{section.title && (
|
|
273
|
-
<CardHeader className={cn(section.headerColor && `bg-${section.headerColor}`)}>
|
|
274
|
-
<CardTitle className="flex items-center justify-between">
|
|
275
|
-
<div className="flex items-center gap-2">
|
|
276
|
-
{section.icon && <span className="text-muted-foreground">{section.icon}</span>}
|
|
277
|
-
<span>{section.title}</span>
|
|
278
|
-
{section.fields && (
|
|
279
|
-
<Badge variant="secondary" className="ml-2 text-xs">
|
|
280
|
-
{section.fields.length}
|
|
281
|
-
</Badge>
|
|
282
|
-
)}
|
|
283
|
-
</div>
|
|
284
|
-
</CardTitle>
|
|
285
|
-
{section.description && (
|
|
286
|
-
<p className="text-sm text-muted-foreground mt-1.5">{section.description}</p>
|
|
287
|
-
)}
|
|
288
|
-
</CardHeader>
|
|
289
|
-
)}
|
|
290
|
-
<CardContent className="pt-4 sm:pt-6 px-3 sm:px-6">
|
|
291
|
-
{content}
|
|
292
|
-
</CardContent>
|
|
293
|
-
</Card>
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return (
|
|
298
|
-
<Collapsible
|
|
299
|
-
open={!isCollapsed}
|
|
300
|
-
onOpenChange={(open) => setIsCollapsed(!open)}
|
|
301
|
-
className={className}
|
|
302
|
-
>
|
|
303
|
-
<Card>
|
|
304
|
-
<CollapsibleTrigger asChild>
|
|
305
|
-
<CardHeader className={cn(
|
|
306
|
-
"cursor-pointer hover:bg-muted/50 transition-colors",
|
|
307
|
-
section.headerColor && `bg-${section.headerColor}`
|
|
308
|
-
)}>
|
|
309
|
-
<CardTitle className="flex items-center justify-between">
|
|
310
|
-
<div className="flex items-center gap-2">
|
|
311
|
-
{section.icon && <span className="text-muted-foreground">{section.icon}</span>}
|
|
312
|
-
<span>{section.title}</span>
|
|
313
|
-
{section.fields && (
|
|
314
|
-
<Badge variant="secondary" className="ml-2 text-xs">
|
|
315
|
-
{section.fields.length}
|
|
316
|
-
</Badge>
|
|
317
|
-
)}
|
|
318
|
-
</div>
|
|
319
|
-
<div className="flex items-center gap-2">
|
|
320
|
-
{isCollapsed ? (
|
|
321
|
-
<ChevronRight className="h-4 w-4 text-muted-foreground" />
|
|
322
|
-
) : (
|
|
323
|
-
<ChevronDown className="h-4 w-4 text-muted-foreground" />
|
|
324
|
-
)}
|
|
325
|
-
</div>
|
|
326
|
-
</CardTitle>
|
|
327
|
-
{section.description && !isCollapsed && (
|
|
328
|
-
<p className="text-sm text-muted-foreground mt-1.5">{section.description}</p>
|
|
329
|
-
)}
|
|
330
|
-
</CardHeader>
|
|
331
|
-
</CollapsibleTrigger>
|
|
332
|
-
<CollapsibleContent>
|
|
333
|
-
<CardContent className="pt-4 sm:pt-6 px-3 sm:px-6">
|
|
334
|
-
{content}
|
|
335
|
-
</CardContent>
|
|
336
|
-
</CollapsibleContent>
|
|
337
|
-
</Card>
|
|
338
|
-
</Collapsible>
|
|
339
|
-
);
|
|
340
|
-
};
|
package/src/DetailTabs.tsx
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import * as React from 'react';
|
|
10
|
-
import { Tabs, TabsList, TabsTrigger, TabsContent, Badge } from '@object-ui/components';
|
|
11
|
-
import { SchemaRenderer } from '@object-ui/react';
|
|
12
|
-
import type { DetailViewTab } from '@object-ui/types';
|
|
13
|
-
|
|
14
|
-
export interface DetailTabsProps {
|
|
15
|
-
tabs: DetailViewTab[];
|
|
16
|
-
data?: any;
|
|
17
|
-
className?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const DetailTabs: React.FC<DetailTabsProps> = ({
|
|
21
|
-
tabs,
|
|
22
|
-
data,
|
|
23
|
-
className,
|
|
24
|
-
}) => {
|
|
25
|
-
const [activeTab, setActiveTab] = React.useState(tabs[0]?.key);
|
|
26
|
-
|
|
27
|
-
const visibleTabs = tabs.filter(tab => {
|
|
28
|
-
if (typeof tab.visible === 'boolean') return tab.visible;
|
|
29
|
-
if (typeof tab.visible === 'string') {
|
|
30
|
-
// Simple expression evaluation could go here
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
return true;
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<Tabs value={activeTab} onValueChange={setActiveTab} className={className}>
|
|
38
|
-
<TabsList className="w-full justify-start border-b rounded-none bg-transparent p-0">
|
|
39
|
-
{visibleTabs.map((tab) => (
|
|
40
|
-
<TabsTrigger
|
|
41
|
-
key={tab.key}
|
|
42
|
-
value={tab.key}
|
|
43
|
-
className="relative rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-transparent"
|
|
44
|
-
>
|
|
45
|
-
<div className="flex items-center gap-2">
|
|
46
|
-
{tab.icon && <span>{tab.icon}</span>}
|
|
47
|
-
<span>{tab.label}</span>
|
|
48
|
-
{tab.badge && (
|
|
49
|
-
<Badge variant="secondary" className="ml-1">
|
|
50
|
-
{tab.badge}
|
|
51
|
-
</Badge>
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
54
|
-
</TabsTrigger>
|
|
55
|
-
))}
|
|
56
|
-
</TabsList>
|
|
57
|
-
|
|
58
|
-
{visibleTabs.map((tab) => (
|
|
59
|
-
<TabsContent key={tab.key} value={tab.key} className="mt-4">
|
|
60
|
-
{Array.isArray(tab.content) ? (
|
|
61
|
-
<div className="space-y-4">
|
|
62
|
-
{tab.content.map((schema, index) => (
|
|
63
|
-
<SchemaRenderer key={index} schema={schema} data={data} />
|
|
64
|
-
))}
|
|
65
|
-
</div>
|
|
66
|
-
) : (
|
|
67
|
-
<SchemaRenderer schema={tab.content} data={data} />
|
|
68
|
-
)}
|
|
69
|
-
</TabsContent>
|
|
70
|
-
))}
|
|
71
|
-
</Tabs>
|
|
72
|
-
);
|
|
73
|
-
};
|