@object-ui/plugin-detail 3.1.5 → 3.3.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 +50 -47
- package/CHANGELOG.md +20 -0
- package/dist/{AddressField-DBkEyMcG.js → AddressField-CDLSeyNx.js} +1 -1
- package/dist/{AutoNumberField-Baa191z-.js → AutoNumberField-CtE7suf5.js} +1 -1
- package/dist/{AvatarField-YGj51ozd.js → AvatarField-Xuieq0ZI.js} +1 -1
- package/dist/{BooleanField-CaA898Tk.js → BooleanField-DwfMKknK.js} +1 -1
- package/dist/{CodeField-BU51nl1L.js → CodeField-CfwgRxx2.js} +1 -1
- package/dist/{ColorField-Cnf6ZM7c.js → ColorField-YKHA7dBD.js} +1 -1
- package/dist/{CurrencyField-Wg-XOId2.js → CurrencyField-tvS3fPAF.js} +1 -1
- package/dist/{DateField-Cth1ky_m.js → DateField-BKqXpkOh.js} +1 -1
- package/dist/{DateTimeField-B0m6FhHL.js → DateTimeField-CR-nJCE7.js} +1 -1
- package/dist/{EmailField-Do7qT_L_.js → EmailField-CgvW1Qal.js} +1 -1
- package/dist/{FileField-aRJAdbQb.js → FileField-BVAme2ML.js} +1 -1
- package/dist/{FormulaField-DTMkagFx.js → FormulaField-DamJ2VaG.js} +1 -1
- package/dist/{GeolocationField-RqpHWTEv.js → GeolocationField-C99z7ZBM.js} +1 -1
- package/dist/{GridField-D4IH0cpo.js → GridField-C9JbpTx_.js} +1 -1
- package/dist/{ImageField-BYCFajjr.js → ImageField-CDANtgVV.js} +1 -1
- package/dist/{LocationField-Bi_ew9sd.js → LocationField-ZSyZ0O-h.js} +1 -1
- package/dist/{LookupField-BjwlDPtt.js → LookupField-B3hQJt95.js} +307 -306
- package/dist/LookupField-D00z6gn_.js +2 -0
- package/dist/{MasterDetailField-I1A9oEGC.js → MasterDetailField-B0HTmmD7.js} +1 -1
- package/dist/{NumberField-D_NucQlp.js → NumberField-DL2QAL7X.js} +1 -1
- package/dist/{ObjectField-CG-LaM65.js → ObjectField-JYvUnuRO.js} +1 -1
- package/dist/{PasswordField-DBtluGJ1.js → PasswordField-DVTimsc3.js} +1 -1
- package/dist/{PercentField-B6sO_J3i.js → PercentField-DjR6BSpw.js} +1 -1
- package/dist/{PhoneField-CcQAWwR6.js → PhoneField-CX1JL-jp.js} +1 -1
- package/dist/{QRCodeField-CEjWs-J5.js → QRCodeField-CH_1pU6R.js} +1 -1
- package/dist/{RatingField-B_Mnr63i.js → RatingField-rRi_P0N0.js} +1 -1
- package/dist/{RichTextField-qOEJl5Ai.js → RichTextField-CJqLWlrb.js} +1 -1
- package/dist/SelectField-DGoDoRM_.js +30 -0
- package/dist/SelectField-XBVI50AD.js +2 -0
- package/dist/{SignatureField-CddhEK9u.js → SignatureField-2CnhcWI0.js} +1 -1
- package/dist/{SliderField-Df5hMzNc.js → SliderField-DEpMVXko.js} +1 -1
- package/dist/{SummaryField-DgiFm-Cr.js → SummaryField-7ch9aqAu.js} +1 -1
- package/dist/{TextAreaField-DuriTqsD.js → TextAreaField-Cmw1oXcw.js} +1 -1
- package/dist/{TextField-CGNSl7RU.js → TextField-OTLa3p51.js} +1 -1
- package/dist/{TimeField-YO58ctFg.js → TimeField-DKPoNWoR.js} +1 -1
- package/dist/{UrlField-1-BMM1jn.js → UrlField-CxbmzP9f.js} +1 -1
- package/dist/{UserField-B6GqxP_S.js → UserField-ChvwUkMK.js} +1 -1
- package/dist/{VectorField-BkEjbSt0.js → VectorField-BVClL8Vw.js} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1252 -1199
- package/dist/index.umd.cjs +47 -46
- package/dist/packages/plugin-detail/src/ActivityTimeline.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/CommentAttachment.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/CommentInput.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailSection.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailTabs.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailView.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DetailView.stories.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/DiffView.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/FieldChangeItem.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/HeaderHighlight.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/InlineCreateRelated.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/MentionAutocomplete.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/PointInTimeRestore.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/ReactionPicker.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordActivityTimeline.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordChatterPanel.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordComments.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RecordNavigationEnhanced.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RelatedList.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RelationshipGraph.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/RichTextCommentInput.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/SectionGroup.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/SubscriptionToggle.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/ThreadedReplies.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/autoLayout.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/index.d.ts.map +1 -0
- package/dist/packages/plugin-detail/src/useDetailTranslation.d.ts.map +1 -0
- package/dist/plugin-detail.css +1 -1
- package/dist/{src-CXr1-vVl.js → src-C56Ly5uG.js} +35777 -35335
- package/dist/useFieldTranslation-CkxqyB82.js +9 -0
- package/package.json +10 -10
- package/src/CommentAttachment.tsx +5 -3
- package/src/DiffView.tsx +7 -5
- package/src/RecordActivityTimeline.tsx +21 -17
- package/src/RecordChatterPanel.tsx +9 -7
- package/src/RecordComments.tsx +11 -9
- package/src/RecordNavigationEnhanced.tsx +9 -7
- package/src/RichTextCommentInput.tsx +11 -9
- package/src/SubscriptionToggle.tsx +4 -2
- package/src/ThreadedReplies.tsx +4 -2
- package/src/__tests__/RecordActivityTimeline.test.tsx +9 -9
- package/src/__tests__/RecordChatterPanel.test.tsx +6 -6
- package/src/__tests__/RecordCommentsPinSearch.test.tsx +6 -6
- package/src/useDetailTranslation.ts +69 -0
- package/vite.config.ts +1 -0
- package/dist/SelectField-C8hWu3gm.js +0 -30
- package/dist/src/ActivityTimeline.d.ts.map +0 -1
- package/dist/src/CommentAttachment.d.ts.map +0 -1
- package/dist/src/CommentInput.d.ts.map +0 -1
- package/dist/src/DetailSection.d.ts.map +0 -1
- package/dist/src/DetailTabs.d.ts.map +0 -1
- package/dist/src/DetailView.d.ts.map +0 -1
- package/dist/src/DetailView.stories.d.ts.map +0 -1
- package/dist/src/DiffView.d.ts.map +0 -1
- package/dist/src/FieldChangeItem.d.ts.map +0 -1
- package/dist/src/HeaderHighlight.d.ts.map +0 -1
- package/dist/src/InlineCreateRelated.d.ts.map +0 -1
- package/dist/src/MentionAutocomplete.d.ts.map +0 -1
- package/dist/src/PointInTimeRestore.d.ts.map +0 -1
- package/dist/src/ReactionPicker.d.ts.map +0 -1
- package/dist/src/RecordActivityTimeline.d.ts.map +0 -1
- package/dist/src/RecordChatterPanel.d.ts.map +0 -1
- package/dist/src/RecordComments.d.ts.map +0 -1
- package/dist/src/RecordNavigationEnhanced.d.ts.map +0 -1
- package/dist/src/RelatedList.d.ts.map +0 -1
- package/dist/src/RelationshipGraph.d.ts.map +0 -1
- package/dist/src/RichTextCommentInput.d.ts.map +0 -1
- package/dist/src/SectionGroup.d.ts.map +0 -1
- package/dist/src/SubscriptionToggle.d.ts.map +0 -1
- package/dist/src/ThreadedReplies.d.ts.map +0 -1
- package/dist/src/autoLayout.d.ts.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/useDetailTranslation.d.ts.map +0 -1
- /package/dist/{src → packages/plugin-detail/src}/ActivityTimeline.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/CommentAttachment.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/CommentInput.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailSection.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailTabs.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailView.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DetailView.stories.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/DiffView.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/FieldChangeItem.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/HeaderHighlight.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/InlineCreateRelated.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/MentionAutocomplete.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/PointInTimeRestore.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/ReactionPicker.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordActivityTimeline.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordChatterPanel.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordComments.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RecordNavigationEnhanced.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RelatedList.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RelationshipGraph.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/RichTextCommentInput.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/SectionGroup.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/SubscriptionToggle.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/ThreadedReplies.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/autoLayout.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/index.d.ts +0 -0
- /package/dist/{src → packages/plugin-detail/src}/useDetailTranslation.d.ts +0 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { dn as e } from "./src-C56Ly5uG.js";
|
|
2
|
+
var t = e({
|
|
3
|
+
"common.selectOption": "Select an option",
|
|
4
|
+
"common.select": "Select...",
|
|
5
|
+
"common.search": "Search",
|
|
6
|
+
"table.selected": "{{count}} selected"
|
|
7
|
+
}, "common.selectOption");
|
|
8
|
+
//#endregion
|
|
9
|
+
export { t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/plugin-detail",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.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,12 +24,12 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"lucide-react": "^
|
|
28
|
-
"@object-ui/components": "3.
|
|
29
|
-
"@object-ui/core": "3.
|
|
30
|
-
"@object-ui/
|
|
31
|
-
"@object-ui/
|
|
32
|
-
"@object-ui/
|
|
27
|
+
"lucide-react": "^1.8.0",
|
|
28
|
+
"@object-ui/components": "3.3.0",
|
|
29
|
+
"@object-ui/core": "3.3.0",
|
|
30
|
+
"@object-ui/fields": "3.3.0",
|
|
31
|
+
"@object-ui/react": "3.3.0",
|
|
32
|
+
"@object-ui/types": "3.3.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"@types/react": "19.2.14",
|
|
40
40
|
"@types/react-dom": "19.2.3",
|
|
41
41
|
"@vitejs/plugin-react": "^6.0.1",
|
|
42
|
-
"typescript": "^
|
|
43
|
-
"vite": "^8.0.
|
|
42
|
+
"typescript": "^6.0.2",
|
|
43
|
+
"vite": "^8.0.8",
|
|
44
44
|
"vite-plugin-dts": "^4.5.4",
|
|
45
|
-
"vitest": "^4.1.
|
|
45
|
+
"vitest": "^4.1.4"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "vite build",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import * as React from 'react';
|
|
10
10
|
import { cn, Button } from '@object-ui/components';
|
|
11
11
|
import { Paperclip, X, FileText, Image, FileArchive, File, Upload } from 'lucide-react';
|
|
12
|
+
import { useDetailTranslation } from './useDetailTranslation';
|
|
12
13
|
|
|
13
14
|
export interface Attachment {
|
|
14
15
|
id: string;
|
|
@@ -53,6 +54,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
|
|
|
53
54
|
className,
|
|
54
55
|
readOnly = false,
|
|
55
56
|
}) => {
|
|
57
|
+
const { t } = useDetailTranslation();
|
|
56
58
|
const [isDragOver, setIsDragOver] = React.useState(false);
|
|
57
59
|
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
|
58
60
|
|
|
@@ -117,7 +119,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
|
|
|
117
119
|
>
|
|
118
120
|
<Upload className="h-5 w-5 mx-auto text-muted-foreground mb-1" />
|
|
119
121
|
<p className="text-xs text-muted-foreground">
|
|
120
|
-
|
|
122
|
+
{t('detail.dropFilesToUpload')}
|
|
121
123
|
</p>
|
|
122
124
|
<input
|
|
123
125
|
ref={fileInputRef}
|
|
@@ -135,7 +137,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
|
|
|
135
137
|
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
|
|
136
138
|
<Paperclip className="h-3 w-3" />
|
|
137
139
|
<span>
|
|
138
|
-
{attachments.length
|
|
140
|
+
{attachments.length !== 1 ? t('detail.attachmentCountPlural', { count: attachments.length }) : t('detail.attachmentCount', { count: attachments.length })}
|
|
139
141
|
</span>
|
|
140
142
|
</div>
|
|
141
143
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
|
@@ -176,7 +178,7 @@ export const CommentAttachment: React.FC<CommentAttachmentProps> = ({
|
|
|
176
178
|
size="icon"
|
|
177
179
|
className="h-6 w-6 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
|
|
178
180
|
onClick={() => onRemove(attachment.id)}
|
|
179
|
-
title=
|
|
181
|
+
title={t('detail.removeAttachment')}
|
|
180
182
|
>
|
|
181
183
|
<X className="h-3.5 w-3.5" />
|
|
182
184
|
</Button>
|
package/src/DiffView.tsx
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import * as React from 'react';
|
|
10
10
|
import { cn, Button, Card, CardHeader, CardTitle, CardContent } from '@object-ui/components';
|
|
11
11
|
import { Columns2, Rows3 } from 'lucide-react';
|
|
12
|
+
import { useDetailTranslation } from './useDetailTranslation';
|
|
12
13
|
|
|
13
14
|
export type DiffFieldType = 'string' | 'number' | 'boolean' | 'json' | 'date';
|
|
14
15
|
export type DiffMode = 'unified' | 'side-by-side';
|
|
@@ -98,6 +99,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
|
|
98
99
|
mode: initialMode = 'unified',
|
|
99
100
|
className,
|
|
100
101
|
}) => {
|
|
102
|
+
const { t } = useDetailTranslation();
|
|
101
103
|
const [mode, setMode] = React.useState<DiffMode>(initialMode);
|
|
102
104
|
|
|
103
105
|
const oldLines = React.useMemo(() => valueToLines(oldValue, fieldType), [oldValue, fieldType]);
|
|
@@ -146,7 +148,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
|
|
146
148
|
size="icon"
|
|
147
149
|
className="h-7 w-7"
|
|
148
150
|
onClick={() => setMode('unified')}
|
|
149
|
-
title=
|
|
151
|
+
title={t('detail.unifiedDiff')}
|
|
150
152
|
>
|
|
151
153
|
<Rows3 className="h-3.5 w-3.5" />
|
|
152
154
|
</Button>
|
|
@@ -155,7 +157,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
|
|
155
157
|
size="icon"
|
|
156
158
|
className="h-7 w-7"
|
|
157
159
|
onClick={() => setMode('side-by-side')}
|
|
158
|
-
title=
|
|
160
|
+
title={t('detail.sideBySideDiff')}
|
|
159
161
|
>
|
|
160
162
|
<Columns2 className="h-3.5 w-3.5" />
|
|
161
163
|
</Button>
|
|
@@ -164,7 +166,7 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
|
|
164
166
|
</CardHeader>
|
|
165
167
|
<CardContent className="p-0">
|
|
166
168
|
{!hasChanges ? (
|
|
167
|
-
<p className="px-4 py-3 text-sm text-muted-foreground">
|
|
169
|
+
<p className="px-4 py-3 text-sm text-muted-foreground">{t('detail.noChanges')}</p>
|
|
168
170
|
) : mode === 'unified' ? (
|
|
169
171
|
/* Unified diff view */
|
|
170
172
|
<div className="font-mono text-xs overflow-x-auto">
|
|
@@ -192,10 +194,10 @@ export const DiffView: React.FC<DiffViewProps> = ({
|
|
|
192
194
|
<div className="grid grid-cols-2 divide-x font-mono text-xs min-w-0">
|
|
193
195
|
{/* Headers */}
|
|
194
196
|
<div className="px-3 py-1.5 text-xs font-medium text-muted-foreground bg-muted/50">
|
|
195
|
-
|
|
197
|
+
{t('detail.previousVersion')}
|
|
196
198
|
</div>
|
|
197
199
|
<div className="px-3 py-1.5 text-xs font-medium text-muted-foreground bg-muted/50">
|
|
198
|
-
|
|
200
|
+
{t('detail.currentVersion')}
|
|
199
201
|
</div>
|
|
200
202
|
{/* Rows */}
|
|
201
203
|
{sideBySidePairs.map((pair, index) => (
|
|
@@ -28,6 +28,7 @@ import { FieldChangeItem } from './FieldChangeItem';
|
|
|
28
28
|
import { ReactionPicker } from './ReactionPicker';
|
|
29
29
|
import { ThreadedReplies } from './ThreadedReplies';
|
|
30
30
|
import { SubscriptionToggle } from './SubscriptionToggle';
|
|
31
|
+
import { useDetailTranslation } from './useDetailTranslation';
|
|
31
32
|
|
|
32
33
|
export type FeedFilterMode = 'all' | 'comments_only' | 'changes_only' | 'tasks_only';
|
|
33
34
|
|
|
@@ -81,12 +82,14 @@ const FEED_TYPE_COLORS: Record<FeedItemType, string> = {
|
|
|
81
82
|
call: 'bg-teal-100 text-teal-600',
|
|
82
83
|
};
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
function getFilterOptions(t: (key: string) => string): { value: FeedFilterMode; label: string }[] {
|
|
86
|
+
return [
|
|
87
|
+
{ value: 'all', label: t('detail.allActivity') },
|
|
88
|
+
{ value: 'comments_only', label: t('detail.commentsOnly') },
|
|
89
|
+
{ value: 'changes_only', label: t('detail.fieldChangesFilter') },
|
|
90
|
+
{ value: 'tasks_only', label: t('detail.tasksOnly') },
|
|
91
|
+
];
|
|
92
|
+
}
|
|
90
93
|
|
|
91
94
|
function formatTimestamp(timestamp: string): string {
|
|
92
95
|
try {
|
|
@@ -144,6 +147,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
|
|
|
144
147
|
collapseWhenEmpty = false,
|
|
145
148
|
className,
|
|
146
149
|
}) => {
|
|
150
|
+
const { t } = useDetailTranslation();
|
|
147
151
|
const [internalFilter, setInternalFilter] = React.useState<FeedFilterMode>('all');
|
|
148
152
|
const [commentText, setCommentText] = React.useState('');
|
|
149
153
|
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
@@ -229,7 +233,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
|
|
|
229
233
|
<div className="flex items-center justify-between">
|
|
230
234
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
231
235
|
<Activity className="h-4 w-4" />
|
|
232
|
-
|
|
236
|
+
{t('detail.activity')}
|
|
233
237
|
<span className="text-sm font-normal text-muted-foreground">
|
|
234
238
|
({filtered.length})
|
|
235
239
|
</span>
|
|
@@ -252,9 +256,9 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
|
|
|
252
256
|
className="rounded-md border border-input bg-background px-2.5 py-1.5 text-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
253
257
|
value={activeFilter}
|
|
254
258
|
onChange={(e) => handleFilterChange(e.target.value as FeedFilterMode)}
|
|
255
|
-
aria-label=
|
|
259
|
+
aria-label={t('detail.filterActivity')}
|
|
256
260
|
>
|
|
257
|
-
{
|
|
261
|
+
{getFilterOptions(t).map((opt) => (
|
|
258
262
|
<option key={opt.value} value={opt.value}>
|
|
259
263
|
{opt.label}
|
|
260
264
|
</option>
|
|
@@ -268,7 +272,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
|
|
|
268
272
|
<div className="flex gap-2">
|
|
269
273
|
<textarea
|
|
270
274
|
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"
|
|
271
|
-
placeholder=
|
|
275
|
+
placeholder={t('detail.leaveCommentPlaceholder')}
|
|
272
276
|
value={commentText}
|
|
273
277
|
onChange={(e) => setCommentText(e.target.value)}
|
|
274
278
|
onKeyDown={handleKeyDown}
|
|
@@ -280,7 +284,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
|
|
|
280
284
|
onClick={handleAddComment}
|
|
281
285
|
disabled={!commentText.trim() || isSubmitting}
|
|
282
286
|
className="shrink-0 self-end"
|
|
283
|
-
aria-label=
|
|
287
|
+
aria-label={t('detail.submitComment')}
|
|
284
288
|
>
|
|
285
289
|
<MessageSquare className="h-4 w-4" />
|
|
286
290
|
</Button>
|
|
@@ -291,7 +295,7 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
|
|
|
291
295
|
{filtered.length === 0 ? (
|
|
292
296
|
collapseWhenEmpty ? null : (
|
|
293
297
|
<p className="text-sm text-muted-foreground text-center py-4">
|
|
294
|
-
|
|
298
|
+
{t('detail.noActivity')}
|
|
295
299
|
</p>
|
|
296
300
|
)
|
|
297
301
|
) : (
|
|
@@ -332,17 +336,17 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
|
|
|
332
336
|
<span className="text-sm font-medium">{item.actor}</span>
|
|
333
337
|
{item.source && (
|
|
334
338
|
<span className="text-xs text-muted-foreground">
|
|
335
|
-
via {item.source}
|
|
339
|
+
{t('detail.via', { source: item.source })}
|
|
336
340
|
</span>
|
|
337
341
|
)}
|
|
338
342
|
<span className="text-xs text-muted-foreground">
|
|
339
343
|
{formatTimestamp(item.createdAt)}
|
|
340
344
|
</span>
|
|
341
345
|
{item.edited && (
|
|
342
|
-
<span className="text-xs text-muted-foreground italic">(edited)</span>
|
|
346
|
+
<span className="text-xs text-muted-foreground italic">{t('detail.edited')}</span>
|
|
343
347
|
)}
|
|
344
348
|
{item.pinned && (
|
|
345
|
-
<span className="text-xs text-amber-600">📌
|
|
349
|
+
<span className="text-xs text-amber-600">📌 {t('detail.pinned')}</span>
|
|
346
350
|
)}
|
|
347
351
|
</div>
|
|
348
352
|
|
|
@@ -412,14 +416,14 @@ export const RecordActivityTimeline: React.FC<RecordActivityTimelineProps> = ({
|
|
|
412
416
|
size="sm"
|
|
413
417
|
onClick={handleLoadMore}
|
|
414
418
|
disabled={isLoadingMore}
|
|
415
|
-
aria-label=
|
|
419
|
+
aria-label={t('detail.loadMore')}
|
|
416
420
|
>
|
|
417
421
|
{isLoadingMore ? (
|
|
418
422
|
<Loader2 className="h-4 w-4 animate-spin mr-1" />
|
|
419
423
|
) : (
|
|
420
424
|
<ChevronDown className="h-4 w-4 mr-1" />
|
|
421
425
|
)}
|
|
422
|
-
|
|
426
|
+
{t('detail.loadMore')}
|
|
423
427
|
</Button>
|
|
424
428
|
</div>
|
|
425
429
|
)}
|
|
@@ -12,6 +12,7 @@ import { MessageSquare, PanelRightOpen, PanelRightClose, X } from 'lucide-react'
|
|
|
12
12
|
import type { RecordChatterComponentProps, FeedItem, RecordSubscription } from '@object-ui/types';
|
|
13
13
|
import { RecordActivityTimeline } from './RecordActivityTimeline';
|
|
14
14
|
import type { FeedFilterMode, RecordActivityTimelineProps } from './RecordActivityTimeline';
|
|
15
|
+
import { useDetailTranslation } from './useDetailTranslation';
|
|
15
16
|
|
|
16
17
|
export interface RecordChatterPanelProps {
|
|
17
18
|
/** Chatter panel configuration from RecordChatterComponentProps */
|
|
@@ -73,6 +74,7 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
|
|
|
73
74
|
const collapsible = config?.collapsible ?? true;
|
|
74
75
|
const defaultCollapsed = (collapseWhenEmpty && items.length === 0) || (config?.defaultCollapsed ?? false);
|
|
75
76
|
|
|
77
|
+
const { t } = useDetailTranslation();
|
|
76
78
|
const [collapsed, setCollapsed] = React.useState(defaultCollapsed);
|
|
77
79
|
|
|
78
80
|
const isSidebar = position === 'right' || position === 'left';
|
|
@@ -94,7 +96,7 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
|
|
|
94
96
|
size="icon"
|
|
95
97
|
className="h-8 w-8 mx-1"
|
|
96
98
|
onClick={() => setCollapsed(false)}
|
|
97
|
-
aria-label=
|
|
99
|
+
aria-label={t('detail.openDiscussion')}
|
|
98
100
|
>
|
|
99
101
|
<PanelRightOpen className="h-4 w-4" />
|
|
100
102
|
</Button>
|
|
@@ -115,7 +117,7 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
|
|
|
115
117
|
<div className="flex items-center justify-between px-4 py-3 border-b">
|
|
116
118
|
<div className="flex items-center gap-2">
|
|
117
119
|
<MessageSquare className="h-4 w-4" />
|
|
118
|
-
<span className="text-sm font-medium">
|
|
120
|
+
<span className="text-sm font-medium">{t('detail.discussion')}</span>
|
|
119
121
|
</div>
|
|
120
122
|
{collapsible && (
|
|
121
123
|
<Button
|
|
@@ -123,7 +125,7 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
|
|
|
123
125
|
size="icon"
|
|
124
126
|
className="h-7 w-7"
|
|
125
127
|
onClick={() => setCollapsed(true)}
|
|
126
|
-
aria-label=
|
|
128
|
+
aria-label={t('detail.closeDiscussion')}
|
|
127
129
|
>
|
|
128
130
|
<X className="h-3.5 w-3.5" />
|
|
129
131
|
</Button>
|
|
@@ -161,10 +163,10 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
|
|
|
161
163
|
variant="ghost"
|
|
162
164
|
className="w-full justify-start gap-2 text-muted-foreground"
|
|
163
165
|
onClick={() => setCollapsed(false)}
|
|
164
|
-
aria-label=
|
|
166
|
+
aria-label={t('detail.showDiscussion', { count: items.length })}
|
|
165
167
|
>
|
|
166
168
|
<MessageSquare className="h-4 w-4" />
|
|
167
|
-
<span>
|
|
169
|
+
<span>{t('detail.showDiscussion', { count: items.length })}</span>
|
|
168
170
|
</Button>
|
|
169
171
|
) : (
|
|
170
172
|
<div>
|
|
@@ -172,14 +174,14 @@ export const RecordChatterPanel: React.FC<RecordChatterPanelProps> = ({
|
|
|
172
174
|
<div className="flex items-center justify-between mb-2">
|
|
173
175
|
<div className="flex items-center gap-2 text-sm font-medium">
|
|
174
176
|
<MessageSquare className="h-4 w-4" />
|
|
175
|
-
|
|
177
|
+
{t('detail.discussion')}
|
|
176
178
|
</div>
|
|
177
179
|
<Button
|
|
178
180
|
variant="ghost"
|
|
179
181
|
size="icon"
|
|
180
182
|
className="h-7 w-7"
|
|
181
183
|
onClick={() => setCollapsed(true)}
|
|
182
|
-
aria-label=
|
|
184
|
+
aria-label={t('detail.hideDiscussion')}
|
|
183
185
|
>
|
|
184
186
|
<PanelRightClose className="h-3.5 w-3.5" />
|
|
185
187
|
</Button>
|
package/src/RecordComments.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import * as React from 'react';
|
|
|
10
10
|
import { cn, Button, Card, CardHeader, CardTitle, CardContent } from '@object-ui/components';
|
|
11
11
|
import { MessageSquare, Send, Pin, Search, X } from 'lucide-react';
|
|
12
12
|
import type { CommentEntry } from '@object-ui/types';
|
|
13
|
+
import { useDetailTranslation } from './useDetailTranslation';
|
|
13
14
|
|
|
14
15
|
export interface RecordCommentsProps {
|
|
15
16
|
comments: CommentEntry[];
|
|
@@ -50,6 +51,7 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
|
|
|
50
51
|
searchable = false,
|
|
51
52
|
className,
|
|
52
53
|
}) => {
|
|
54
|
+
const { t } = useDetailTranslation();
|
|
53
55
|
const [newComment, setNewComment] = React.useState('');
|
|
54
56
|
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
55
57
|
const [searchQuery, setSearchQuery] = React.useState('');
|
|
@@ -98,7 +100,7 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
|
|
|
98
100
|
<CardHeader>
|
|
99
101
|
<CardTitle className="flex items-center gap-2 text-base">
|
|
100
102
|
<MessageSquare className="h-4 w-4" />
|
|
101
|
-
|
|
103
|
+
{t('detail.comments')}
|
|
102
104
|
<span className="text-sm font-normal text-muted-foreground">
|
|
103
105
|
({comments.length})
|
|
104
106
|
</span>
|
|
@@ -112,16 +114,16 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
|
|
|
112
114
|
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
|
113
115
|
<input
|
|
114
116
|
className="w-full rounded-md border border-input bg-background pl-8 pr-8 py-1.5 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
115
|
-
placeholder=
|
|
117
|
+
placeholder={t('detail.searchComments')}
|
|
116
118
|
value={searchQuery}
|
|
117
119
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
118
|
-
aria-label=
|
|
120
|
+
aria-label={t('detail.searchComments')}
|
|
119
121
|
/>
|
|
120
122
|
{searchQuery && (
|
|
121
123
|
<button
|
|
122
124
|
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
|
123
125
|
onClick={() => setSearchQuery('')}
|
|
124
|
-
aria-label=
|
|
126
|
+
aria-label={t('detail.clearSearch')}
|
|
125
127
|
type="button"
|
|
126
128
|
>
|
|
127
129
|
<X className="h-3.5 w-3.5" />
|
|
@@ -136,7 +138,7 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
|
|
|
136
138
|
<div className="flex gap-2">
|
|
137
139
|
<textarea
|
|
138
140
|
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"
|
|
139
|
-
placeholder=
|
|
141
|
+
placeholder={t('detail.addCommentPlaceholder')}
|
|
140
142
|
value={newComment}
|
|
141
143
|
onChange={(e) => setNewComment(e.target.value)}
|
|
142
144
|
onKeyDown={handleKeyDown}
|
|
@@ -157,7 +159,7 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
|
|
|
157
159
|
{/* Comment List */}
|
|
158
160
|
{sortedComments.length === 0 ? (
|
|
159
161
|
<p className="text-sm text-muted-foreground text-center py-4">
|
|
160
|
-
{searchQuery.trim() ? '
|
|
162
|
+
{searchQuery.trim() ? t('detail.noMatchingComments') : t('detail.noCommentsYet')}
|
|
161
163
|
</p>
|
|
162
164
|
) : (
|
|
163
165
|
<div className="space-y-3">
|
|
@@ -187,7 +189,7 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
|
|
|
187
189
|
{comment.pinned && (
|
|
188
190
|
<span className="text-xs text-amber-600 flex items-center gap-0.5">
|
|
189
191
|
<Pin className="h-3 w-3" />
|
|
190
|
-
|
|
192
|
+
{t('detail.pinned')}
|
|
191
193
|
</span>
|
|
192
194
|
)}
|
|
193
195
|
</div>
|
|
@@ -198,10 +200,10 @@ export const RecordComments: React.FC<RecordCommentsProps> = ({
|
|
|
198
200
|
type="button"
|
|
199
201
|
className="mt-1 text-xs text-muted-foreground hover:text-foreground flex items-center gap-1"
|
|
200
202
|
onClick={() => onTogglePin(comment.id)}
|
|
201
|
-
aria-label={comment.pinned ? '
|
|
203
|
+
aria-label={comment.pinned ? t('detail.unpin') : t('detail.pin')}
|
|
202
204
|
>
|
|
203
205
|
<Pin className="h-3 w-3" />
|
|
204
|
-
{comment.pinned ? '
|
|
206
|
+
{comment.pinned ? t('detail.unpin') : t('detail.pin')}
|
|
205
207
|
</button>
|
|
206
208
|
)}
|
|
207
209
|
</div>
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
ChevronRight,
|
|
16
16
|
Search,
|
|
17
17
|
} from 'lucide-react';
|
|
18
|
+
import { useDetailTranslation } from './useDetailTranslation';
|
|
18
19
|
|
|
19
20
|
export interface RecordNavigationEnhancedProps {
|
|
20
21
|
currentIndex: number;
|
|
@@ -33,6 +34,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
|
|
|
33
34
|
onSearch,
|
|
34
35
|
className,
|
|
35
36
|
}) => {
|
|
37
|
+
const { t } = useDetailTranslation();
|
|
36
38
|
const [searchQuery, setSearchQuery] = React.useState('');
|
|
37
39
|
const [isSearchOpen, setIsSearchOpen] = React.useState(false);
|
|
38
40
|
const searchInputRef = React.useRef<HTMLInputElement>(null);
|
|
@@ -133,7 +135,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
|
|
|
133
135
|
className="h-8 w-8"
|
|
134
136
|
disabled={!canGoFirst}
|
|
135
137
|
onClick={handleFirst}
|
|
136
|
-
title=
|
|
138
|
+
title={t('detail.firstRecord')}
|
|
137
139
|
>
|
|
138
140
|
<ChevronsLeft className="h-4 w-4" />
|
|
139
141
|
</Button>
|
|
@@ -145,14 +147,14 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
|
|
|
145
147
|
className="h-8 w-8"
|
|
146
148
|
disabled={!canGoPrev}
|
|
147
149
|
onClick={handlePrev}
|
|
148
|
-
title=
|
|
150
|
+
title={t('detail.previousRecordKey')}
|
|
149
151
|
>
|
|
150
152
|
<ChevronLeft className="h-4 w-4" />
|
|
151
153
|
</Button>
|
|
152
154
|
|
|
153
155
|
{/* Position indicator */}
|
|
154
156
|
<span className="text-xs text-muted-foreground whitespace-nowrap px-1.5 tabular-nums">
|
|
155
|
-
{totalRecords > 0 ?
|
|
157
|
+
{totalRecords > 0 ? t('detail.recordOf', { current: currentIndex + 1, total: totalRecords }) : t('detail.noRecords')}
|
|
156
158
|
</span>
|
|
157
159
|
|
|
158
160
|
{/* Next */}
|
|
@@ -162,7 +164,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
|
|
|
162
164
|
className="h-8 w-8"
|
|
163
165
|
disabled={!canGoNext}
|
|
164
166
|
onClick={handleNext}
|
|
165
|
-
title=
|
|
167
|
+
title={t('detail.nextRecordKey')}
|
|
166
168
|
>
|
|
167
169
|
<ChevronRight className="h-4 w-4" />
|
|
168
170
|
</Button>
|
|
@@ -174,7 +176,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
|
|
|
174
176
|
className="h-8 w-8"
|
|
175
177
|
disabled={!canGoLast}
|
|
176
178
|
onClick={handleLast}
|
|
177
|
-
title=
|
|
179
|
+
title={t('detail.lastRecord')}
|
|
178
180
|
>
|
|
179
181
|
<ChevronsRight className="h-4 w-4" />
|
|
180
182
|
</Button>
|
|
@@ -187,7 +189,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
|
|
|
187
189
|
size="icon"
|
|
188
190
|
className="h-8 w-8"
|
|
189
191
|
onClick={handleToggleSearch}
|
|
190
|
-
title=
|
|
192
|
+
title={t('detail.searchWhileNavigating')}
|
|
191
193
|
>
|
|
192
194
|
<Search className="h-4 w-4" />
|
|
193
195
|
</Button>
|
|
@@ -197,7 +199,7 @@ export const RecordNavigationEnhanced: React.FC<RecordNavigationEnhancedProps> =
|
|
|
197
199
|
<Input
|
|
198
200
|
ref={searchInputRef}
|
|
199
201
|
type="text"
|
|
200
|
-
placeholder=
|
|
202
|
+
placeholder={t('detail.searchRecords')}
|
|
201
203
|
value={searchQuery}
|
|
202
204
|
onChange={handleSearchChange}
|
|
203
205
|
className="h-8 w-48 text-sm"
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
Edit,
|
|
19
19
|
Send,
|
|
20
20
|
} from 'lucide-react';
|
|
21
|
+
import { useDetailTranslation } from './useDetailTranslation';
|
|
21
22
|
|
|
22
23
|
export interface MentionSuggestion {
|
|
23
24
|
id: string;
|
|
@@ -71,10 +72,11 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
|
|
|
71
72
|
onChange,
|
|
72
73
|
onSubmit,
|
|
73
74
|
mentionSuggestions = [],
|
|
74
|
-
placeholder
|
|
75
|
+
placeholder,
|
|
75
76
|
className,
|
|
76
77
|
disabled = false,
|
|
77
78
|
}) => {
|
|
79
|
+
const { t } = useDetailTranslation();
|
|
78
80
|
const [isPreview, setIsPreview] = React.useState(false);
|
|
79
81
|
const [showMentions, setShowMentions] = React.useState(false);
|
|
80
82
|
const [mentionQuery, setMentionQuery] = React.useState('');
|
|
@@ -214,7 +216,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
|
|
|
214
216
|
className="h-7 w-7"
|
|
215
217
|
onClick={handleBold}
|
|
216
218
|
disabled={disabled || isPreview}
|
|
217
|
-
title=
|
|
219
|
+
title={t('detail.bold')}
|
|
218
220
|
>
|
|
219
221
|
<Bold className="h-3.5 w-3.5" />
|
|
220
222
|
</Button>
|
|
@@ -224,7 +226,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
|
|
|
224
226
|
className="h-7 w-7"
|
|
225
227
|
onClick={handleItalic}
|
|
226
228
|
disabled={disabled || isPreview}
|
|
227
|
-
title=
|
|
229
|
+
title={t('detail.italic')}
|
|
228
230
|
>
|
|
229
231
|
<Italic className="h-3.5 w-3.5" />
|
|
230
232
|
</Button>
|
|
@@ -234,7 +236,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
|
|
|
234
236
|
className="h-7 w-7"
|
|
235
237
|
onClick={handleList}
|
|
236
238
|
disabled={disabled || isPreview}
|
|
237
|
-
title=
|
|
239
|
+
title={t('detail.listFormat')}
|
|
238
240
|
>
|
|
239
241
|
<List className="h-3.5 w-3.5" />
|
|
240
242
|
</Button>
|
|
@@ -244,7 +246,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
|
|
|
244
246
|
className="h-7 w-7"
|
|
245
247
|
onClick={handleCode}
|
|
246
248
|
disabled={disabled || isPreview}
|
|
247
|
-
title=
|
|
249
|
+
title={t('detail.inlineCode')}
|
|
248
250
|
>
|
|
249
251
|
<Code className="h-3.5 w-3.5" />
|
|
250
252
|
</Button>
|
|
@@ -254,7 +256,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
|
|
|
254
256
|
className="h-7 w-7"
|
|
255
257
|
onClick={handleMentionTrigger}
|
|
256
258
|
disabled={disabled || isPreview}
|
|
257
|
-
title=
|
|
259
|
+
title={t('detail.mentionSomeone')}
|
|
258
260
|
>
|
|
259
261
|
<AtSign className="h-3.5 w-3.5" />
|
|
260
262
|
</Button>
|
|
@@ -266,7 +268,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
|
|
|
266
268
|
size="icon"
|
|
267
269
|
className="h-7 w-7"
|
|
268
270
|
onClick={() => setIsPreview(!isPreview)}
|
|
269
|
-
title={isPreview ? '
|
|
271
|
+
title={isPreview ? t('detail.edit') : t('detail.preview')}
|
|
270
272
|
>
|
|
271
273
|
{isPreview ? (
|
|
272
274
|
<Edit className="h-3.5 w-3.5" />
|
|
@@ -282,7 +284,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
|
|
|
282
284
|
className="h-7 w-7"
|
|
283
285
|
onClick={onSubmit}
|
|
284
286
|
disabled={disabled || !value.trim()}
|
|
285
|
-
title=
|
|
287
|
+
title={t('detail.submitComment')}
|
|
286
288
|
>
|
|
287
289
|
<Send className="h-3.5 w-3.5" />
|
|
288
290
|
</Button>
|
|
@@ -301,7 +303,7 @@ export const RichTextCommentInput: React.FC<RichTextCommentInputProps> = ({
|
|
|
301
303
|
<textarea
|
|
302
304
|
ref={textareaRef}
|
|
303
305
|
className="w-full min-h-[80px] px-3 py-2 text-sm bg-transparent resize-none focus:outline-none placeholder:text-muted-foreground"
|
|
304
|
-
placeholder={placeholder}
|
|
306
|
+
placeholder={placeholder ?? t('detail.writeComment')}
|
|
305
307
|
value={value}
|
|
306
308
|
onChange={handleTextChange}
|
|
307
309
|
onKeyDown={handleKeyDown}
|
|
@@ -10,6 +10,7 @@ import * as React from 'react';
|
|
|
10
10
|
import { cn, Button } from '@object-ui/components';
|
|
11
11
|
import { Bell, BellOff } from 'lucide-react';
|
|
12
12
|
import type { RecordSubscription } from '@object-ui/types';
|
|
13
|
+
import { useDetailTranslation } from './useDetailTranslation';
|
|
13
14
|
|
|
14
15
|
export interface SubscriptionToggleProps {
|
|
15
16
|
/** Current subscription state */
|
|
@@ -28,6 +29,7 @@ export const SubscriptionToggle: React.FC<SubscriptionToggleProps> = ({
|
|
|
28
29
|
onToggle,
|
|
29
30
|
className,
|
|
30
31
|
}) => {
|
|
32
|
+
const { t } = useDetailTranslation();
|
|
31
33
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
32
34
|
|
|
33
35
|
const handleToggle = React.useCallback(async () => {
|
|
@@ -47,8 +49,8 @@ export const SubscriptionToggle: React.FC<SubscriptionToggleProps> = ({
|
|
|
47
49
|
className={cn('h-8 w-8', className)}
|
|
48
50
|
onClick={handleToggle}
|
|
49
51
|
disabled={isLoading || !onToggle}
|
|
50
|
-
aria-label={subscription.subscribed ? '
|
|
51
|
-
title={subscription.subscribed ? '
|
|
52
|
+
aria-label={subscription.subscribed ? t('detail.unsubscribeAriaLabel') : t('detail.subscribeAriaLabel')}
|
|
53
|
+
title={subscription.subscribed ? t('detail.subscribedTooltip') : t('detail.unsubscribedTooltip')}
|
|
52
54
|
>
|
|
53
55
|
{subscription.subscribed ? (
|
|
54
56
|
<Bell className="h-4 w-4 text-primary" />
|
package/src/ThreadedReplies.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import * as React from 'react';
|
|
|
10
10
|
import { cn, Button } from '@object-ui/components';
|
|
11
11
|
import { MessageSquare, ChevronDown, ChevronRight, Send } from 'lucide-react';
|
|
12
12
|
import type { FeedItem } from '@object-ui/types';
|
|
13
|
+
import { useDetailTranslation } from './useDetailTranslation';
|
|
13
14
|
|
|
14
15
|
export interface ThreadedRepliesProps {
|
|
15
16
|
/** Parent feed item (root comment) */
|
|
@@ -53,6 +54,7 @@ export const ThreadedReplies: React.FC<ThreadedRepliesProps> = ({
|
|
|
53
54
|
showReplyInput = true,
|
|
54
55
|
className,
|
|
55
56
|
}) => {
|
|
57
|
+
const { t } = useDetailTranslation();
|
|
56
58
|
const [expanded, setExpanded] = React.useState(false);
|
|
57
59
|
const [replyText, setReplyText] = React.useState('');
|
|
58
60
|
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
@@ -97,7 +99,7 @@ export const ThreadedReplies: React.FC<ThreadedRepliesProps> = ({
|
|
|
97
99
|
<ChevronRight className="h-3 w-3" />
|
|
98
100
|
)}
|
|
99
101
|
<MessageSquare className="h-3 w-3" />
|
|
100
|
-
<span>{replies.length
|
|
102
|
+
<span>{replies.length === 1 ? t('detail.replyCount', { count: replies.length }) : t('detail.replyCountPlural', { count: replies.length })}</span>
|
|
101
103
|
</button>
|
|
102
104
|
)}
|
|
103
105
|
|
|
@@ -138,7 +140,7 @@ export const ThreadedReplies: React.FC<ThreadedRepliesProps> = ({
|
|
|
138
140
|
<div className="flex gap-1.5 mt-1.5">
|
|
139
141
|
<input
|
|
140
142
|
className="flex-1 rounded-md border border-input bg-background px-2 py-1 text-xs placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
|
|
141
|
-
placeholder=
|
|
143
|
+
placeholder={t('detail.replyPlaceholder')}
|
|
142
144
|
value={replyText}
|
|
143
145
|
onChange={(e) => setReplyText(e.target.value)}
|
|
144
146
|
onKeyDown={handleKeyDown}
|