@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.
Files changed (141) hide show
  1. package/.turbo/turbo-build.log +45 -8
  2. package/CHANGELOG.md +11 -0
  3. package/dist/AddressField-B1iVr404.js +96 -0
  4. package/dist/AutoNumberField-BxnFqllo.js +8 -0
  5. package/dist/AvatarField-Duw4xOLZ.js +82 -0
  6. package/dist/BooleanField-CZ4axVeq.js +37 -0
  7. package/dist/CodeField-BSz-mk2v.js +21 -0
  8. package/dist/ColorField-B522ad8m.js +42 -0
  9. package/dist/CurrencyField-Cwr3_pow.js +43 -0
  10. package/dist/DateField-DCo6dxud.js +21 -0
  11. package/dist/DateTimeField-BWfBuANO.js +28 -0
  12. package/dist/EmailField-CpwbdVCU.js +31 -0
  13. package/dist/FileField-DVAUAJ8e.js +133 -0
  14. package/dist/FormulaField-CJkkwIK8.js +9 -0
  15. package/dist/GeolocationField-DNCKitgo.js +123 -0
  16. package/dist/GridField-DSblZNfp.js +30 -0
  17. package/dist/ImageField-DBAlnMon.js +90 -0
  18. package/dist/LocationField-DsHsXA6R.js +31 -0
  19. package/dist/LookupField-CsT0QQz2.js +96 -0
  20. package/dist/MasterDetailField-Db8b7Gqs.js +108 -0
  21. package/dist/NumberField-0IGp7lcA.js +26 -0
  22. package/dist/ObjectField-BLApgJtS.js +48 -0
  23. package/dist/PasswordField-pHKyNlmo.js +38 -0
  24. package/dist/PercentField-CwgKmlIb.js +63 -0
  25. package/dist/PhoneField-lKtbYOdN.js +31 -0
  26. package/dist/QRCodeField-BTTasT3w.js +77 -0
  27. package/dist/RatingField-De2X-l44.js +47 -0
  28. package/dist/RichTextField-B5QnvUOr.js +38 -0
  29. package/dist/SelectField-C9AZRHWu.js +26 -0
  30. package/dist/SignatureField-BgcEmYzd.js +85 -0
  31. package/dist/SliderField-BzrttVOY.js +30 -0
  32. package/dist/SummaryField-ugYPYxjP.js +9 -0
  33. package/dist/TextAreaField-DSE_CaU6.js +39 -0
  34. package/dist/TextField-DFQ4T9PR.js +32 -0
  35. package/dist/TimeField-F0cfmsps.js +21 -0
  36. package/dist/UrlField-DLXrFIH-.js +33 -0
  37. package/dist/UserField-PXMmxJY9.js +49 -0
  38. package/dist/VectorField-CKg9jdGa.js +25 -0
  39. package/dist/index-qQ1C-yUR.js +59976 -0
  40. package/dist/index.js +32 -55026
  41. package/dist/index.umd.cjs +41 -30
  42. package/dist/plugin-detail.css +1 -1
  43. package/dist/src/ActivityTimeline.d.ts +20 -0
  44. package/dist/src/ActivityTimeline.d.ts.map +1 -0
  45. package/dist/src/CommentAttachment.d.ts +25 -0
  46. package/dist/src/CommentAttachment.d.ts.map +1 -0
  47. package/dist/src/CommentInput.d.ts +24 -0
  48. package/dist/src/CommentInput.d.ts.map +1 -0
  49. package/dist/src/DetailSection.d.ts +8 -0
  50. package/dist/src/DetailSection.d.ts.map +1 -1
  51. package/dist/src/DetailView.d.ts +4 -0
  52. package/dist/src/DetailView.d.ts.map +1 -1
  53. package/dist/src/DetailView.stories.d.ts +8 -0
  54. package/dist/src/DetailView.stories.d.ts.map +1 -1
  55. package/dist/src/DiffView.d.ts +24 -0
  56. package/dist/src/DiffView.d.ts.map +1 -0
  57. package/dist/src/FieldChangeItem.d.ts +21 -0
  58. package/dist/src/FieldChangeItem.d.ts.map +1 -0
  59. package/dist/src/HeaderHighlight.d.ts +18 -0
  60. package/dist/src/HeaderHighlight.d.ts.map +1 -0
  61. package/dist/src/InlineCreateRelated.d.ts +32 -0
  62. package/dist/src/InlineCreateRelated.d.ts.map +1 -0
  63. package/dist/src/MentionAutocomplete.d.ts +43 -0
  64. package/dist/src/MentionAutocomplete.d.ts.map +1 -0
  65. package/dist/src/PointInTimeRestore.d.ts +28 -0
  66. package/dist/src/PointInTimeRestore.d.ts.map +1 -0
  67. package/dist/src/ReactionPicker.d.ts +25 -0
  68. package/dist/src/ReactionPicker.d.ts.map +1 -0
  69. package/dist/src/RecordActivityTimeline.d.ts +49 -0
  70. package/dist/src/RecordActivityTimeline.d.ts.map +1 -0
  71. package/dist/src/RecordChatterPanel.d.ts +48 -0
  72. package/dist/src/RecordChatterPanel.d.ts.map +1 -0
  73. package/dist/src/RecordComments.d.ts +20 -0
  74. package/dist/src/RecordComments.d.ts.map +1 -0
  75. package/dist/src/RecordNavigationEnhanced.d.ts +18 -0
  76. package/dist/src/RecordNavigationEnhanced.d.ts.map +1 -0
  77. package/dist/src/RelatedList.d.ts +20 -0
  78. package/dist/src/RelatedList.d.ts.map +1 -1
  79. package/dist/src/RelationshipGraph.d.ts +23 -0
  80. package/dist/src/RelationshipGraph.d.ts.map +1 -0
  81. package/dist/src/RichTextCommentInput.d.ts +24 -0
  82. package/dist/src/RichTextCommentInput.d.ts.map +1 -0
  83. package/dist/src/SectionGroup.d.ts +21 -0
  84. package/dist/src/SectionGroup.d.ts.map +1 -0
  85. package/dist/src/SubscriptionToggle.d.ts +22 -0
  86. package/dist/src/SubscriptionToggle.d.ts.map +1 -0
  87. package/dist/src/ThreadedReplies.d.ts +26 -0
  88. package/dist/src/ThreadedReplies.d.ts.map +1 -0
  89. package/dist/src/autoLayout.d.ts +34 -0
  90. package/dist/src/autoLayout.d.ts.map +1 -0
  91. package/dist/src/index.d.ts +40 -0
  92. package/dist/src/index.d.ts.map +1 -1
  93. package/dist/src/useDetailTranslation.d.ts +34 -0
  94. package/dist/src/useDetailTranslation.d.ts.map +1 -0
  95. package/package.json +8 -7
  96. package/src/ActivityTimeline.tsx +184 -0
  97. package/src/CommentAttachment.tsx +192 -0
  98. package/src/CommentInput.tsx +81 -0
  99. package/src/DetailSection.tsx +81 -10
  100. package/src/DetailView.stories.tsx +76 -0
  101. package/src/DetailView.tsx +519 -66
  102. package/src/DiffView.tsx +231 -0
  103. package/src/FieldChangeItem.tsx +46 -0
  104. package/src/HeaderHighlight.tsx +67 -0
  105. package/src/InlineCreateRelated.tsx +291 -0
  106. package/src/MentionAutocomplete.tsx +123 -0
  107. package/src/PointInTimeRestore.tsx +261 -0
  108. package/src/ReactionPicker.tsx +106 -0
  109. package/src/RecordActivityTimeline.tsx +429 -0
  110. package/src/RecordChatterPanel.tsx +202 -0
  111. package/src/RecordComments.tsx +215 -0
  112. package/src/RecordNavigationEnhanced.tsx +211 -0
  113. package/src/RelatedList.tsx +314 -19
  114. package/src/RelationshipGraph.tsx +286 -0
  115. package/src/RichTextCommentInput.tsx +348 -0
  116. package/src/SectionGroup.tsx +101 -0
  117. package/src/SubscriptionToggle.tsx +60 -0
  118. package/src/ThreadedReplies.tsx +161 -0
  119. package/src/__tests__/ActivityTimeline.test.tsx +119 -0
  120. package/src/__tests__/ActivityTimelineFiltering.test.tsx +143 -0
  121. package/src/__tests__/CommentInput.test.tsx +57 -0
  122. package/src/__tests__/DetailSection.test.tsx +320 -0
  123. package/src/__tests__/DetailView.test.tsx +415 -1
  124. package/src/__tests__/FieldChangeItem.test.tsx +119 -0
  125. package/src/__tests__/HeaderHighlight.test.tsx +68 -0
  126. package/src/__tests__/MentionAutocomplete.test.tsx +97 -0
  127. package/src/__tests__/ReactionPicker.test.tsx +113 -0
  128. package/src/__tests__/RecordActivityTimeline.test.tsx +395 -0
  129. package/src/__tests__/RecordChatterPanel.test.tsx +227 -0
  130. package/src/__tests__/RecordComments.test.tsx +96 -0
  131. package/src/__tests__/RecordCommentsPinSearch.test.tsx +133 -0
  132. package/src/__tests__/RelatedList.test.tsx +160 -0
  133. package/src/__tests__/SectionGroup.test.tsx +101 -0
  134. package/src/__tests__/SubscriptionToggle.test.tsx +84 -0
  135. package/src/__tests__/ThreadedReplies.test.tsx +212 -0
  136. package/src/__tests__/autoLayout.test.ts +184 -0
  137. package/src/__tests__/phase12-features.test.tsx +583 -0
  138. package/src/__tests__/roadmap-features.test.tsx +478 -0
  139. package/src/autoLayout.ts +111 -0
  140. package/src/index.tsx +50 -0
  141. package/src/useDetailTranslation.ts +114 -0
