@object-ui/plugin-detail 3.0.2 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +45 -8
- package/CHANGELOG.md +9 -0
- package/dist/AddressField-C07oUOY6.js +96 -0
- package/dist/AutoNumberField-BxnFqllo.js +8 -0
- package/dist/AvatarField-VThNABzo.js +82 -0
- package/dist/BooleanField-CGHKBzAi.js +37 -0
- package/dist/CodeField-Co_muhRR.js +21 -0
- package/dist/ColorField-DLid_tFz.js +42 -0
- package/dist/CurrencyField-Bw-LqANM.js +43 -0
- package/dist/DateField-BNHAzMB2.js +21 -0
- package/dist/DateTimeField-DjAyn_DQ.js +28 -0
- package/dist/EmailField-xoNcSppb.js +31 -0
- package/dist/FileField-DbNJwjU2.js +133 -0
- package/dist/FormulaField-CJkkwIK8.js +9 -0
- package/dist/GeolocationField-C1AnS6VV.js +123 -0
- package/dist/GridField-DATAHIKf.js +30 -0
- package/dist/ImageField-CEKJpyJp.js +90 -0
- package/dist/LocationField-jDWXjlpx.js +31 -0
- package/dist/LookupField-DQ08L9UQ.js +96 -0
- package/dist/MasterDetailField-Dbk529Ea.js +108 -0
- package/dist/NumberField-BVroN9aV.js +26 -0
- package/dist/ObjectField-CT3l_IHW.js +48 -0
- package/dist/PasswordField-DweVLEE0.js +38 -0
- package/dist/PercentField-ZpWUK97K.js +63 -0
- package/dist/PhoneField-mw-9fqZ_.js +31 -0
- package/dist/QRCodeField-Cbb9ck59.js +77 -0
- package/dist/RatingField-CSqgLS6t.js +47 -0
- package/dist/RichTextField-BpfBOd99.js +38 -0
- package/dist/SelectField-B9Ei-5jl.js +26 -0
- package/dist/SignatureField-DgGpHnQ8.js +85 -0
- package/dist/SliderField-C6HvOHd8.js +30 -0
- package/dist/SummaryField-ugYPYxjP.js +9 -0
- package/dist/TextAreaField-BK3RgzY3.js +39 -0
- package/dist/TextField-Bvzx3atT.js +32 -0
- package/dist/TimeField-Cuz9-Uai.js +21 -0
- package/dist/UrlField-B6XHTV73.js +33 -0
- package/dist/UserField-ooTul2d6.js +49 -0
- package/dist/VectorField-CKg9jdGa.js +25 -0
- package/dist/index-CnlyRfY_.js +59461 -0
- package/dist/index.js +30 -55026
- package/dist/index.umd.cjs +41 -30
- package/dist/plugin-detail.css +1 -1
- package/dist/src/ActivityTimeline.d.ts +20 -0
- package/dist/src/ActivityTimeline.d.ts.map +1 -0
- package/dist/src/CommentAttachment.d.ts +25 -0
- package/dist/src/CommentAttachment.d.ts.map +1 -0
- package/dist/src/CommentInput.d.ts +24 -0
- package/dist/src/CommentInput.d.ts.map +1 -0
- package/dist/src/DetailSection.d.ts +6 -0
- package/dist/src/DetailSection.d.ts.map +1 -1
- package/dist/src/DetailView.d.ts +4 -0
- package/dist/src/DetailView.d.ts.map +1 -1
- package/dist/src/DetailView.stories.d.ts +8 -0
- package/dist/src/DetailView.stories.d.ts.map +1 -1
- package/dist/src/DiffView.d.ts +24 -0
- package/dist/src/DiffView.d.ts.map +1 -0
- package/dist/src/FieldChangeItem.d.ts +21 -0
- package/dist/src/FieldChangeItem.d.ts.map +1 -0
- package/dist/src/InlineCreateRelated.d.ts +32 -0
- package/dist/src/InlineCreateRelated.d.ts.map +1 -0
- package/dist/src/MentionAutocomplete.d.ts +43 -0
- package/dist/src/MentionAutocomplete.d.ts.map +1 -0
- package/dist/src/PointInTimeRestore.d.ts +28 -0
- package/dist/src/PointInTimeRestore.d.ts.map +1 -0
- package/dist/src/ReactionPicker.d.ts +25 -0
- package/dist/src/ReactionPicker.d.ts.map +1 -0
- package/dist/src/RecordActivityTimeline.d.ts +49 -0
- package/dist/src/RecordActivityTimeline.d.ts.map +1 -0
- package/dist/src/RecordChatterPanel.d.ts +48 -0
- package/dist/src/RecordChatterPanel.d.ts.map +1 -0
- package/dist/src/RecordComments.d.ts +20 -0
- package/dist/src/RecordComments.d.ts.map +1 -0
- package/dist/src/RecordNavigationEnhanced.d.ts +18 -0
- package/dist/src/RecordNavigationEnhanced.d.ts.map +1 -0
- package/dist/src/RelatedList.d.ts +4 -0
- package/dist/src/RelatedList.d.ts.map +1 -1
- package/dist/src/RelationshipGraph.d.ts +23 -0
- package/dist/src/RelationshipGraph.d.ts.map +1 -0
- package/dist/src/RichTextCommentInput.d.ts +24 -0
- package/dist/src/RichTextCommentInput.d.ts.map +1 -0
- package/dist/src/SubscriptionToggle.d.ts +22 -0
- package/dist/src/SubscriptionToggle.d.ts.map +1 -0
- package/dist/src/ThreadedReplies.d.ts +26 -0
- package/dist/src/ThreadedReplies.d.ts.map +1 -0
- package/dist/src/autoLayout.d.ts +34 -0
- package/dist/src/autoLayout.d.ts.map +1 -0
- package/dist/src/index.d.ts +36 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/useDetailTranslation.d.ts +34 -0
- package/dist/src/useDetailTranslation.d.ts.map +1 -0
- package/package.json +8 -7
- package/src/ActivityTimeline.tsx +184 -0
- package/src/CommentAttachment.tsx +192 -0
- package/src/CommentInput.tsx +81 -0
- package/src/DetailSection.tsx +74 -9
- package/src/DetailView.stories.tsx +76 -0
- package/src/DetailView.tsx +270 -27
- package/src/DiffView.tsx +231 -0
- package/src/FieldChangeItem.tsx +46 -0
- package/src/InlineCreateRelated.tsx +291 -0
- package/src/MentionAutocomplete.tsx +123 -0
- package/src/PointInTimeRestore.tsx +261 -0
- package/src/ReactionPicker.tsx +106 -0
- package/src/RecordActivityTimeline.tsx +429 -0
- package/src/RecordChatterPanel.tsx +202 -0
- package/src/RecordComments.tsx +215 -0
- package/src/RecordNavigationEnhanced.tsx +211 -0
- package/src/RelatedList.tsx +37 -8
- package/src/RelationshipGraph.tsx +286 -0
- package/src/RichTextCommentInput.tsx +348 -0
- package/src/SubscriptionToggle.tsx +60 -0
- package/src/ThreadedReplies.tsx +161 -0
- package/src/__tests__/ActivityTimeline.test.tsx +119 -0
- package/src/__tests__/ActivityTimelineFiltering.test.tsx +143 -0
- package/src/__tests__/CommentInput.test.tsx +57 -0
- package/src/__tests__/DetailSection.test.tsx +320 -0
- package/src/__tests__/DetailView.test.tsx +415 -1
- package/src/__tests__/FieldChangeItem.test.tsx +119 -0
- package/src/__tests__/MentionAutocomplete.test.tsx +97 -0
- package/src/__tests__/ReactionPicker.test.tsx +113 -0
- package/src/__tests__/RecordActivityTimeline.test.tsx +395 -0
- package/src/__tests__/RecordChatterPanel.test.tsx +227 -0
- package/src/__tests__/RecordComments.test.tsx +96 -0
- package/src/__tests__/RecordCommentsPinSearch.test.tsx +133 -0
- package/src/__tests__/RelatedList.test.tsx +66 -0
- package/src/__tests__/SubscriptionToggle.test.tsx +84 -0
- package/src/__tests__/ThreadedReplies.test.tsx +212 -0
- package/src/__tests__/autoLayout.test.ts +184 -0
- package/src/__tests__/phase12-features.test.tsx +583 -0
- package/src/autoLayout.ts +111 -0
- package/src/index.tsx +46 -0
- package/src/useDetailTranslation.ts +103 -0
package/dist/src/index.d.ts
CHANGED
|
@@ -3,8 +3,44 @@ 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 { inferDetailColumns, isWideFieldType, applyAutoSpan, applyDetailAutoLayout } from './autoLayout';
|
|
7
|
+
export { useDetailTranslation, DETAIL_DEFAULT_TRANSLATIONS, createSafeTranslationHook } from './useDetailTranslation';
|
|
8
|
+
export { RecordComments } from './RecordComments';
|
|
9
|
+
export { ActivityTimeline } from './ActivityTimeline';
|
|
10
|
+
export { InlineCreateRelated } from './InlineCreateRelated';
|
|
11
|
+
export { RichTextCommentInput } from './RichTextCommentInput';
|
|
12
|
+
export { DiffView } from './DiffView';
|
|
13
|
+
export { RecordNavigationEnhanced } from './RecordNavigationEnhanced';
|
|
14
|
+
export { RelationshipGraph } from './RelationshipGraph';
|
|
15
|
+
export { CommentAttachment } from './CommentAttachment';
|
|
16
|
+
export { PointInTimeRestore } from './PointInTimeRestore';
|
|
17
|
+
export { RecordActivityTimeline } from './RecordActivityTimeline';
|
|
18
|
+
export { RecordChatterPanel } from './RecordChatterPanel';
|
|
19
|
+
export { CommentInput } from './CommentInput';
|
|
20
|
+
export { FieldChangeItem } from './FieldChangeItem';
|
|
21
|
+
export { MentionAutocomplete, createMentionFromSuggestion } from './MentionAutocomplete';
|
|
22
|
+
export { SubscriptionToggle } from './SubscriptionToggle';
|
|
23
|
+
export { ReactionPicker } from './ReactionPicker';
|
|
24
|
+
export { ThreadedReplies } from './ThreadedReplies';
|
|
6
25
|
export type { DetailViewProps } from './DetailView';
|
|
7
26
|
export type { DetailSectionProps } from './DetailSection';
|
|
8
27
|
export type { DetailTabsProps } from './DetailTabs';
|
|
9
28
|
export type { RelatedListProps } from './RelatedList';
|
|
29
|
+
export type { RecordCommentsProps } from './RecordComments';
|
|
30
|
+
export type { ActivityTimelineProps, ActivityFilterType } from './ActivityTimeline';
|
|
31
|
+
export type { InlineCreateRelatedProps, RelatedFieldDefinition, RelatedRecordOption } from './InlineCreateRelated';
|
|
32
|
+
export type { RichTextCommentInputProps, MentionSuggestion } from './RichTextCommentInput';
|
|
33
|
+
export type { DiffViewProps, DiffFieldType, DiffMode, DiffLine } from './DiffView';
|
|
34
|
+
export type { RecordNavigationEnhancedProps } from './RecordNavigationEnhanced';
|
|
35
|
+
export type { RelationshipGraphProps, GraphNode } from './RelationshipGraph';
|
|
36
|
+
export type { CommentAttachmentProps, Attachment } from './CommentAttachment';
|
|
37
|
+
export type { PointInTimeRestoreProps, RevisionEntry } from './PointInTimeRestore';
|
|
38
|
+
export type { RecordActivityTimelineProps, FeedFilterMode } from './RecordActivityTimeline';
|
|
39
|
+
export type { RecordChatterPanelProps } from './RecordChatterPanel';
|
|
40
|
+
export type { CommentInputProps } from './CommentInput';
|
|
41
|
+
export type { FieldChangeItemProps } from './FieldChangeItem';
|
|
42
|
+
export type { MentionAutocompleteProps, MentionSuggestionItem } from './MentionAutocomplete';
|
|
43
|
+
export type { SubscriptionToggleProps } from './SubscriptionToggle';
|
|
44
|
+
export type { ReactionPickerProps } from './ReactionPicker';
|
|
45
|
+
export type { ThreadedRepliesProps } from './ThreadedReplies';
|
|
10
46
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -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":"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"}
|
|
@@ -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,CAkC9D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB;aAxEd,MAAM,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;CA2E3D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/plugin-detail",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
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.
|
|
28
|
-
"@object-ui/components": "3.0
|
|
29
|
-
"@object-ui/core": "3.0
|
|
30
|
-
"@object-ui/
|
|
31
|
-
"@object-ui/
|
|
27
|
+
"lucide-react": "^0.576.0",
|
|
28
|
+
"@object-ui/components": "3.1.0",
|
|
29
|
+
"@object-ui/core": "3.1.0",
|
|
30
|
+
"@object-ui/fields": "3.1.0",
|
|
31
|
+
"@object-ui/react": "3.1.0",
|
|
32
|
+
"@object-ui/types": "3.1.0"
|
|
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.
|
|
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
|
+
};
|