@object-ui/plugin-detail 3.3.0 → 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.
Files changed (134) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +21 -1
  3. package/dist/AddressField-LgHnO2Lk.js +98 -0
  4. package/dist/AutoNumberField-xZCrU0eW.js +14 -0
  5. package/dist/{AvatarField-Xuieq0ZI.js → AvatarField-Dy2XGlPz.js} +16 -15
  6. package/dist/{BooleanField-DwfMKknK.js → BooleanField-C0Clfka5.js} +11 -10
  7. package/dist/CodeField-CHUa07B6.js +23 -0
  8. package/dist/ColorField-vxHqEhcS.js +38 -0
  9. package/dist/CurrencyField-DiWjYWDo.js +49 -0
  10. package/dist/DateField-DGaRPM4P.js +22 -0
  11. package/dist/DateTimeField-8QnpsI_h.js +30 -0
  12. package/dist/EmailField-CkVgMbpI.js +26 -0
  13. package/dist/FileField-5UPV7uek.js +149 -0
  14. package/dist/FormulaField-BUgt6-Pi.js +17 -0
  15. package/dist/GeolocationField-D9T_jgG6.js +118 -0
  16. package/dist/GridField-DE_HwiIN.js +49 -0
  17. package/dist/ImageField-Dswnqtzf.js +73 -0
  18. package/dist/LocationField-gjqbE6na.js +36 -0
  19. package/dist/LookupField-BcS3LRKc.js +901 -0
  20. package/dist/{MasterDetailField-B0HTmmD7.js → MasterDetailField-BF6_-X3A.js} +20 -19
  21. package/dist/NumberField-Dj2rYmrS.js +27 -0
  22. package/dist/ObjectField-BymIojwd.js +50 -0
  23. package/dist/{PasswordField-DVTimsc3.js → PasswordField-ED_Xgqz-.js} +8 -7
  24. package/dist/PercentField-D-JKOxKC.js +61 -0
  25. package/dist/PhoneField-DSCaGYq7.js +26 -0
  26. package/dist/QRCodeField-CtcOUapi.js +73 -0
  27. package/dist/{RatingField-rRi_P0N0.js → RatingField-BDnyQFWy.js} +10 -9
  28. package/dist/RichTextField-CH6LVZQA.js +33 -0
  29. package/dist/SelectField-DE4dpkMV.js +36 -0
  30. package/dist/{SignatureField-2CnhcWI0.js → SignatureField-B1wh3f5A.js} +18 -17
  31. package/dist/{SliderField-DEpMVXko.js → SliderField-zoTCKh9n.js} +2 -1
  32. package/dist/SummaryField-BeBVT6VN.js +22 -0
  33. package/dist/TextAreaField-rfUGrRxh.js +37 -0
  34. package/dist/TextField-C_yM7ATQ.js +30 -0
  35. package/dist/TimeField-BcQmBZi9.js +22 -0
  36. package/dist/UrlField-BakaF6NI.js +31 -0
  37. package/dist/UserField-zS7y3eKb.js +76 -0
  38. package/dist/VectorField-CTZ4myDM.js +34 -0
  39. package/dist/index.js +1912 -1728
  40. package/dist/index.umd.cjs +38 -47
  41. package/dist/packages/plugin-detail/src/DetailSection.d.ts.map +1 -1
  42. package/dist/packages/plugin-detail/src/DetailView.d.ts +24 -0
  43. package/dist/packages/plugin-detail/src/DetailView.d.ts.map +1 -1
  44. package/dist/packages/plugin-detail/src/RelatedList.d.ts +8 -0
  45. package/dist/packages/plugin-detail/src/RelatedList.d.ts.map +1 -1
  46. package/dist/packages/plugin-detail/src/useDetailTranslation.d.ts.map +1 -1
  47. package/dist/plugin-detail.css +1 -2
  48. package/dist/rolldown-runtime-DnwLefa7.js +23 -0
  49. package/dist/{src-C56Ly5uG.js → src-DyUKLvMN.js} +18271 -26636
  50. package/dist/{useFieldTranslation-CkxqyB82.js → useFieldTranslation-BRgjC1oq.js} +1 -1
  51. package/package.json +33 -11
  52. package/.turbo/turbo-build.log +0 -64
  53. package/dist/AddressField-CDLSeyNx.js +0 -93
  54. package/dist/AutoNumberField-CtE7suf5.js +0 -14
  55. package/dist/CodeField-CfwgRxx2.js +0 -22
  56. package/dist/ColorField-YKHA7dBD.js +0 -37
  57. package/dist/CurrencyField-tvS3fPAF.js +0 -51
  58. package/dist/DateField-BKqXpkOh.js +0 -21
  59. package/dist/DateTimeField-CR-nJCE7.js +0 -32
  60. package/dist/EmailField-CgvW1Qal.js +0 -28
  61. package/dist/FileField-BVAme2ML.js +0 -151
  62. package/dist/FormulaField-DamJ2VaG.js +0 -14
  63. package/dist/GeolocationField-C99z7ZBM.js +0 -113
  64. package/dist/GridField-C9JbpTx_.js +0 -51
  65. package/dist/ImageField-CDANtgVV.js +0 -75
  66. package/dist/LocationField-ZSyZ0O-h.js +0 -35
  67. package/dist/LookupField-B3hQJt95.js +0 -903
  68. package/dist/LookupField-D00z6gn_.js +0 -2
  69. package/dist/NumberField-DL2QAL7X.js +0 -26
  70. package/dist/ObjectField-JYvUnuRO.js +0 -52
  71. package/dist/PercentField-DjR6BSpw.js +0 -63
  72. package/dist/PhoneField-CX1JL-jp.js +0 -28
  73. package/dist/QRCodeField-CH_1pU6R.js +0 -72
  74. package/dist/RichTextField-CJqLWlrb.js +0 -32
  75. package/dist/SelectField-DGoDoRM_.js +0 -30
  76. package/dist/SelectField-XBVI50AD.js +0 -2
  77. package/dist/SummaryField-7ch9aqAu.js +0 -19
  78. package/dist/TextAreaField-Cmw1oXcw.js +0 -36
  79. package/dist/TextField-OTLa3p51.js +0 -29
  80. package/dist/TimeField-DKPoNWoR.js +0 -21
  81. package/dist/UrlField-CxbmzP9f.js +0 -33
  82. package/dist/UserField-ChvwUkMK.js +0 -78
  83. package/dist/VectorField-BVClL8Vw.js +0 -36
  84. package/src/ActivityTimeline.tsx +0 -184
  85. package/src/CommentAttachment.tsx +0 -194
  86. package/src/CommentInput.tsx +0 -81
  87. package/src/DetailSection.tsx +0 -340
  88. package/src/DetailTabs.tsx +0 -73
  89. package/src/DetailView.stories.tsx +0 -334
  90. package/src/DetailView.tsx +0 -823
  91. package/src/DiffView.tsx +0 -233
  92. package/src/FieldChangeItem.tsx +0 -46
  93. package/src/HeaderHighlight.tsx +0 -88
  94. package/src/InlineCreateRelated.tsx +0 -291
  95. package/src/MentionAutocomplete.tsx +0 -123
  96. package/src/PointInTimeRestore.tsx +0 -261
  97. package/src/ReactionPicker.tsx +0 -106
  98. package/src/RecordActivityTimeline.tsx +0 -433
  99. package/src/RecordChatterPanel.tsx +0 -209
  100. package/src/RecordComments.tsx +0 -217
  101. package/src/RecordNavigationEnhanced.tsx +0 -213
  102. package/src/RelatedList.tsx +0 -413
  103. package/src/RelationshipGraph.tsx +0 -286
  104. package/src/RichTextCommentInput.tsx +0 -350
  105. package/src/SectionGroup.tsx +0 -101
  106. package/src/SubscriptionToggle.tsx +0 -62
  107. package/src/ThreadedReplies.tsx +0 -163
  108. package/src/__tests__/ActivityTimeline.test.tsx +0 -119
  109. package/src/__tests__/ActivityTimelineFiltering.test.tsx +0 -143
  110. package/src/__tests__/CommentInput.test.tsx +0 -57
  111. package/src/__tests__/DetailSection.test.tsx +0 -490
  112. package/src/__tests__/DetailView.test.tsx +0 -694
  113. package/src/__tests__/FieldChangeItem.test.tsx +0 -119
  114. package/src/__tests__/HeaderHighlight.test.tsx +0 -213
  115. package/src/__tests__/MentionAutocomplete.test.tsx +0 -97
  116. package/src/__tests__/ReactionPicker.test.tsx +0 -113
  117. package/src/__tests__/RecordActivityTimeline.test.tsx +0 -395
  118. package/src/__tests__/RecordChatterPanel.test.tsx +0 -265
  119. package/src/__tests__/RecordComments.test.tsx +0 -96
  120. package/src/__tests__/RecordCommentsPinSearch.test.tsx +0 -133
  121. package/src/__tests__/RelatedList.test.tsx +0 -160
  122. package/src/__tests__/SectionGroup.test.tsx +0 -101
  123. package/src/__tests__/SubscriptionToggle.test.tsx +0 -84
  124. package/src/__tests__/ThreadedReplies.test.tsx +0 -212
  125. package/src/__tests__/autoLayout.test.ts +0 -228
  126. package/src/__tests__/phase12-features.test.tsx +0 -583
  127. package/src/__tests__/roadmap-features.test.tsx +0 -478
  128. package/src/autoLayout.ts +0 -128
  129. package/src/index.tsx +0 -149
  130. package/src/useDetailTranslation.ts +0 -183
  131. package/tsconfig.json +0 -18
  132. package/vite.config.ts +0 -57
  133. package/vitest.config.ts +0 -13
  134. package/vitest.setup.ts +0 -1
@@ -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
- };
@@ -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
- };