@@ -0,0 +1,26 @@
1
+ import { FeedItem } from '../../types/src';
2
+ /**
3
+ * ObjectUI
4
+ * Copyright (c) 2024-present ObjectStack Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+ import * as React from 'react';
10
+ export interface ThreadedRepliesProps {
11
+ /** Parent feed item (root comment) */
12
+ parentItem: FeedItem;
13
+ /** Reply feed items (children) */
14
+ replies: FeedItem[];
15
+ /** Called when a reply is submitted */
16
+ onAddReply?: (parentId: string | number, text: string) => void | Promise<void>;
17
+ /** Whether to show the reply input */
18
+ showReplyInput?: boolean;
19
+ className?: string;
20
+ }
21
+ /**
22
+ * ThreadedReplies — Displays a collapsible thread of replies under a parent comment.
23
+ * Supports adding new replies and showing reply count.
24
+ */
25
+ export declare const ThreadedReplies: React.FC<ThreadedRepliesProps>;
26
+ //# sourceMappingURL=ThreadedReplies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThreadedReplies.d.ts","sourceRoot":"","sources":["../../src/ThreadedReplies.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,WAAW,oBAAoB;IACnC,sCAAsC;IACtC,UAAU,EAAE,QAAQ,CAAC;IACrB,kCAAkC;IAClC,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,uCAAuC;IACvC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,sCAAsC;IACtC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqBD;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAgH1D,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { DetailViewField } from '../../types/src';
2
+ /**
3
+ * Check if a field type is "wide" (should span full row in multi-column layout).
4
+ */
5
+ export declare function isWideFieldType(type: string): boolean;
6
+ /**
7
+ * Infer optimal number of columns for a detail section based on field count.
8
+ *
9
+ * Rules:
10
+ * - 0-3 fields → 1 column
11
+ * - 4-10 fields → 2 columns
12
+ * - 11+ fields → 3 columns
13
+ */
14
+ export declare function inferDetailColumns(fieldCount: number): number;
15
+ /**
16
+ * Apply auto span to wide fields so they span the full row.
17
+ * Only sets span if the field does not already have one explicitly set.
18
+ *
19
+ * @returns A new array of fields with span applied where needed.
20
+ */
21
+ export declare function applyAutoSpan(fields: DetailViewField[], columns: number): DetailViewField[];
22
+ /**
23
+ * Main auto-layout orchestrator for detail sections.
24
+ * Applies intelligent defaults only when the user has not explicitly configured columns.
25
+ *
26
+ * @param fields - The section fields
27
+ * @param schemaColumns - User-provided columns (from DetailViewSection or DetailViewSchema)
28
+ * @returns Object with processed fields and inferred columns
29
+ */
30
+ export declare function applyDetailAutoLayout(fields: DetailViewField[], schemaColumns: number | undefined): {
31
+ fields: DetailViewField[];
32
+ columns: number;
33
+ };
34
+ //# sourceMappingURL=autoLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autoLayout.d.ts","sourceRoot":"","sources":["../../src/autoLayout.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAgBxD;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAErD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,eAAe,EAAE,EACzB,OAAO,EAAE,MAAM,GACd,eAAe,EAAE,CAcnB;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,eAAe,EAAE,EACzB,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC;IAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAchD"}
@@ -3,8 +3,48 @@ import { DetailSection } from './DetailSection';
3
3
  import { DetailTabs } from './DetailTabs';
