@tuturuuu/ui 0.7.0 → 0.8.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/CHANGELOG.md +48 -0
- package/package.json +8 -8
- package/src/components/ui/currency-input.test.tsx +43 -0
- package/src/components/ui/currency-input.tsx +1 -1
- package/src/components/ui/custom/workspace-access/workspace-access-default-role-card.tsx +60 -35
- package/src/components/ui/custom/workspace-access/workspace-access-member-row.tsx +176 -167
- package/src/components/ui/custom/workspace-access/workspace-access-members.tsx +16 -10
- package/src/components/ui/custom/workspace-access/workspace-access-page-header.tsx +75 -36
- package/src/components/ui/custom/workspace-access/workspace-access-page.tsx +39 -42
- package/src/components/ui/custom/workspace-access/workspace-access-people-filters.tsx +1 -1
- package/src/components/ui/custom/workspace-access/workspace-access-roles.tsx +113 -91
- package/src/components/ui/custom/workspace-access/workspace-access-tabs-toolbar.tsx +73 -32
- package/src/components/ui/finance/transactions/form-types.ts +2 -0
- package/src/components/ui/finance/transactions/transaction-card.tsx +21 -9
- package/src/components/ui/money-input.test.tsx +64 -0
- package/src/components/ui/money-input.tsx +63 -0
- package/src/components/ui/storefront/cart-summary.tsx +114 -29
- package/src/components/ui/storefront/checkout-overlay.tsx +27 -0
- package/src/components/ui/storefront/hero-panel.tsx +2 -8
- package/src/components/ui/storefront/image-panel.tsx +6 -0
- package/src/components/ui/storefront/index.ts +11 -0
- package/src/components/ui/storefront/listing-card.tsx +84 -22
- package/src/components/ui/storefront/product-detail.tsx +289 -0
- package/src/components/ui/storefront/product-dialog.tsx +72 -0
- package/src/components/ui/storefront/storefront-surface.test.tsx +124 -1
- package/src/components/ui/storefront/storefront-surface.tsx +333 -133
- package/src/components/ui/storefront/types.ts +23 -1
- package/src/components/ui/storefront/utils.ts +111 -27
- package/src/components/ui/text-editor/__tests__/content-migration.test.ts +32 -0
- package/src/components/ui/text-editor/__tests__/image-extension.test.ts +69 -1
- package/src/components/ui/text-editor/__tests__/video-extension.test.ts +47 -0
- package/src/components/ui/text-editor/content-migration.ts +41 -18
- package/src/components/ui/text-editor/extensions.ts +1 -1
- package/src/components/ui/text-editor/image-extension.ts +40 -18
- package/src/components/ui/text-editor/video-extension.ts +11 -2
- package/src/components/ui/tu-do/boards/__tests__/workspace-projects-client-page.test.tsx +70 -1
- package/src/components/ui/tu-do/boards/boardId/board-column-external-retry.test.tsx +127 -0
- package/src/components/ui/tu-do/boards/boardId/board-column.tsx +1 -3
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/task-drag-cache.ts +13 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.test.ts +63 -0
- package/src/components/ui/tu-do/boards/boardId/kanban/dnd/use-kanban-dnd.ts +46 -8
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.test.tsx +13 -2
- package/src/components/ui/tu-do/boards/boardId/kanban/rendering/kanban-columns.tsx +3 -1
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.test.tsx +164 -0
- package/src/components/ui/tu-do/boards/boardId/task-board-server-page.tsx +56 -2
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-display.ts +9 -0
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-grid.tsx +8 -16
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-task-row.tsx +5 -25
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.test.ts +36 -1
- package/src/components/ui/tu-do/boards/boardId/timeline/timeline-utils.ts +51 -2
- package/src/components/ui/tu-do/boards/workspace-projects-client-page.tsx +13 -3
- package/src/components/ui/tu-do/shared/__tests__/board-views.test.tsx +34 -1
- package/src/components/ui/tu-do/shared/board-header.tsx +39 -0
- package/src/components/ui/tu-do/shared/board-views.tsx +9 -7
- package/src/components/ui/tu-do/shared/cursor-overlay-multi-wrapper.tsx +53 -12
- package/src/components/ui/tu-do/shared/task-dialog-presentation.test.ts +53 -0
- package/src/components/ui/tu-do/shared/task-dialog-presentation.ts +19 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.test.tsx +57 -0
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx +136 -111
- package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx +3 -1
- package/src/components/ui/tu-do/shared/task-edit-dialog.tsx +42 -14
- package/src/hooks/__tests__/useBoardRealtime.test.tsx +2 -2
- package/src/hooks/__tests__/useCursorTracking.test.tsx +212 -0
- package/src/hooks/useBoardRealtime.ts +6 -3
- package/src/hooks/useBoardRealtime.types.ts +11 -0
- package/src/hooks/useCursorTracking.ts +91 -27
- package/src/hooks/useTaskUserRealtime.ts +5 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { resolveTaskDialogOpeningPresentation } from './task-dialog-presentation';
|
|
3
|
+
|
|
4
|
+
describe('resolveTaskDialogOpeningPresentation', () => {
|
|
5
|
+
it('opens existing document-list tasks fullscreen', () => {
|
|
6
|
+
expect(
|
|
7
|
+
resolveTaskDialogOpeningPresentation({
|
|
8
|
+
defaultPresentation: 'compact',
|
|
9
|
+
mode: 'edit',
|
|
10
|
+
selectedListStatus: 'documents',
|
|
11
|
+
})
|
|
12
|
+
).toBe('fullscreen');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('keeps create mode compact even in document lists', () => {
|
|
16
|
+
expect(
|
|
17
|
+
resolveTaskDialogOpeningPresentation({
|
|
18
|
+
defaultPresentation: 'fullscreen',
|
|
19
|
+
mode: 'create',
|
|
20
|
+
selectedListStatus: 'documents',
|
|
21
|
+
})
|
|
22
|
+
).toBe('compact');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('respects the user default for existing non-document tasks', () => {
|
|
26
|
+
expect(
|
|
27
|
+
resolveTaskDialogOpeningPresentation({
|
|
28
|
+
defaultPresentation: 'compact',
|
|
29
|
+
mode: 'edit',
|
|
30
|
+
selectedListStatus: 'active',
|
|
31
|
+
})
|
|
32
|
+
).toBe('compact');
|
|
33
|
+
|
|
34
|
+
expect(
|
|
35
|
+
resolveTaskDialogOpeningPresentation({
|
|
36
|
+
defaultPresentation: 'fullscreen',
|
|
37
|
+
mode: 'edit',
|
|
38
|
+
selectedListStatus: 'not_started',
|
|
39
|
+
})
|
|
40
|
+
).toBe('fullscreen');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('keeps drafts fullscreen', () => {
|
|
44
|
+
expect(
|
|
45
|
+
resolveTaskDialogOpeningPresentation({
|
|
46
|
+
defaultPresentation: 'compact',
|
|
47
|
+
draftId: 'draft-1',
|
|
48
|
+
mode: 'create',
|
|
49
|
+
selectedListStatus: 'documents',
|
|
50
|
+
})
|
|
51
|
+
).toBe('fullscreen');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -2,6 +2,7 @@ export const TASK_DIALOG_DEFAULT_PRESENTATION_CONFIG_ID =
|
|
|
2
2
|
'TASK_DIALOG_DEFAULT_PRESENTATION';
|
|
3
3
|
|
|
4
4
|
export type TaskDialogPresentation = 'compact' | 'fullscreen';
|
|
5
|
+
export type TaskDialogMode = 'edit' | 'create';
|
|
5
6
|
|
|
6
7
|
export function normalizeTaskDialogPresentation(
|
|
7
8
|
value: unknown,
|
|
@@ -9,3 +10,21 @@ export function normalizeTaskDialogPresentation(
|
|
|
9
10
|
): TaskDialogPresentation {
|
|
10
11
|
return value === 'fullscreen' || value === 'compact' ? value : fallback;
|
|
11
12
|
}
|
|
13
|
+
|
|
14
|
+
export function resolveTaskDialogOpeningPresentation({
|
|
15
|
+
defaultPresentation,
|
|
16
|
+
draftId,
|
|
17
|
+
mode = 'edit',
|
|
18
|
+
selectedListStatus,
|
|
19
|
+
}: {
|
|
20
|
+
defaultPresentation?: unknown;
|
|
21
|
+
draftId?: string;
|
|
22
|
+
mode?: TaskDialogMode;
|
|
23
|
+
selectedListStatus?: string | null;
|
|
24
|
+
}): TaskDialogPresentation {
|
|
25
|
+
if (draftId) return 'fullscreen';
|
|
26
|
+
if (mode === 'create') return 'compact';
|
|
27
|
+
if (selectedListStatus === 'documents') return 'fullscreen';
|
|
28
|
+
|
|
29
|
+
return normalizeTaskDialogPresentation(defaultPresentation);
|
|
30
|
+
}
|
|
@@ -178,6 +178,63 @@ describe('CompactTaskCreatePopover', () => {
|
|
|
178
178
|
).not.toBeInTheDocument();
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
+
it('renders compact description preview without affecting panel layout', () => {
|
|
182
|
+
const onDescriptionPreviewClick = vi.fn();
|
|
183
|
+
|
|
184
|
+
render(
|
|
185
|
+
<Dialog open={true}>
|
|
186
|
+
<CompactTaskCreatePopover
|
|
187
|
+
title="Edit task"
|
|
188
|
+
titleInput={<input aria-label="Task title" defaultValue="Existing" />}
|
|
189
|
+
propertyControls={
|
|
190
|
+
<button type="button" aria-label="List: Inbox">
|
|
191
|
+
List
|
|
192
|
+
</button>
|
|
193
|
+
}
|
|
194
|
+
descriptionPreview="Confirm the plan and publish the final notes."
|
|
195
|
+
descriptionPreviewLabel="Open full task"
|
|
196
|
+
onDescriptionPreviewClick={onDescriptionPreviewClick}
|
|
197
|
+
onClose={vi.fn()}
|
|
198
|
+
onFullscreen={vi.fn()}
|
|
199
|
+
/>
|
|
200
|
+
</Dialog>
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const preview = screen.getByTestId('compact-task-description-preview');
|
|
204
|
+
|
|
205
|
+
expect(preview).toHaveTextContent(
|
|
206
|
+
'Confirm the plan and publish the final notes.'
|
|
207
|
+
);
|
|
208
|
+
expect(preview).toHaveAttribute('aria-label', 'Open full task');
|
|
209
|
+
expect(preview).toHaveClass('absolute', 'top-full');
|
|
210
|
+
|
|
211
|
+
fireEvent.click(preview);
|
|
212
|
+
|
|
213
|
+
expect(onDescriptionPreviewClick).toHaveBeenCalledTimes(1);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('omits compact description preview when the caller does not provide one', () => {
|
|
217
|
+
render(
|
|
218
|
+
<Dialog open={true}>
|
|
219
|
+
<CompactTaskCreatePopover
|
|
220
|
+
title="Create task"
|
|
221
|
+
titleInput={<input aria-label="Task title" defaultValue="New" />}
|
|
222
|
+
propertyControls={
|
|
223
|
+
<button type="button" aria-label="List: Inbox">
|
|
224
|
+
List
|
|
225
|
+
</button>
|
|
226
|
+
}
|
|
227
|
+
onClose={vi.fn()}
|
|
228
|
+
onFullscreen={vi.fn()}
|
|
229
|
+
/>
|
|
230
|
+
</Dialog>
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
expect(
|
|
234
|
+
screen.queryByTestId('compact-task-description-preview')
|
|
235
|
+
).not.toBeInTheDocument();
|
|
236
|
+
});
|
|
237
|
+
|
|
181
238
|
it('renders compact edit actions when provided', () => {
|
|
182
239
|
const onDelete = vi.fn();
|
|
183
240
|
const onDone = vi.fn();
|
package/src/components/ui/tu-do/shared/task-edit-dialog/components/compact-task-create-popover.tsx
CHANGED
|
@@ -25,6 +25,8 @@ interface CompactTaskDialogPanelProps {
|
|
|
25
25
|
iconRingClass?: string;
|
|
26
26
|
titleInput: ReactNode;
|
|
27
27
|
showHeaderTitle?: boolean;
|
|
28
|
+
descriptionPreview?: string | null;
|
|
29
|
+
descriptionPreviewLabel?: string;
|
|
28
30
|
taskStatus?: ReactNode;
|
|
29
31
|
propertyControls: ReactNode;
|
|
30
32
|
editActions?: ReactNode;
|
|
@@ -39,6 +41,7 @@ interface CompactTaskDialogPanelProps {
|
|
|
39
41
|
onCreateMultipleChange?: (value: boolean) => void;
|
|
40
42
|
onClose: () => void;
|
|
41
43
|
onFullscreen: () => void;
|
|
44
|
+
onDescriptionPreviewClick?: () => void;
|
|
42
45
|
onSave?: () => void;
|
|
43
46
|
}
|
|
44
47
|
|
|
@@ -84,6 +87,8 @@ export function CompactTaskDialogPanel({
|
|
|
84
87
|
iconRingClass = 'ring-dynamic-orange/20',
|
|
85
88
|
titleInput,
|
|
86
89
|
showHeaderTitle = true,
|
|
90
|
+
descriptionPreview,
|
|
91
|
+
descriptionPreviewLabel,
|
|
87
92
|
taskStatus,
|
|
88
93
|
propertyControls,
|
|
89
94
|
editActions,
|
|
@@ -98,6 +103,7 @@ export function CompactTaskDialogPanel({
|
|
|
98
103
|
onCreateMultipleChange,
|
|
99
104
|
onClose,
|
|
100
105
|
onFullscreen,
|
|
106
|
+
onDescriptionPreviewClick,
|
|
101
107
|
onSave,
|
|
102
108
|
}: CompactTaskDialogPanelProps) {
|
|
103
109
|
const t = useTranslations();
|
|
@@ -114,127 +120,146 @@ export function CompactTaskDialogPanel({
|
|
|
114
120
|
const hasHeaderTitle = showHeaderTitle;
|
|
115
121
|
|
|
116
122
|
return (
|
|
117
|
-
<div
|
|
118
|
-
data-testid="compact-task-dialog-panel"
|
|
119
|
-
className="flex max-h-[calc(100vh-2rem)] min-h-0 flex-col overflow-hidden rounded-lg bg-background"
|
|
120
|
-
>
|
|
123
|
+
<div className="relative">
|
|
121
124
|
<div
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
hasHeaderTitle ? 'justify-between' : 'justify-end'
|
|
125
|
-
)}
|
|
125
|
+
data-testid="compact-task-dialog-panel"
|
|
126
|
+
className="flex max-h-[calc(100vh-2rem)] min-h-0 flex-col overflow-hidden rounded-lg bg-background"
|
|
126
127
|
>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
128
|
+
<div
|
|
129
|
+
className={cn(
|
|
130
|
+
'flex items-start gap-3 border-b px-4 py-3',
|
|
131
|
+
hasHeaderTitle ? 'justify-between' : 'justify-end'
|
|
132
|
+
)}
|
|
133
|
+
>
|
|
134
|
+
{hasHeaderTitle ? (
|
|
135
|
+
<div className="flex min-w-0 items-start gap-2.5">
|
|
136
|
+
<div
|
|
137
|
+
className={cn(
|
|
138
|
+
'mt-0.5 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg ring-1',
|
|
139
|
+
iconBgClass,
|
|
140
|
+
iconRingClass
|
|
141
|
+
)}
|
|
142
|
+
>
|
|
143
|
+
{icon ?? <ListTodo className="h-4 w-4 text-dynamic-orange" />}
|
|
144
|
+
</div>
|
|
145
|
+
<div className="min-w-0 space-y-0.5">
|
|
146
|
+
<DialogTitle className="truncate font-semibold text-base">
|
|
147
|
+
{title}
|
|
148
|
+
</DialogTitle>
|
|
149
|
+
{description && (
|
|
150
|
+
<DialogDescription className="truncate text-muted-foreground text-xs">
|
|
151
|
+
{description}
|
|
152
|
+
</DialogDescription>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
147
155
|
</div>
|
|
156
|
+
) : (
|
|
157
|
+
<DialogTitle className="sr-only">{title}</DialogTitle>
|
|
158
|
+
)}
|
|
159
|
+
<div className="flex shrink-0 items-center gap-1">
|
|
160
|
+
{smartAction}
|
|
161
|
+
{editActions}
|
|
162
|
+
<Tooltip>
|
|
163
|
+
<TooltipTrigger asChild>
|
|
164
|
+
<Button
|
|
165
|
+
type="button"
|
|
166
|
+
variant="ghost"
|
|
167
|
+
size="icon"
|
|
168
|
+
aria-label={t('ws-task-boards.dialog.open_fullscreen')}
|
|
169
|
+
className="h-8 w-8 text-muted-foreground hover:text-foreground"
|
|
170
|
+
onClick={onFullscreen}
|
|
171
|
+
>
|
|
172
|
+
<Maximize2 className="h-4 w-4" />
|
|
173
|
+
</Button>
|
|
174
|
+
</TooltipTrigger>
|
|
175
|
+
<TooltipContent side="bottom">
|
|
176
|
+
{t('ws-task-boards.dialog.open_fullscreen')}
|
|
177
|
+
</TooltipContent>
|
|
178
|
+
</Tooltip>
|
|
179
|
+
<Tooltip>
|
|
180
|
+
<TooltipTrigger asChild>
|
|
181
|
+
<Button
|
|
182
|
+
type="button"
|
|
183
|
+
variant="ghost"
|
|
184
|
+
size="icon"
|
|
185
|
+
aria-label={t('common.close')}
|
|
186
|
+
className="h-8 w-8 text-muted-foreground hover:text-foreground"
|
|
187
|
+
onClick={onClose}
|
|
188
|
+
>
|
|
189
|
+
<X className="h-4 w-4" />
|
|
190
|
+
</Button>
|
|
191
|
+
</TooltipTrigger>
|
|
192
|
+
<TooltipContent side="bottom">{t('common.close')}</TooltipContent>
|
|
193
|
+
</Tooltip>
|
|
148
194
|
</div>
|
|
149
|
-
) : (
|
|
150
|
-
<DialogTitle className="sr-only">{title}</DialogTitle>
|
|
151
|
-
)}
|
|
152
|
-
<div className="flex shrink-0 items-center gap-1">
|
|
153
|
-
{smartAction}
|
|
154
|
-
{editActions}
|
|
155
|
-
<Tooltip>
|
|
156
|
-
<TooltipTrigger asChild>
|
|
157
|
-
<Button
|
|
158
|
-
type="button"
|
|
159
|
-
variant="ghost"
|
|
160
|
-
size="icon"
|
|
161
|
-
aria-label={t('ws-task-boards.dialog.open_fullscreen')}
|
|
162
|
-
className="h-8 w-8 text-muted-foreground hover:text-foreground"
|
|
163
|
-
onClick={onFullscreen}
|
|
164
|
-
>
|
|
165
|
-
<Maximize2 className="h-4 w-4" />
|
|
166
|
-
</Button>
|
|
167
|
-
</TooltipTrigger>
|
|
168
|
-
<TooltipContent side="bottom">
|
|
169
|
-
{t('ws-task-boards.dialog.open_fullscreen')}
|
|
170
|
-
</TooltipContent>
|
|
171
|
-
</Tooltip>
|
|
172
|
-
<Tooltip>
|
|
173
|
-
<TooltipTrigger asChild>
|
|
174
|
-
<Button
|
|
175
|
-
type="button"
|
|
176
|
-
variant="ghost"
|
|
177
|
-
size="icon"
|
|
178
|
-
aria-label={t('common.close')}
|
|
179
|
-
className="h-8 w-8 text-muted-foreground hover:text-foreground"
|
|
180
|
-
onClick={onClose}
|
|
181
|
-
>
|
|
182
|
-
<X className="h-4 w-4" />
|
|
183
|
-
</Button>
|
|
184
|
-
</TooltipTrigger>
|
|
185
|
-
<TooltipContent side="bottom">{t('common.close')}</TooltipContent>
|
|
186
|
-
</Tooltip>
|
|
187
195
|
</div>
|
|
188
|
-
</div>
|
|
189
196
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
197
|
+
<div className="min-h-0 space-y-3 overflow-y-auto px-4 py-3">
|
|
198
|
+
{titleInput}
|
|
199
|
+
{taskStatus}
|
|
200
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
201
|
+
{propertyControls}
|
|
202
|
+
</div>
|
|
203
|
+
{smartPanel}
|
|
195
204
|
</div>
|
|
196
|
-
{smartPanel}
|
|
197
|
-
</div>
|
|
198
205
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
206
|
+
{hasCreateActions && (
|
|
207
|
+
<div className="flex items-center justify-between gap-2 border-t bg-muted/20 px-4 py-3">
|
|
208
|
+
<div className="flex items-center gap-1">
|
|
209
|
+
<CompactIconButton
|
|
210
|
+
active={!!saveAsDraft}
|
|
211
|
+
label={t('task-drafts.save_as_draft')}
|
|
212
|
+
onClick={() => onSaveAsDraftChange?.(!saveAsDraft)}
|
|
213
|
+
>
|
|
214
|
+
<FileEdit className="h-4 w-4" />
|
|
215
|
+
</CompactIconButton>
|
|
216
|
+
<CompactIconButton
|
|
217
|
+
active={!!createMultiple}
|
|
218
|
+
label={t('ws-task-boards.dialog.create_multiple')}
|
|
219
|
+
onClick={() => onCreateMultipleChange?.(!createMultiple)}
|
|
220
|
+
>
|
|
221
|
+
<Copy className="h-4 w-4" />
|
|
222
|
+
</CompactIconButton>
|
|
223
|
+
<QuickSettingsPopover isPersonalWorkspace={isPersonalWorkspace} />
|
|
224
|
+
</div>
|
|
225
|
+
<Button
|
|
226
|
+
type="button"
|
|
227
|
+
size="sm"
|
|
228
|
+
disabled={!canSave}
|
|
229
|
+
onClick={() => onSave?.()}
|
|
230
|
+
className="min-w-28"
|
|
213
231
|
>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
232
|
+
{isLoading ? (
|
|
233
|
+
<>
|
|
234
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
235
|
+
{t('ws-task-boards.dialog.saving')}
|
|
236
|
+
</>
|
|
237
|
+
) : (
|
|
238
|
+
<>
|
|
239
|
+
<Check className="h-4 w-4" />
|
|
240
|
+
{saveLabel}
|
|
241
|
+
</>
|
|
242
|
+
)}
|
|
243
|
+
</Button>
|
|
217
244
|
</div>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
</Button>
|
|
237
|
-
</div>
|
|
245
|
+
)}
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
{descriptionPreview && onDescriptionPreviewClick && (
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
data-testid="compact-task-description-preview"
|
|
252
|
+
aria-label={
|
|
253
|
+
descriptionPreviewLabel ??
|
|
254
|
+
t('ws-task-boards.dialog.open_fullscreen')
|
|
255
|
+
}
|
|
256
|
+
className="absolute top-full left-1/2 mt-2 w-full max-w-[30rem] -translate-x-1/2 rounded-lg border bg-background/95 px-4 py-3 text-left opacity-70 shadow-xl ring-1 ring-border/60 backdrop-blur transition hover:bg-muted/70 hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
257
|
+
onClick={onDescriptionPreviewClick}
|
|
258
|
+
>
|
|
259
|
+
<span className="line-clamp-3 whitespace-pre-line text-muted-foreground text-sm leading-relaxed">
|
|
260
|
+
{descriptionPreview}
|
|
261
|
+
</span>
|
|
262
|
+
</button>
|
|
238
263
|
)}
|
|
239
264
|
</div>
|
|
240
265
|
);
|
package/src/components/ui/tu-do/shared/task-edit-dialog/components/task-description-editor.tsx
CHANGED
|
@@ -4,6 +4,7 @@ import type { QueryClient } from '@tanstack/react-query';
|
|
|
4
4
|
import type { Editor, JSONContent } from '@tiptap/react';
|
|
5
5
|
import { Loader2 } from '@tuturuuu/icons';
|
|
6
6
|
import type { TaskList } from '@tuturuuu/types/primitives/TaskList';
|
|
7
|
+
import { getBoardRealtimeChannelName } from '@tuturuuu/ui/hooks/useBoardRealtime.types';
|
|
7
8
|
import { toast } from '@tuturuuu/ui/sonner';
|
|
8
9
|
import { cn } from '@tuturuuu/utils/format';
|
|
9
10
|
import { useTranslations } from 'next-intl';
|
|
@@ -352,8 +353,9 @@ export function TaskDescriptionEditor({
|
|
|
352
353
|
|
|
353
354
|
{showCollaborationCursors && taskId && (
|
|
354
355
|
<CursorOverlayMultiWrapper
|
|
355
|
-
channelName={
|
|
356
|
+
channelName={getBoardRealtimeChannelName(boardId)}
|
|
356
357
|
containerRef={richTextEditorRef}
|
|
358
|
+
cursorScope={{ taskId, type: 'task-description' }}
|
|
357
359
|
/>
|
|
358
360
|
)}
|
|
359
361
|
|
|
@@ -47,6 +47,7 @@ import { createInitialSuggestionState } from './mention-system/types';
|
|
|
47
47
|
import { SyncWarningDialog } from './sync-warning-dialog';
|
|
48
48
|
import {
|
|
49
49
|
normalizeTaskDialogPresentation,
|
|
50
|
+
resolveTaskDialogOpeningPresentation,
|
|
50
51
|
type TaskDialogPresentation,
|
|
51
52
|
} from './task-dialog-presentation';
|
|
52
53
|
import { CompactTaskDialogPanel } from './task-edit-dialog/components/compact-task-create-popover';
|
|
@@ -107,6 +108,7 @@ import {
|
|
|
107
108
|
getDescriptionContent,
|
|
108
109
|
getDraftStorageKey,
|
|
109
110
|
getTaskDescriptionPercentLeft,
|
|
111
|
+
getTaskDescriptionPreviewText,
|
|
110
112
|
getTaskDescriptionStorageLength,
|
|
111
113
|
saveAndVerifyYjsDescriptionToDatabase,
|
|
112
114
|
saveYjsDescriptionToDatabase,
|
|
@@ -494,6 +496,23 @@ export function TaskEditDialog({
|
|
|
494
496
|
taskSearchQuery,
|
|
495
497
|
sharedContext,
|
|
496
498
|
});
|
|
499
|
+
const currentList = availableLists?.find(
|
|
500
|
+
(list) => list.id === formState.selectedListId
|
|
501
|
+
);
|
|
502
|
+
const normalizedDefaultPresentation = useMemo(
|
|
503
|
+
() => normalizeTaskDialogPresentation(defaultPresentation),
|
|
504
|
+
[defaultPresentation]
|
|
505
|
+
);
|
|
506
|
+
const openingPresentation = useMemo(
|
|
507
|
+
() =>
|
|
508
|
+
resolveTaskDialogOpeningPresentation({
|
|
509
|
+
defaultPresentation: normalizedDefaultPresentation,
|
|
510
|
+
draftId,
|
|
511
|
+
mode,
|
|
512
|
+
selectedListStatus: currentList?.status,
|
|
513
|
+
}),
|
|
514
|
+
[currentList?.status, draftId, mode, normalizedDefaultPresentation]
|
|
515
|
+
);
|
|
497
516
|
|
|
498
517
|
// Update browser tab title
|
|
499
518
|
useEffect(() => {
|
|
@@ -552,13 +571,8 @@ export function TaskEditDialog({
|
|
|
552
571
|
useState(false);
|
|
553
572
|
const [showShareDialog, setShowShareDialog] = useState(false);
|
|
554
573
|
const [saveAsDraft, setSaveAsDraft] = useState(draftModeEnabled);
|
|
555
|
-
const
|
|
556
|
-
()
|
|
557
|
-
[defaultPresentation]
|
|
558
|
-
);
|
|
559
|
-
const [presentation, setPresentation] = useState<TaskDialogPresentation>(
|
|
560
|
-
normalizedDefaultPresentation
|
|
561
|
-
);
|
|
574
|
+
const [presentation, setPresentation] =
|
|
575
|
+
useState<TaskDialogPresentation>(openingPresentation);
|
|
562
576
|
const [smartSuggestions, setSmartSuggestions] = useState<
|
|
563
577
|
WorkspaceTaskSuggestionTask[]
|
|
564
578
|
>([]);
|
|
@@ -849,9 +863,6 @@ export function TaskEditDialog({
|
|
|
849
863
|
onUpdate,
|
|
850
864
|
});
|
|
851
865
|
|
|
852
|
-
const currentList = availableLists?.find(
|
|
853
|
-
(list) => list.id === formState.selectedListId
|
|
854
|
-
);
|
|
855
866
|
const doneList = availableLists?.find(
|
|
856
867
|
(list) => list.status === 'done' && !list.deleted
|
|
857
868
|
);
|
|
@@ -1946,7 +1957,7 @@ export function TaskEditDialog({
|
|
|
1946
1957
|
previousOpenRef.current = isOpen;
|
|
1947
1958
|
|
|
1948
1959
|
if (!isOpen) {
|
|
1949
|
-
setPresentation(
|
|
1960
|
+
setPresentation(openingPresentation);
|
|
1950
1961
|
setSmartSuggestions([]);
|
|
1951
1962
|
setSelectedSmartSuggestionIds([]);
|
|
1952
1963
|
setSmartSuggestionError(null);
|
|
@@ -1957,9 +1968,14 @@ export function TaskEditDialog({
|
|
|
1957
1968
|
}
|
|
1958
1969
|
|
|
1959
1970
|
if (justOpened) {
|
|
1960
|
-
setPresentation(
|
|
1971
|
+
setPresentation(openingPresentation);
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
if (!isCreateMode && currentList?.status === 'documents') {
|
|
1976
|
+
setPresentation('fullscreen');
|
|
1961
1977
|
}
|
|
1962
|
-
}, [
|
|
1978
|
+
}, [currentList?.status, isCreateMode, isOpen, openingPresentation]);
|
|
1963
1979
|
|
|
1964
1980
|
// Track whether the title input is scrolled out of view
|
|
1965
1981
|
useEffect(() => {
|
|
@@ -2023,6 +2039,15 @@ export function TaskEditDialog({
|
|
|
2023
2039
|
]);
|
|
2024
2040
|
|
|
2025
2041
|
const showCompactDialog = presentation === 'compact' && !draftId;
|
|
2042
|
+
const compactDescriptionPreview = useMemo(() => {
|
|
2043
|
+
if (isCreateMode) return null;
|
|
2044
|
+
|
|
2045
|
+
const previewText = getTaskDescriptionPreviewText(
|
|
2046
|
+
formState.description
|
|
2047
|
+
).trim();
|
|
2048
|
+
|
|
2049
|
+
return previewText || null;
|
|
2050
|
+
}, [formState.description, isCreateMode]);
|
|
2026
2051
|
const taskHydrationNotice = taskLoadError ? (
|
|
2027
2052
|
<div
|
|
2028
2053
|
className="mx-4 mb-2 flex items-center justify-between gap-3 rounded-md border border-dynamic-red/30 bg-dynamic-red/10 px-3 py-2 text-dynamic-red text-sm md:mx-8"
|
|
@@ -2235,7 +2260,7 @@ export function TaskEditDialog({
|
|
|
2235
2260
|
showCloseButton={false}
|
|
2236
2261
|
className={
|
|
2237
2262
|
showCompactDialog
|
|
2238
|
-
? 'w-[min(calc(100vw-2rem),30rem)] max-w-[30rem] gap-0 overflow-
|
|
2263
|
+
? 'w-[min(calc(100vw-2rem),30rem)] max-w-[30rem] gap-0 overflow-visible rounded-lg border p-0 shadow-xl'
|
|
2239
2264
|
: 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:slide-out-to-bottom-2 data-[state=open]:slide-in-from-bottom-2 inset-0! top-0! left-0! flex h-screen max-h-screen w-screen max-w-none! translate-x-0! translate-y-0! gap-0 rounded-none! border-0 p-0'
|
|
2240
2265
|
}
|
|
2241
2266
|
onContextMenu={(e) => {
|
|
@@ -2271,6 +2296,8 @@ export function TaskEditDialog({
|
|
|
2271
2296
|
iconBgClass={compactHeaderInfo.iconBgClass}
|
|
2272
2297
|
iconRingClass={compactHeaderInfo.iconRingClass}
|
|
2273
2298
|
showHeaderTitle={isCreateMode}
|
|
2299
|
+
descriptionPreview={compactDescriptionPreview}
|
|
2300
|
+
descriptionPreviewLabel={dialogT('open_fullscreen')}
|
|
2274
2301
|
titleInput={
|
|
2275
2302
|
<TaskNameInput
|
|
2276
2303
|
name={formState.name}
|
|
@@ -2311,6 +2338,7 @@ export function TaskEditDialog({
|
|
|
2311
2338
|
}
|
|
2312
2339
|
onClose={handleAttemptClose}
|
|
2313
2340
|
onFullscreen={() => setPresentation('fullscreen')}
|
|
2341
|
+
onDescriptionPreviewClick={() => setPresentation('fullscreen')}
|
|
2314
2342
|
onSave={
|
|
2315
2343
|
isCreateMode && !taskControlsDisabled ? handleSave : undefined
|
|
2316
2344
|
}
|
|
@@ -248,7 +248,7 @@ describe('useBoardRealtime', () => {
|
|
|
248
248
|
expect(mockChannel.subscribe).toHaveBeenCalledTimes(1);
|
|
249
249
|
});
|
|
250
250
|
|
|
251
|
-
it('should create channel with self: false config', () => {
|
|
251
|
+
it('should create a private channel with self: false config', () => {
|
|
252
252
|
renderHook(() => useBoardRealtime('board-1', { enabled: true }), {
|
|
253
253
|
wrapper,
|
|
254
254
|
});
|
|
@@ -258,7 +258,7 @@ describe('useBoardRealtime', () => {
|
|
|
258
258
|
)();
|
|
259
259
|
expect(supabaseInstance.channel).toHaveBeenCalledWith(
|
|
260
260
|
'board-realtime-board-1',
|
|
261
|
-
{ config: { broadcast: { self: false } } }
|
|
261
|
+
{ config: { broadcast: { self: false }, private: true } }
|
|
262
262
|
);
|
|
263
263
|
});
|
|
264
264
|
|