@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.
Files changed (209) hide show
  1. package/CHANGELOG.md +31 -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-YGj51ozd.js → AvatarField-Dy2XGlPz.js} +16 -15
  6. package/dist/{BooleanField-CaA898Tk.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-I1A9oEGC.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-DBtluGJ1.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-B_Mnr63i.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-CddhEK9u.js → SignatureField-B1wh3f5A.js} +18 -17
  31. package/dist/{SliderField-Df5hMzNc.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.d.ts +1 -1
  40. package/dist/index.js +1741 -1504
  41. package/dist/index.umd.cjs +43 -51
  42. package/dist/packages/plugin-detail/src/ActivityTimeline.d.ts.map +1 -0
  43. package/dist/packages/plugin-detail/src/CommentAttachment.d.ts.map +1 -0
  44. package/dist/packages/plugin-detail/src/CommentInput.d.ts.map +1 -0
  45. package/dist/packages/plugin-detail/src/DetailSection.d.ts.map +1 -0
  46. package/dist/packages/plugin-detail/src/DetailTabs.d.ts.map +1 -0
  47. package/dist/packages/plugin-detail/src/DetailView.d.ts +47 -0
  48. package/dist/packages/plugin-detail/src/DetailView.d.ts.map +1 -0
  49. package/dist/packages/plugin-detail/src/DetailView.stories.d.ts.map +1 -0
  50. package/dist/packages/plugin-detail/src/DiffView.d.ts.map +1 -0
  51. package/dist/packages/plugin-detail/src/FieldChangeItem.d.ts.map +1 -0
  52. package/dist/packages/plugin-detail/src/HeaderHighlight.d.ts.map +1 -0
  53. package/dist/packages/plugin-detail/src/InlineCreateRelated.d.ts.map +1 -0
  54. package/dist/packages/plugin-detail/src/MentionAutocomplete.d.ts.map +1 -0
  55. package/dist/packages/plugin-detail/src/PointInTimeRestore.d.ts.map +1 -0
  56. package/dist/packages/plugin-detail/src/ReactionPicker.d.ts.map +1 -0
  57. package/dist/packages/plugin-detail/src/RecordActivityTimeline.d.ts.map +1 -0
  58. package/dist/packages/plugin-detail/src/RecordChatterPanel.d.ts.map +1 -0
  59. package/dist/packages/plugin-detail/src/RecordComments.d.ts.map +1 -0
  60. package/dist/packages/plugin-detail/src/RecordNavigationEnhanced.d.ts.map +1 -0
  61. package/dist/{src → packages/plugin-detail/src}/RelatedList.d.ts +8 -0
  62. package/dist/packages/plugin-detail/src/RelatedList.d.ts.map +1 -0
  63. package/dist/packages/plugin-detail/src/RelationshipGraph.d.ts.map +1 -0
  64. package/dist/packages/plugin-detail/src/RichTextCommentInput.d.ts.map +1 -0
  65. package/dist/packages/plugin-detail/src/SectionGroup.d.ts.map +1 -0
  66. package/dist/packages/plugin-detail/src/SubscriptionToggle.d.ts.map +1 -0
  67. package/dist/packages/plugin-detail/src/ThreadedReplies.d.ts.map +1 -0
  68. package/dist/packages/plugin-detail/src/autoLayout.d.ts.map +1 -0
  69. package/dist/packages/plugin-detail/src/index.d.ts.map +1 -0
  70. package/dist/packages/plugin-detail/src/useDetailTranslation.d.ts.map +1 -0
  71. package/dist/plugin-detail.css +1 -2
  72. package/dist/rolldown-runtime-DnwLefa7.js +23 -0
  73. package/dist/{src-CXr1-vVl.js → src-DyUKLvMN.js} +29788 -37711
  74. package/dist/useFieldTranslation-BRgjC1oq.js +9 -0
  75. package/package.json +34 -12
  76. package/.turbo/turbo-build.log +0 -61
  77. package/dist/AddressField-DBkEyMcG.js +0 -93
  78. package/dist/AutoNumberField-Baa191z-.js +0 -14
  79. package/dist/CodeField-BU51nl1L.js +0 -22
  80. package/dist/ColorField-Cnf6ZM7c.js +0 -37
  81. package/dist/CurrencyField-Wg-XOId2.js +0 -51
  82. package/dist/DateField-Cth1ky_m.js +0 -21
  83. package/dist/DateTimeField-B0m6FhHL.js +0 -32
  84. package/dist/EmailField-Do7qT_L_.js +0 -28
  85. package/dist/FileField-aRJAdbQb.js +0 -151
  86. package/dist/FormulaField-DTMkagFx.js +0 -14
  87. package/dist/GeolocationField-RqpHWTEv.js +0 -113
  88. package/dist/GridField-D4IH0cpo.js +0 -51
  89. package/dist/ImageField-BYCFajjr.js +0 -75
  90. package/dist/LocationField-Bi_ew9sd.js +0 -35
  91. package/dist/LookupField-BjwlDPtt.js +0 -902
  92. package/dist/NumberField-D_NucQlp.js +0 -26
  93. package/dist/ObjectField-CG-LaM65.js +0 -52
  94. package/dist/PercentField-B6sO_J3i.js +0 -63
  95. package/dist/PhoneField-CcQAWwR6.js +0 -28
  96. package/dist/QRCodeField-CEjWs-J5.js +0 -72
  97. package/dist/RichTextField-qOEJl5Ai.js +0 -32
  98. package/dist/SelectField-C8hWu3gm.js +0 -30
  99. package/dist/SummaryField-DgiFm-Cr.js +0 -19
  100. package/dist/TextAreaField-DuriTqsD.js +0 -36
  101. package/dist/TextField-CGNSl7RU.js +0 -29
  102. package/dist/TimeField-YO58ctFg.js +0 -21
  103. package/dist/UrlField-1-BMM1jn.js +0 -33
  104. package/dist/UserField-B6GqxP_S.js +0 -78
  105. package/dist/VectorField-BkEjbSt0.js +0 -36
  106. package/dist/src/ActivityTimeline.d.ts.map +0 -1
  107. package/dist/src/CommentAttachment.d.ts.map +0 -1
  108. package/dist/src/CommentInput.d.ts.map +0 -1
  109. package/dist/src/DetailSection.d.ts.map +0 -1
  110. package/dist/src/DetailTabs.d.ts.map +0 -1
  111. package/dist/src/DetailView.d.ts +0 -23
  112. package/dist/src/DetailView.d.ts.map +0 -1
  113. package/dist/src/DetailView.stories.d.ts.map +0 -1
  114. package/dist/src/DiffView.d.ts.map +0 -1
  115. package/dist/src/FieldChangeItem.d.ts.map +0 -1
  116. package/dist/src/HeaderHighlight.d.ts.map +0 -1
  117. package/dist/src/InlineCreateRelated.d.ts.map +0 -1
  118. package/dist/src/MentionAutocomplete.d.ts.map +0 -1
  119. package/dist/src/PointInTimeRestore.d.ts.map +0 -1
  120. package/dist/src/ReactionPicker.d.ts.map +0 -1
  121. package/dist/src/RecordActivityTimeline.d.ts.map +0 -1
  122. package/dist/src/RecordChatterPanel.d.ts.map +0 -1
  123. package/dist/src/RecordComments.d.ts.map +0 -1
  124. package/dist/src/RecordNavigationEnhanced.d.ts.map +0 -1
  125. package/dist/src/RelatedList.d.ts.map +0 -1
  126. package/dist/src/RelationshipGraph.d.ts.map +0 -1
  127. package/dist/src/RichTextCommentInput.d.ts.map +0 -1
  128. package/dist/src/SectionGroup.d.ts.map +0 -1
  129. package/dist/src/SubscriptionToggle.d.ts.map +0 -1
  130. package/dist/src/ThreadedReplies.d.ts.map +0 -1
  131. package/dist/src/autoLayout.d.ts.map +0 -1
  132. package/dist/src/index.d.ts.map +0 -1
  133. package/dist/src/useDetailTranslation.d.ts.map +0 -1
  134. package/src/ActivityTimeline.tsx +0 -184
  135. package/src/CommentAttachment.tsx +0 -192
  136. package/src/CommentInput.tsx +0 -81
  137. package/src/DetailSection.tsx +0 -340
  138. package/src/DetailTabs.tsx +0 -73
  139. package/src/DetailView.stories.tsx +0 -334
  140. package/src/DetailView.tsx +0 -823
  141. package/src/DiffView.tsx +0 -231
  142. package/src/FieldChangeItem.tsx +0 -46
  143. package/src/HeaderHighlight.tsx +0 -88
  144. package/src/InlineCreateRelated.tsx +0 -291
  145. package/src/MentionAutocomplete.tsx +0 -123
  146. package/src/PointInTimeRestore.tsx +0 -261
  147. package/src/ReactionPicker.tsx +0 -106
  148. package/src/RecordActivityTimeline.tsx +0 -429
  149. package/src/RecordChatterPanel.tsx +0 -207
  150. package/src/RecordComments.tsx +0 -215
  151. package/src/RecordNavigationEnhanced.tsx +0 -211
  152. package/src/RelatedList.tsx +0 -413
  153. package/src/RelationshipGraph.tsx +0 -286
  154. package/src/RichTextCommentInput.tsx +0 -348
  155. package/src/SectionGroup.tsx +0 -101
  156. package/src/SubscriptionToggle.tsx +0 -60
  157. package/src/ThreadedReplies.tsx +0 -161
  158. package/src/__tests__/ActivityTimeline.test.tsx +0 -119
  159. package/src/__tests__/ActivityTimelineFiltering.test.tsx +0 -143
  160. package/src/__tests__/CommentInput.test.tsx +0 -57
  161. package/src/__tests__/DetailSection.test.tsx +0 -490
  162. package/src/__tests__/DetailView.test.tsx +0 -694
  163. package/src/__tests__/FieldChangeItem.test.tsx +0 -119
  164. package/src/__tests__/HeaderHighlight.test.tsx +0 -213
  165. package/src/__tests__/MentionAutocomplete.test.tsx +0 -97
  166. package/src/__tests__/ReactionPicker.test.tsx +0 -113
  167. package/src/__tests__/RecordActivityTimeline.test.tsx +0 -395
  168. package/src/__tests__/RecordChatterPanel.test.tsx +0 -265
  169. package/src/__tests__/RecordComments.test.tsx +0 -96
  170. package/src/__tests__/RecordCommentsPinSearch.test.tsx +0 -133
  171. package/src/__tests__/RelatedList.test.tsx +0 -160
  172. package/src/__tests__/SectionGroup.test.tsx +0 -101
  173. package/src/__tests__/SubscriptionToggle.test.tsx +0 -84
  174. package/src/__tests__/ThreadedReplies.test.tsx +0 -212
  175. package/src/__tests__/autoLayout.test.ts +0 -228
  176. package/src/__tests__/phase12-features.test.tsx +0 -583
  177. package/src/__tests__/roadmap-features.test.tsx +0 -478
  178. package/src/autoLayout.ts +0 -128
  179. package/src/index.tsx +0 -149
  180. package/src/useDetailTranslation.ts +0 -114
  181. package/tsconfig.json +0 -18
  182. package/vite.config.ts +0 -56
  183. package/vitest.config.ts +0 -13
  184. package/vitest.setup.ts +0 -1
  185. /package/dist/{src → packages/plugin-detail/src}/ActivityTimeline.d.ts +0 -0
  186. /package/dist/{src → packages/plugin-detail/src}/CommentAttachment.d.ts +0 -0
  187. /package/dist/{src → packages/plugin-detail/src}/CommentInput.d.ts +0 -0
  188. /package/dist/{src → packages/plugin-detail/src}/DetailSection.d.ts +0 -0
  189. /package/dist/{src → packages/plugin-detail/src}/DetailTabs.d.ts +0 -0
  190. /package/dist/{src → packages/plugin-detail/src}/DetailView.stories.d.ts +0 -0
  191. /package/dist/{src → packages/plugin-detail/src}/DiffView.d.ts +0 -0
  192. /package/dist/{src → packages/plugin-detail/src}/FieldChangeItem.d.ts +0 -0
  193. /package/dist/{src → packages/plugin-detail/src}/HeaderHighlight.d.ts +0 -0
  194. /package/dist/{src → packages/plugin-detail/src}/InlineCreateRelated.d.ts +0 -0
  195. /package/dist/{src → packages/plugin-detail/src}/MentionAutocomplete.d.ts +0 -0
  196. /package/dist/{src → packages/plugin-detail/src}/PointInTimeRestore.d.ts +0 -0
  197. /package/dist/{src → packages/plugin-detail/src}/ReactionPicker.d.ts +0 -0
  198. /package/dist/{src → packages/plugin-detail/src}/RecordActivityTimeline.d.ts +0 -0
  199. /package/dist/{src → packages/plugin-detail/src}/RecordChatterPanel.d.ts +0 -0
  200. /package/dist/{src → packages/plugin-detail/src}/RecordComments.d.ts +0 -0
  201. /package/dist/{src → packages/plugin-detail/src}/RecordNavigationEnhanced.d.ts +0 -0
  202. /package/dist/{src → packages/plugin-detail/src}/RelationshipGraph.d.ts +0 -0
  203. /package/dist/{src → packages/plugin-detail/src}/RichTextCommentInput.d.ts +0 -0
  204. /package/dist/{src → packages/plugin-detail/src}/SectionGroup.d.ts +0 -0
  205. /package/dist/{src → packages/plugin-detail/src}/SubscriptionToggle.d.ts +0 -0
  206. /package/dist/{src → packages/plugin-detail/src}/ThreadedReplies.d.ts +0 -0
  207. /package/dist/{src → packages/plugin-detail/src}/autoLayout.d.ts +0 -0
  208. /package/dist/{src → packages/plugin-detail/src}/index.d.ts +0 -0
  209. /package/dist/{src → packages/plugin-detail/src}/useDetailTranslation.d.ts +0 -0
@@ -1,123 +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 { cn } from '@object-ui/components';
11
- import type { Mention } from '@object-ui/types';
12
-
13
- export interface MentionSuggestionItem {
14
- /** Entity ID */
15
- id: string;
16
- /** Display name */
17
- name: string;
18
- /** Avatar URL */
19
- avatarUrl?: string;
20
- /** Entity type */
21
- type: 'user' | 'team' | 'group';
22
- }
23
-
24
- export interface MentionAutocompleteProps {
25
- /** Search query (text after @) */
26
- query: string;
27
- /** Available suggestions */
28
- suggestions: MentionSuggestionItem[];
29
- /** Called when a suggestion is selected */
30
- onSelect: (item: MentionSuggestionItem) => void;
31
- /** Whether the dropdown is visible */
32
- visible?: boolean;
33
- /** Active/highlighted index */
34
- activeIndex?: number;
35
- className?: string;
36
- }
37
-
38
- /**
39
- * MentionAutocomplete — Dropdown for @mention autocomplete.
40
- * Filters suggestions by query and renders a selectable list.
41
- * Produces MentionSchema data on selection.
42
- */
43
- export const MentionAutocomplete: React.FC<MentionAutocompleteProps> = ({
44
- query,
45
- suggestions,
46
- onSelect,
47
- visible = true,
48
- activeIndex = 0,
49
- className,
50
- }) => {
51
- const filtered = React.useMemo(() => {
52
- if (!query) return suggestions;
53
- const q = query.toLowerCase();
54
- return suggestions.filter(
55
- (s) => s.name.toLowerCase().includes(q) || s.id.toLowerCase().includes(q),
56
- );
57
- }, [query, suggestions]);
58
-
59
- if (!visible || filtered.length === 0) return null;
60
-
61
- return (
62
- <div
63
- className={cn(
64
- 'bg-popover border rounded-md shadow-md z-50 max-h-48 overflow-y-auto w-56',
65
- className,
66
- )}
67
- role="listbox"
68
- aria-label="Mention suggestions"
69
- >
70
- {filtered.map((item, index) => (
71
- <button
72
- key={item.id}
73
- type="button"
74
- role="option"
75
- aria-selected={index === activeIndex}
76
- className={cn(
77
- 'w-full text-left px-3 py-1.5 text-sm flex items-center gap-2 hover:bg-accent transition-colors',
78
- index === activeIndex && 'bg-accent',
79
- )}
80
- onMouseDown={(e) => {
81
- e.preventDefault();
82
- onSelect(item);
83
- }}
84
- >
85
- {item.avatarUrl ? (
86
- <img
87
- src={item.avatarUrl}
88
- alt={item.name}
89
- className="h-5 w-5 rounded-full object-cover"
90
- />
91
- ) : (
92
- <div className="h-5 w-5 rounded-full bg-muted flex items-center justify-center text-[10px] font-medium">
93
- {item.name.charAt(0).toUpperCase()}
94
- </div>
95
- )}
96
- <div className="flex-1 min-w-0">
97
- <span className="truncate">{item.name}</span>
98
- {item.type !== 'user' && (
99
- <span className="ml-1 text-xs text-muted-foreground capitalize">({item.type})</span>
100
- )}
101
- </div>
102
- </button>
103
- ))}
104
- </div>
105
- );
106
- };
107
-
108
- /**
109
- * Helper to create a Mention object from a suggestion item.
110
- */
111
- export function createMentionFromSuggestion(
112
- item: MentionSuggestionItem,
113
- offset: number,
114
- length: number,
115
- ): Mention {
116
- return {
117
- type: item.type,
118
- id: item.id,
119
- name: item.name,
120
- offset,
121
- length,
122
- };
123
- }
@@ -1,261 +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
- Button,
13
- Card,
14
- CardHeader,
15
- CardTitle,
16
- CardContent,
17
- } from '@object-ui/components';
18
- import { History, RotateCcw, Eye, ChevronRight } from 'lucide-react';
19
-
20
- export interface RevisionEntry {
21
- id: string;
22
- timestamp: string;
23
- user: string;
24
- changes: { field: string; oldValue: any; newValue: any }[];
25
- /** Full record snapshot at this revision point. */
26
- snapshot?: Record<string, any>;
27
- }
28
-
29
- export interface PointInTimeRestoreProps {
30
- recordId: string;
31
- revisions: RevisionEntry[];
32
- onRestore?: (revisionId: string, snapshot: Record<string, any>) => void | Promise<void>;
33
- className?: string;
34
- }
35
-
36
- function formatTimestamp(timestamp: string): string {
37
- try {
38
- const date = new Date(timestamp);
39
- const now = new Date();
40
- const diffMs = now.getTime() - date.getTime();
41
- const diffMins = Math.floor(diffMs / 60000);
42
-
43
- if (diffMins < 1) return 'just now';
44
- if (diffMins < 60) return `${diffMins}m ago`;
45
- const diffHours = Math.floor(diffMins / 60);
46
- if (diffHours < 24) return `${diffHours}h ago`;
47
- return date.toLocaleString();
48
- } catch {
49
- return timestamp;
50
- }
51
- }
52
-
53
- export const PointInTimeRestore: React.FC<PointInTimeRestoreProps> = ({
54
- recordId: _recordId,
55
- revisions,
56
- onRestore,
57
- className,
58
- }) => {
59
- const [selectedRevisionId, setSelectedRevisionId] = React.useState<string | null>(null);
60
- const [isConfirming, setIsConfirming] = React.useState(false);
61
- const [isRestoring, setIsRestoring] = React.useState(false);
62
-
63
- const selectedRevision = React.useMemo(
64
- () => revisions.find((r) => r.id === selectedRevisionId) ?? null,
65
- [revisions, selectedRevisionId],
66
- );
67
-
68
- const handleRestore = React.useCallback(async () => {
69
- if (!selectedRevision || !onRestore) return;
70
-
71
- if (!isConfirming) {
72
- setIsConfirming(true);
73
- return;
74
- }
75
-
76
- setIsRestoring(true);
77
- try {
78
- const snapshot = selectedRevision.snapshot ?? {};
79
- await onRestore(selectedRevision.id, snapshot);
80
- setIsConfirming(false);
81
- setSelectedRevisionId(null);
82
- } finally {
83
- setIsRestoring(false);
84
- }
85
- }, [selectedRevision, onRestore, isConfirming]);
86
-
87
- const handleCancelConfirm = React.useCallback(() => {
88
- setIsConfirming(false);
89
- }, []);
90
-
91
- return (
92
- <Card className={cn('', className)}>
93
- <CardHeader className="pb-3">
94
- <CardTitle className="flex items-center gap-2 text-base">
95
- <History className="h-4 w-4" />
96
- Revision History
97
- <span className="text-sm font-normal text-muted-foreground">
98
- ({revisions.length})
99
- </span>
100
- </CardTitle>
101
- </CardHeader>
102
- <CardContent>
103
- {revisions.length === 0 ? (
104
- <p className="text-sm text-muted-foreground text-center py-4">
105
- No revisions recorded
106
- </p>
107
- ) : (
108
- <div className="flex flex-col lg:flex-row gap-4">
109
- {/* Timeline */}
110
- <div className="flex-1 min-w-0">
111
- <div className="relative">
112
- <div className="absolute left-3 top-2 bottom-2 w-px bg-border" />
113
- <div className="space-y-1">
114
- {revisions.map((revision) => {
115
- const isSelected = revision.id === selectedRevisionId;
116
- return (
117
- <button
118
- key={revision.id}
119
- type="button"
120
- className={cn(
121
- 'w-full text-left flex items-start gap-3 py-2 px-2 rounded-md transition-colors relative',
122
- isSelected
123
- ? 'bg-accent'
124
- : 'hover:bg-accent/50',
125
- )}
126
- onClick={() => {
127
- setSelectedRevisionId(revision.id);
128
- setIsConfirming(false);
129
- }}
130
- >
131
- {/* Timeline dot */}
132
- <div
133
- className={cn(
134
- 'shrink-0 h-6 w-6 rounded-full border-2 flex items-center justify-center z-10 mt-0.5',
135
- isSelected
136
- ? 'border-primary bg-primary'
137
- : 'border-border bg-background',
138
- )}
139
- >
140
- {isSelected && (
141
- <ChevronRight className="h-3 w-3 text-primary-foreground" />
142
- )}
143
- </div>
144
- {/* Content */}
145
- <div className="flex-1 min-w-0">
146
- <div className="flex items-center gap-2">
147
- <span className="text-sm font-medium">{revision.user}</span>
148
- <span className="text-xs text-muted-foreground">
149
- {formatTimestamp(revision.timestamp)}
150
- </span>
151
- </div>
152
- <p className="text-xs text-muted-foreground mt-0.5">
153
- {revision.changes.length} field{revision.changes.length !== 1 ? 's' : ''}{' '}
154
- changed
155
- </p>
156
- </div>
157
- </button>
158
- );
159
- })}
160
- </div>
161
- </div>
162
- </div>
163
-
164
- {/* Preview panel */}
165
- {selectedRevision && (
166
- <div className="lg:w-80 border rounded-md p-3 space-y-3">
167
- <div className="flex items-center gap-2 text-sm font-medium">
168
- <Eye className="h-4 w-4 text-muted-foreground" />
169
- Revision Preview
170
- </div>
171
-
172
- {/* Field changes */}
173
- <div className="space-y-2">
174
- {selectedRevision.changes.map((change, i) => (
175
- <div key={i} className="text-xs">
176
- <span className="font-medium text-muted-foreground">
177
- {change.field}
178
- </span>
179
- <div className="flex items-center gap-1.5 mt-0.5">
180
- <span className="line-through text-red-600 dark:text-red-400 truncate max-w-[120px]">
181
- {change.oldValue != null ? String(change.oldValue) : '(empty)'}
182
- </span>
183
- <ChevronRight className="h-3 w-3 text-muted-foreground shrink-0" />
184
- <span className="text-green-600 dark:text-green-400 truncate max-w-[120px]">
185
- {change.newValue != null ? String(change.newValue) : '(empty)'}
186
- </span>
187
- </div>
188
- </div>
189
- ))}
190
- </div>
191
-
192
- {/* Snapshot values */}
193
- {selectedRevision.snapshot && (
194
- <div className="border-t pt-2 space-y-1">
195
- <p className="text-xs font-medium text-muted-foreground">
196
- Record state at this point
197
- </p>
198
- <div className="max-h-40 overflow-y-auto space-y-1">
199
- {Object.entries(selectedRevision.snapshot).map(([key, val]) => (
200
- <div key={key} className="flex justify-between text-xs gap-2">
201
- <span className="text-muted-foreground truncate">{key}</span>
202
- <span className="font-mono truncate max-w-[140px]">
203
- {val != null ? String(val) : '–'}
204
- </span>
205
- </div>
206
- ))}
207
- </div>
208
- </div>
209
- )}
210
-
211
- {/* Restore button */}
212
- {onRestore && (
213
- <div className="pt-1 space-y-2">
214
- {isConfirming ? (
215
- <>
216
- <p className="text-xs text-amber-600 dark:text-amber-400">
217
- This will restore the record to its state at{' '}
218
- {formatTimestamp(selectedRevision.timestamp)}. Continue?
219
- </p>
220
- <div className="flex gap-2">
221
- <Button
222
- variant="destructive"
223
- size="sm"
224
- className="gap-1.5 flex-1"
225
- onClick={handleRestore}
226
- disabled={isRestoring}
227
- >
228
- <RotateCcw className="h-3.5 w-3.5" />
229
- {isRestoring ? 'Restoring…' : 'Confirm Restore'}
230
- </Button>
231
- <Button
232
- variant="ghost"
233
- size="sm"
234
- onClick={handleCancelConfirm}
235
- disabled={isRestoring}
236
- >
237
- Cancel
238
- </Button>
239
- </div>
240
- </>
241
- ) : (
242
- <Button
243
- variant="outline"
244
- size="sm"
245
- className="w-full gap-1.5"
246
- onClick={handleRestore}
247
- >
248
- <RotateCcw className="h-3.5 w-3.5" />
249
- Restore to this point
250
- </Button>
251
- )}
252
- </div>
253
- )}
254
- </div>
255
- )}
256
- </div>
257
- )}
258
- </CardContent>
259
- </Card>
260
- );
261
- };
@@ -1,106 +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 { cn, Button } from '@object-ui/components';
11
- import { SmilePlus } from 'lucide-react';
12
- import type { Reaction } from '@object-ui/types';
13
-
14
- const DEFAULT_EMOJI_OPTIONS = ['👍', '❤️', '🎉', '😂', '😮', '😢'];
15
-
16
- export interface ReactionPickerProps {
17
- /** Existing reactions on the feed item */
18
- reactions: Reaction[];
19
- /** Called when user adds or removes a reaction */
20
- onToggleReaction?: (emoji: string) => void | Promise<void>;
21
- /** Available emoji options */
22
- emojiOptions?: string[];
23
- className?: string;
24
- }
25
-
26
- /**
27
- * ReactionPicker — Emoji reaction selector and display.
28
- * Shows existing reactions with counts, and a picker to add/remove.
29
- * Aligned with @objectstack/spec ReactionSchema.
30
- */
31
- export const ReactionPicker: React.FC<ReactionPickerProps> = ({
32
- reactions,
33
- onToggleReaction,
34
- emojiOptions = DEFAULT_EMOJI_OPTIONS,
35
- className,
36
- }) => {
37
- const [showPicker, setShowPicker] = React.useState(false);
38
-
39
- const handleReaction = React.useCallback(
40
- (emoji: string) => {
41
- onToggleReaction?.(emoji);
42
- setShowPicker(false);
43
- },
44
- [onToggleReaction],
45
- );
46
-
47
- return (
48
- <div className={cn('flex items-center gap-1 flex-wrap', className)}>
49
- {/* Existing reactions */}
50
- {reactions.map((reaction) => (
51
- <button
52
- key={reaction.emoji}
53
- type="button"
54
- className={cn(
55
- 'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs border transition-colors',
56
- reaction.reacted
57
- ? 'bg-primary/10 border-primary/30 text-primary'
58
- : 'bg-muted border-border text-muted-foreground hover:bg-muted/80',
59
- )}
60
- onClick={() => handleReaction(reaction.emoji)}
61
- disabled={!onToggleReaction}
62
- aria-label={`${reaction.emoji} ${reaction.count} reaction${reaction.count !== 1 ? 's' : ''}`}
63
- >
64
- <span>{reaction.emoji}</span>
65
- <span>{reaction.count}</span>
66
- </button>
67
- ))}
68
-
69
- {/* Add reaction button */}
70
- {onToggleReaction && (
71
- <div className="relative">
72
- <Button
73
- variant="ghost"
74
- size="icon"
75
- className="h-6 w-6"
76
- onClick={() => setShowPicker(!showPicker)}
77
- aria-label="Add reaction"
78
- >
79
- <SmilePlus className="h-3.5 w-3.5" />
80
- </Button>
81
-
82
- {showPicker && (
83
- <div
84
- className="absolute bottom-full mb-1 left-0 bg-popover border rounded-md shadow-md z-50 p-1.5 flex gap-1"
85
- role="listbox"
86
- aria-label="Emoji picker"
87
- >
88
- {emojiOptions.map((emoji) => (
89
- <button
90
- key={emoji}
91
- type="button"
92
- className="hover:bg-accent rounded p-1 text-base transition-colors"
93
- onClick={() => handleReaction(emoji)}
94
- role="option"
95
- aria-selected={reactions.some(r => r.emoji === emoji && r.reacted)}
96
- >
97
- {emoji}
98
- </button>
99
- ))}
100
- </div>
101
- )}
102
- </div>
103
- )}
104
- </div>
105
- );
106
- };