4
4
  import { RelatedList } from './RelatedList';
5
5
  export { DetailView, DetailSection, DetailTabs, RelatedList };
6
+ export { SectionGroup } from './SectionGroup';
7
+ export { HeaderHighlight } from './HeaderHighlight';
8
+ export { inferDetailColumns, isWideFieldType, applyAutoSpan, applyDetailAutoLayout } from './autoLayout';
9
+ export { useDetailTranslation, DETAIL_DEFAULT_TRANSLATIONS, createSafeTranslationHook } from './useDetailTranslation';
10
+ export { RecordComments } from './RecordComments';
11
+ export { ActivityTimeline } from './ActivityTimeline';
12
+ export { InlineCreateRelated } from './InlineCreateRelated';
13
+ export { RichTextCommentInput } from './RichTextCommentInput';
14
+ export { DiffView } from './DiffView';
15
+ export { RecordNavigationEnhanced } from './RecordNavigationEnhanced';
16
+ export { RelationshipGraph } from './RelationshipGraph';
17
+ export { CommentAttachment } from './CommentAttachment';
18
+ export { PointInTimeRestore } from './PointInTimeRestore';
19
+ export { RecordActivityTimeline } from './RecordActivityTimeline';
20
+ export { RecordChatterPanel } from './RecordChatterPanel';
21
+ export { CommentInput } from './CommentInput';
22
+ export { FieldChangeItem } from './FieldChangeItem';
23
+ export { MentionAutocomplete, createMentionFromSuggestion } from './MentionAutocomplete';
24
+ export { SubscriptionToggle } from './SubscriptionToggle';
25
+ export { ReactionPicker } from './ReactionPicker';
26
+ export { ThreadedReplies } from './ThreadedReplies';
6
27
  export type { DetailViewProps } from './DetailView';
7
28
  export type { DetailSectionProps } from './DetailSection';
8
29
  export type { DetailTabsProps } from './DetailTabs';
9
30
  export type { RelatedListProps } from './RelatedList';
31
+ export type { SectionGroupProps } from './SectionGroup';
32
+ export type { HeaderHighlightProps } from './HeaderHighlight';
33
+ export type { RecordCommentsProps } from './RecordComments';
34
+ export type { ActivityTimelineProps, ActivityFilterType } from './ActivityTimeline';
35
+ export type { InlineCreateRelatedProps, RelatedFieldDefinition, RelatedRecordOption } from './InlineCreateRelated';
36
+ export type { RichTextCommentInputProps, MentionSuggestion } from './RichTextCommentInput';
37
+ export type { DiffViewProps, DiffFieldType, DiffMode, DiffLine } from './DiffView';
38
+ export type { RecordNavigationEnhancedProps } from './RecordNavigationEnhanced';
39
+ export type { RelationshipGraphProps, GraphNode } from './RelationshipGraph';
40
+ export type { CommentAttachmentProps, Attachment } from './CommentAttachment';
41
+ export type { PointInTimeRestoreProps, RevisionEntry } from './PointInTimeRestore';
42
+ export type { RecordActivityTimelineProps, FeedFilterMode } from './RecordActivityTimeline';
43
+ export type { RecordChatterPanelProps } from './RecordChatterPanel';
44
+ export type { CommentInputProps } from './CommentInput';
45
+ export type { FieldChangeItemProps } from './FieldChangeItem';
46
+ export type { MentionAutocompleteProps, MentionSuggestionItem } from './MentionAutocomplete';
47
+ export type { SubscriptionToggleProps } from './SubscriptionToggle';
48
+ export type { ReactionPickerProps } from './ReactionPicker';
49
+ export type { ThreadedRepliesProps } from './ThreadedReplies';
10
50
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;AAC9D,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACzG,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACtH,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,uBAAuB,CAAC;AACzF,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,YAAY,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACpF,YAAY,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACnH,YAAY,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3F,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACnF,YAAY,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAChF,YAAY,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC7E,YAAY,EAAE,sBAAsB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC9E,YAAY,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACnF,YAAY,EAAE,2BAA2B,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC5F,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACpE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,YAAY,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC7F,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACpE,YAAY,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,34 @@
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
+ * Create a safe translation hook with fallback to defaults.
10
+ * Follows the same pattern as useGridTranslation / useListViewTranslation.
11
+ *
12
+ * @param defaults - Fallback English translations keyed by i18n key
13
+ * @param testKey - A key to test if i18n is properly configured
14
+ */
15
+ export declare function createSafeTranslationHook(defaults: Record<string, string>, testKey: string): () => {
16
+ t: (key: string, options?: Record<string, unknown>) => string;
17
+ } | {
18
+ t: import('i18next').TFunction<string, undefined>;
19
+ };
20
+ /**
21
+ * Default English translations for detail view components.
22
+ * Used as fallback when no I18nProvider is available.
23
+ */
24
+ export declare const DETAIL_DEFAULT_TRANSLATIONS: Record<string, string>;
25
+ /**
26
+ * Translation hook for detail view components.
27
+ * Falls back to DETAIL_DEFAULT_TRANSLATIONS when no I18nProvider is available.
28
+ */
29
+ export declare const useDetailTranslation: () => {
30
+ t: (key: string, options?: Record<string, unknown>) => string;
31
+ } | {
32
+ t: import('i18next').TFunction<string, undefined>;
33
+ };
34
+ //# sourceMappingURL=useDetailTranslation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDetailTranslation.d.ts","sourceRoot":"","sources":["../../src/useDetailTranslation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,OAAO,EAAE,MAAM;aAQE,MAAM,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;EA0B3D;AAED;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA6C9D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB;aAnFd,MAAM,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;CAsF3D,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/plugin-detail",
3
- "version": "3.0.3",
3
+ "version": "3.1.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "DetailView plugin for Object UI - comprehensive detail page with sections, tabs, and related lists",
@@ -24,18 +24,19 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "lucide-react": "^0.563.0",
28
- "@object-ui/components": "3.0.3",
29
- "@object-ui/core": "3.0.3",
30
- "@object-ui/react": "3.0.3",
31
- "@object-ui/types": "3.0.3"
27
+ "lucide-react": "^0.576.0",
28
+ "@object-ui/components": "3.1.1",
29
+ "@object-ui/fields": "3.1.1",
30
+ "@object-ui/core": "3.1.1",
31
+ "@object-ui/react": "3.1.1",
32
+ "@object-ui/types": "3.1.1"
32
33
  },
33
34
  "peerDependencies": {
34
35
  "react": "^18.0.0 || ^19.0.0",
35
36
  "react-dom": "^18.0.0 || ^19.0.0"
36
37
  },
37
38
  "devDependencies": {
38
- "@types/react": "19.2.13",
39
+ "@types/react": "19.2.14",
39
40
  "@types/react-dom": "19.2.3",
40
41
  "@vitejs/plugin-react": "^5.1.4",
41
42
  "typescript": "^5.9.3",
@@ -0,0 +1,184 @@
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 { cn, Card, CardHeader, CardTitle, CardContent } from '@object-ui/components';
11
+ import { Activity, Edit, PlusCircle, Trash2, MessageSquare, ArrowRightLeft, Filter } from 'lucide-react';
12
+ import type { ActivityEntry } from '@object-ui/types';
13
+
14
+ export type ActivityFilterType = ActivityEntry['type'] | 'all';
15
+
16
+ export interface ActivityTimelineProps {
17
+ activities: ActivityEntry[];
18
+ /** Show filter controls for activity types */
19
+ filterable?: boolean;
20
+ /** Default filter (defaults to 'all') */
21
+ defaultFilter?: ActivityFilterType;
22
+ className?: string;
23
+ }
24
+
25
+ const ACTIVITY_ICONS: Record<ActivityEntry['type'], React.ElementType> = {
26
+ field_change: Edit,
27
+ create: PlusCircle,
28
+ delete: Trash2,
29
+ comment: MessageSquare,
30
+ status_change: ArrowRightLeft,
31
+ };
32
+
33
+ const ACTIVITY_COLORS: Record<ActivityEntry['type'], string> = {
34
+ field_change: 'bg-blue-100 text-blue-600',
35
+ create: 'bg-green-100 text-green-600',
36
+ delete: 'bg-red-100 text-red-600',
37
+ comment: 'bg-purple-100 text-purple-600',
38
+ status_change: 'bg-amber-100 text-amber-600',
39
+ };
40
+
41
+ function formatTimestamp(timestamp: string): string {
42
+ try {
43
+ const date = new Date(timestamp);
44
+ const now = new Date();
45
+ const diffMs = now.getTime() - date.getTime();
46
+ const diffMins = Math.floor(diffMs / 60000);
47
+
48
+ if (diffMins < 1) return 'just now';
49
+ if (diffMins < 60) return `${diffMins}m ago`;
50
+ const diffHours = Math.floor(diffMins / 60);
51
+ if (diffHours < 24) return `${diffHours}h ago`;
52
+ const diffDays = Math.floor(diffHours / 24);
53
+ if (diffDays < 7) return `${diffDays}d ago`;
54
+ return date.toLocaleDateString();
55
+ } catch {
56
+ return timestamp;
57
+ }
58
+ }
59
+
60
+ function formatFieldChange(entry: ActivityEntry): string {
61
+ if (entry.description) return entry.description;
62
+
63
+ if (entry.type === 'field_change' && entry.field) {
64
+ const fieldLabel = entry.field.charAt(0).toUpperCase() + entry.field.slice(1).replace(/_/g, ' ');
65
+ const oldVal = entry.oldValue != null ? String(entry.oldValue) : '(empty)';
66
+ const newVal = entry.newValue != null ? String(entry.newValue) : '(empty)';
67
+ return `Changed ${fieldLabel} from "${oldVal}" to "${newVal}"`;
68
+ }
69
+
70
+ if (entry.type === 'create') return 'Created this record';
71
+ if (entry.type === 'delete') return 'Deleted this record';
72
+ if (entry.type === 'status_change' && entry.field) {
73
+ const newVal = entry.newValue != null ? String(entry.newValue) : '(empty)';
74
+ return `Changed status to "${newVal}"`;
75
+ }
76
+
77
+ return 'Updated record';
78
+ }
79
+
80
+ const FILTER_LABELS: Record<ActivityFilterType, string> = {
81
+ all: 'All',
82
+ field_change: 'Field Changes',
83
+ create: 'Creates',
84
+ delete: 'Deletes',
85
+ comment: 'Comments',
86
+ status_change: 'Status Changes',
87
+ };
88
+
89
+ export const ActivityTimeline: React.FC<ActivityTimelineProps> = ({
90
+ activities,
91
+ filterable = false,
92
+ defaultFilter = 'all',
93
+ className,
94
+ }) => {
95
+ const [activeFilter, setActiveFilter] = React.useState<ActivityFilterType>(defaultFilter);
96
+
97
+ const filteredActivities = React.useMemo(() => {
98
+ if (activeFilter === 'all') return activities;
99
+ return activities.filter(a => a.type === activeFilter);
100
+ }, [activities, activeFilter]);
101
+
102
+ return (
103
+ <Card className={cn('', className)}>
104
+ <CardHeader>
105
+ <CardTitle className="flex items-center gap-2 text-base">
106
+ <Activity className="h-4 w-4" />
107
+ Activity
108
+ <span className="text-sm font-normal text-muted-foreground">
109
+ ({filteredActivities.length})
110
+ </span>
111
+ </CardTitle>
112
+ </CardHeader>
113
+ <CardContent>
114
+ {/* Filter controls */}
115
+ {filterable && (
116
+ <div className="flex flex-wrap gap-1.5 mb-4" role="group" aria-label="Activity type filter">
117
+ {(Object.keys(FILTER_LABELS) as ActivityFilterType[]).map(type => (
118
+ <button
119
+ key={type}
120
+ type="button"
121
+ className={cn(
122
+ 'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium transition-colors',
123
+ activeFilter === type
124
+ ? 'bg-primary text-primary-foreground'
125
+ : 'bg-muted text-muted-foreground hover:bg-muted/80',
126
+ )}
127
+ onClick={() => setActiveFilter(type)}
128
+ aria-pressed={activeFilter === type}
129
+ >
130
+ {type !== 'all' && React.createElement(ACTIVITY_ICONS[type] || Edit, { className: 'h-3 w-3' })}
131
+ {FILTER_LABELS[type]}
132
+ </button>
133
+ ))}
134
+ </div>
135
+ )}
136
+
137
+ {filteredActivities.length === 0 ? (
138
+ <p className="text-sm text-muted-foreground text-center py-4">
139
+ No activity recorded
140
+ </p>
141
+ ) : (
142
+ <div className="relative">
143
+ {/* Timeline line */}
144
+ <div className="absolute left-4 top-2 bottom-2 w-px bg-border" />
145
+
146
+ <div className="space-y-4">
147
+ {filteredActivities.map((entry) => {
148
+ const Icon = ACTIVITY_ICONS[entry.type] || Edit;
149
+ const colorClass = ACTIVITY_COLORS[entry.type] || 'bg-gray-100 text-gray-600';
150
+
151
+ return (
152
+ <div key={entry.id} className="flex gap-3 relative">
153
+ {/* Icon */}
154
+ <div
155
+ className={cn(
156
+ 'shrink-0 h-8 w-8 rounded-full flex items-center justify-center z-10',
157
+ colorClass,
158
+ )}
159
+ >
160
+ <Icon className="h-3.5 w-3.5" />
161
+ </div>
162
+ {/* Content */}
163
+ <div className="flex-1 min-w-0 pt-1">
164
+ <p className="text-sm">
165
+ <span className="font-medium">{entry.user}</span>
166
+ {' '}
167
+ <span className="text-muted-foreground">
168
+ {formatFieldChange(entry)}
169
+ </span>
170
+ </p>
171
+ <p className="text-xs text-muted-foreground mt-0.5">
172
+ {formatTimestamp(entry.timestamp)}
173
+ </p>
174
+ </div>
175
+ </div>
176
+ );
177
+ })}
178
+ </div>
179
+ </div>
180
+ )}
181
+ </CardContent>
182
+ </Card>
183
+ );
184
+ };
@@ -0,0 +1,192 @@
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 { cn, Button } from '@object-ui/components';
11
+ import { Paperclip, X, FileText, Image, FileArchive, File, Upload } from 'lucide-react';
12
+
13
+ export interface Attachment {
14
+ id: string;
15
+ name: string;
16
+ size: number;
17
+ type: string;
18
+ url?: string;
19
+ thumbnailUrl?: string;
20
+ }
21
+
22
+ export interface CommentAttachmentProps {
23
+ attachments: Attachment[];
24
+ onUpload?: (files: FileList) => void | Promise<void>;
25
+ onRemove?: (attachmentId: string) => void;
26
+ className?: string;
27
+ readOnly?: boolean;
28
+ }
29
+
30
+ function formatFileSize(bytes: number): string {
31
+ if (bytes < 1024) return `${bytes} B`;
32
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
33
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
34
+ }
35
+
36
+ function isImageType(type: string): boolean {
37
+ return type.startsWith('image/');
38
+ }
39
+
40
+ function getFileIcon(type: string): React.ElementType {
41
+ if (isImageType(type)) return Image;
42
+ if (type.includes('pdf') || type.includes('document') || type.includes('text'))
43
+ return FileText;
44
+ if (type.includes('zip') || type.includes('archive') || type.includes('compressed'))
45
+ return FileArchive;
46
+ return File;
47
+ }
48
+
49
+ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
50
+ attachments,
51
+ onUpload,
52
+ onRemove,
53
+ className,
54
+ readOnly = false,
55
+ }) => {
56
+ const [isDragOver, setIsDragOver] = React.useState(false);
57
+ const fileInputRef = React.useRef<HTMLInputElement>(null);
58
+
59
+ const handleDragOver = React.useCallback((e: React.DragEvent) => {
60
+ e.preventDefault();
61
+ e.stopPropagation();
62
+ setIsDragOver(true);
63
+ }, []);
64
+
65
+ const handleDragLeave = React.useCallback((e: React.DragEvent) => {
66
+ e.preventDefault();
67
+ e.stopPropagation();
68
+ setIsDragOver(false);
69
+ }, []);
70
+
71
+ const handleDrop = React.useCallback(
72
+ (e: React.DragEvent) => {
73
+ e.preventDefault();
74
+ e.stopPropagation();
75
+ setIsDragOver(false);
76
+ if (onUpload && e.dataTransfer.files.length > 0) {
77
+ onUpload(e.dataTransfer.files);
78
+ }
79
+ },
80
+ [onUpload],
81
+ );
82
+
83
+ const handleFileSelect = React.useCallback(
84
+ (e: React.ChangeEvent<HTMLInputElement>) => {
85
+ if (onUpload && e.target.files && e.target.files.length > 0) {
86
+ onUpload(e.target.files);
87
+ // Reset so the same file can be selected again
88
+ e.target.value = '';
89
+ }
90
+ },
91
+ [onUpload],
92
+ );
93
+
94
+ return (
95
+ <div className={cn('space-y-2', className)}>
96
+ {/* Drop zone */}
97
+ {onUpload && !readOnly && (
98
+ <div
99
+ className={cn(
100
+ 'border-2 border-dashed rounded-md px-4 py-3 text-center transition-colors cursor-pointer',
101
+ isDragOver
102
+ ? 'border-primary bg-primary/5'
103
+ : 'border-muted-foreground/25 hover:border-muted-foreground/40',
104
+ )}
105
+ onDragOver={handleDragOver}
106
+ onDragLeave={handleDragLeave}
107
+ onDrop={handleDrop}
108
+ onClick={() => fileInputRef.current?.click()}
109
+ role="button"
110
+ tabIndex={0}
111
+ onKeyDown={(e) => {
112
+ if (e.key === 'Enter' || e.key === ' ') {
113
+ e.preventDefault();
114
+ fileInputRef.current?.click();
115
+ }
116
+ }}
117
+ >
118
+ <Upload className="h-5 w-5 mx-auto text-muted-foreground mb-1" />
119
+ <p className="text-xs text-muted-foreground">
120
+ Drop files here or click to upload
121
+ </p>
122
+ <input
123
+ ref={fileInputRef}
124
+ type="file"
125
+ multiple
126
+ className="hidden"
127
+ onChange={handleFileSelect}
128
+ />
129
+ </div>
130
+ )}
131
+
132
+ {/* Attachment list */}
133
+ {attachments.length > 0 && (
134
+ <div className="space-y-1.5">
135
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground">
136
+ <Paperclip className="h-3 w-3" />
137
+ <span>
138
+ {attachments.length} attachment{attachments.length !== 1 ? 's' : ''}
139
+ </span>
140
+ </div>
141
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
142
+ {attachments.map((attachment) => {
143
+ const isImage = isImageType(attachment.type);
144
+ const Icon = getFileIcon(attachment.type);
145
+
146
+ return (
147
+ <div
148
+ key={attachment.id}
149
+ className="flex items-center gap-2 rounded-md border px-2.5 py-2 bg-muted/30 group"
150
+ >
151
+ {/* Thumbnail or icon */}
152
+ {isImage && (attachment.thumbnailUrl || attachment.url) ? (
153
+ <img
154
+ src={attachment.thumbnailUrl || attachment.url}
155
+ alt={attachment.name}
156
+ className="h-10 w-10 rounded object-cover shrink-0"
157
+ />
158
+ ) : (
159
+ <div className="h-10 w-10 rounded bg-muted flex items-center justify-center shrink-0">
160
+ <Icon className="h-5 w-5 text-muted-foreground" />
161
+ </div>
162
+ )}
163
+
164
+ {/* File info */}
165
+ <div className="flex-1 min-w-0">
166
+ <p className="text-xs font-medium truncate">{attachment.name}</p>
167
+ <p className="text-[10px] text-muted-foreground">
168
+ {formatFileSize(attachment.size)}
169
+ </p>
170
+ </div>
171
+
172
+ {/* Remove button */}
173
+ {onRemove && !readOnly && (
174
+ <Button
175
+ variant="ghost"
176
+ size="icon"
177
+ className="h-6 w-6 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
178
+ onClick={() => onRemove(attachment.id)}
179
+ title="Remove attachment"
180
+ >
181
+ <X className="h-3.5 w-3.5" />
182
+ </Button>
183
+ )}
184
+ </div>
185
+ );
186
+ })}
187
+ </div>
188
+ </div>
189
+ )}
190
+ </div>
191
+ );
192
+ };
@@ -0,0 +1,81 @@
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 { cn, Button } from '@object-ui/components';
11
+ import { Send } from 'lucide-react';
12
+
13
+ export interface CommentInputProps {
14
+ /** Called when a comment is submitted */
15
+ onSubmit: (text: string) => void | Promise<void>;
16
+ /** Placeholder text */
17
+ placeholder?: string;
18
+ /** Whether the input is disabled */
19
+ disabled?: boolean;
20
+ className?: string;
21
+ }
22
+
23
+ /**
24
+ * CommentInput — Simple comment input component.
25
+ * Renders a "Leave a comment" textarea with submit button.
26
+ * Supports Ctrl+Enter to submit.
27
+ */
28
+ export const CommentInput: React.FC<CommentInputProps> = ({
29
+ onSubmit,
30
+ placeholder = 'Leave a comment…',
31
+ disabled = false,
32
+ className,
33
+ }) => {
34
+ const [text, setText] = React.useState('');
35
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
36
+
37
+ const handleSubmit = React.useCallback(async () => {
38
+ const value = text.trim();
39
+ if (!value) return;
40
+ setIsSubmitting(true);
41
+ try {
42
+ await onSubmit(value);
43
+ setText('');
44
+ } finally {
45
+ setIsSubmitting(false);
46
+ }
47
+ }, [text, onSubmit]);
48
+
49
+ const handleKeyDown = React.useCallback(
50
+ (e: React.KeyboardEvent) => {
51
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
52
+ e.preventDefault();
53
+ handleSubmit();
54
+ }
55
+ },
56
+ [handleSubmit],
57
+ );
58
+
59
+ return (
60
+ <div className={cn('flex gap-2', className)}>
61
+ <textarea
62
+ className="flex-1 min-h-[60px] rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring resize-none"
63
+ placeholder={placeholder}
64
+ value={text}
65
+ onChange={(e) => setText(e.target.value)}
66
+ onKeyDown={handleKeyDown}
67
+ disabled={disabled || isSubmitting}
68
+ />
69
+ <Button
70
+ size="icon"
71
+ variant="default"
72
+ onClick={handleSubmit}
73
+ disabled={!text.trim() || isSubmitting || disabled}
74
+ className="shrink-0 self-end"
75
+ aria-label="Submit comment"
76
+ >
77
+ <Send className="h-4 w-4" />
78
+ </Button>
79
+ </div>
80
+ );
81
+ };