tycho-components 0.41.0 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,6 +33,10 @@ export declare const CommentsTexts: {
33
33
  "tab.write": string;
34
34
  "tab.preview": string;
35
35
  "placeholder.preview.empty": string;
36
+ "menu.undock": string;
37
+ "menu.dock.left": string;
38
+ "menu.dock.bottom": string;
39
+ "menu.dock.right": string;
36
40
  };
37
41
  "pt-BR": {
38
42
  "label.title.comments": string;
@@ -68,6 +72,10 @@ export declare const CommentsTexts: {
68
72
  "tab.write": string;
69
73
  "tab.preview": string;
70
74
  "placeholder.preview.empty": string;
75
+ "menu.undock": string;
76
+ "menu.dock.left": string;
77
+ "menu.dock.bottom": string;
78
+ "menu.dock.right": string;
71
79
  };
72
80
  it: {
73
81
  "label.title.comments": string;
@@ -103,5 +111,9 @@ export declare const CommentsTexts: {
103
111
  "tab.write": string;
104
112
  "tab.preview": string;
105
113
  "placeholder.preview.empty": string;
114
+ "menu.undock": string;
115
+ "menu.dock.left": string;
116
+ "menu.dock.bottom": string;
117
+ "menu.dock.right": string;
106
118
  };
107
119
  };
@@ -33,6 +33,10 @@ export const CommentsTexts = {
33
33
  "tab.write": "Write",
34
34
  "tab.preview": "Preview",
35
35
  "placeholder.preview.empty": "Nothing to preview",
36
+ "menu.undock": "Undock into separate window",
37
+ "menu.dock.left": "Dock to left",
38
+ "menu.dock.bottom": "Dock to bottom",
39
+ "menu.dock.right": "Dock to right",
36
40
  },
37
41
  "pt-BR": {
38
42
  "label.title.comments": "Comentários",
@@ -68,6 +72,10 @@ export const CommentsTexts = {
68
72
  "tab.write": "Escrever",
69
73
  "tab.preview": "Visualizar",
70
74
  "placeholder.preview.empty": "Nada para visualizar",
75
+ "menu.undock": "Desencaixar em janela separada",
76
+ "menu.dock.left": "Encaixar à esquerda",
77
+ "menu.dock.bottom": "Encaixar embaixo",
78
+ "menu.dock.right": "Encaixar à direita",
71
79
  },
72
80
  it: {
73
81
  "label.title.comments": "Commenti",
@@ -103,5 +111,9 @@ export const CommentsTexts = {
103
111
  "tab.write": "Scrivi",
104
112
  "tab.preview": "Anteprima",
105
113
  "placeholder.preview.empty": "Niente da visualizzare",
114
+ "menu.undock": "Sgancia in una finestra separata",
115
+ "menu.dock.left": "Aggancia a sinistra",
116
+ "menu.dock.bottom": "Aggancia in basso",
117
+ "menu.dock.right": "Aggancia a destra",
106
118
  },
107
119
  };
@@ -0,0 +1,24 @@
1
+ export type DockPosition = 'left' | 'right' | 'bottom' | 'undocked';
2
+ export type DockedPosition = Exclude<DockPosition, 'undocked'>;
3
+ declare const CommentUtils: {
4
+ isDockPosition: (value: string | null) => value is DockPosition;
5
+ isDockedPosition: (value: DockPosition) => value is DockedPosition;
6
+ clampDrawerWidth: (width: number) => number;
7
+ clampDrawerHeight: (height: number) => number;
8
+ readStoredDrawerWidth: () => number;
9
+ readStoredDrawerHeight: () => number;
10
+ persistDrawerWidth: (width: number) => void;
11
+ persistDrawerHeight: (height: number) => void;
12
+ readStoredDockPosition: () => DockPosition;
13
+ persistDockPosition: (position: DockedPosition) => void;
14
+ getUndockedPopupSize: () => {
15
+ width: number;
16
+ height: number;
17
+ };
18
+ copyStylesToPopup: (popup: Window) => void;
19
+ openCommentsPopup: () => {
20
+ popup: Window;
21
+ container: HTMLElement;
22
+ } | null;
23
+ };
24
+ export default CommentUtils;
@@ -0,0 +1,175 @@
1
+ const DRAWER_WIDTH_STORAGE_KEY = 'tycho-comments-drawer-width';
2
+ const DRAWER_HEIGHT_STORAGE_KEY = 'tycho-comments-drawer-height';
3
+ const DOCK_POSITION_STORAGE_KEY = 'tycho-comments-dock-position';
4
+ const DRAWER_MIN_WIDTH = 320;
5
+ const DRAWER_MIN_HEIGHT = 200;
6
+ const POPUP_NAME = 'tycho-comments';
7
+ const POPUP_MIN_WIDTH = 960;
8
+ const POPUP_MIN_HEIGHT = 720;
9
+ const isDockPosition = (value) => value === 'left' ||
10
+ value === 'right' ||
11
+ value === 'bottom' ||
12
+ value === 'undocked';
13
+ const isDockedPosition = (value) => value !== 'undocked';
14
+ const getDefaultDrawerWidth = () => {
15
+ if (typeof window === 'undefined')
16
+ return 480;
17
+ const { innerWidth } = window;
18
+ if (innerWidth <= 767)
19
+ return Math.round(innerWidth * 0.9);
20
+ if (innerWidth <= 768)
21
+ return Math.round(innerWidth * 0.6);
22
+ return Math.round(innerWidth * 0.4);
23
+ };
24
+ const getDefaultDrawerHeight = () => {
25
+ if (typeof window === 'undefined')
26
+ return 360;
27
+ return Math.round(window.innerHeight * 0.4);
28
+ };
29
+ const clampDrawerWidth = (width) => {
30
+ if (typeof window === 'undefined')
31
+ return width;
32
+ const maxWidth = Math.min(Math.round(window.innerWidth * 0.9), window.innerWidth - 48);
33
+ return Math.min(Math.max(width, DRAWER_MIN_WIDTH), maxWidth);
34
+ };
35
+ const clampDrawerHeight = (height) => {
36
+ if (typeof window === 'undefined')
37
+ return height;
38
+ const maxHeight = Math.min(Math.round(window.innerHeight * 0.9), window.innerHeight - 48);
39
+ return Math.min(Math.max(height, DRAWER_MIN_HEIGHT), maxHeight);
40
+ };
41
+ const readStoredDrawerWidth = () => {
42
+ try {
43
+ const stored = localStorage.getItem(DRAWER_WIDTH_STORAGE_KEY);
44
+ if (stored) {
45
+ const parsed = Number(stored);
46
+ if (!Number.isNaN(parsed) && parsed > 0) {
47
+ return clampDrawerWidth(parsed);
48
+ }
49
+ }
50
+ }
51
+ catch {
52
+ // ignore storage access errors
53
+ }
54
+ return clampDrawerWidth(getDefaultDrawerWidth());
55
+ };
56
+ const readStoredDrawerHeight = () => {
57
+ try {
58
+ const stored = localStorage.getItem(DRAWER_HEIGHT_STORAGE_KEY);
59
+ if (stored) {
60
+ const parsed = Number(stored);
61
+ if (!Number.isNaN(parsed) && parsed > 0) {
62
+ return clampDrawerHeight(parsed);
63
+ }
64
+ }
65
+ }
66
+ catch {
67
+ // ignore storage access errors
68
+ }
69
+ return clampDrawerHeight(getDefaultDrawerHeight());
70
+ };
71
+ const persistDrawerWidth = (width) => {
72
+ try {
73
+ localStorage.setItem(DRAWER_WIDTH_STORAGE_KEY, String(width));
74
+ }
75
+ catch {
76
+ // ignore storage access errors
77
+ }
78
+ };
79
+ const persistDrawerHeight = (height) => {
80
+ try {
81
+ localStorage.setItem(DRAWER_HEIGHT_STORAGE_KEY, String(height));
82
+ }
83
+ catch {
84
+ // ignore storage access errors
85
+ }
86
+ };
87
+ const readStoredDockPosition = () => {
88
+ try {
89
+ const stored = localStorage.getItem(DOCK_POSITION_STORAGE_KEY);
90
+ if (isDockPosition(stored) && stored !== 'undocked') {
91
+ return stored;
92
+ }
93
+ }
94
+ catch {
95
+ // ignore storage access errors
96
+ }
97
+ return 'right';
98
+ };
99
+ const persistDockPosition = (position) => {
100
+ try {
101
+ localStorage.setItem(DOCK_POSITION_STORAGE_KEY, position);
102
+ }
103
+ catch {
104
+ // ignore storage access errors
105
+ }
106
+ };
107
+ const getUndockedPopupSize = () => {
108
+ if (typeof window === 'undefined') {
109
+ return { width: POPUP_MIN_WIDTH, height: POPUP_MIN_HEIGHT };
110
+ }
111
+ const availWidth = window.screen.availWidth || window.outerWidth;
112
+ const availHeight = window.screen.availHeight || window.outerHeight;
113
+ const width = Math.min(Math.max(Math.round(availWidth * 0.75), POPUP_MIN_WIDTH), availWidth - 40);
114
+ const height = Math.min(Math.max(Math.round(availHeight * 0.85), POPUP_MIN_HEIGHT), availHeight - 40);
115
+ return { width, height };
116
+ };
117
+ const copyStylesToPopup = (popup) => {
118
+ const { document: popupDoc } = popup;
119
+ popupDoc.head.innerHTML = '';
120
+ popupDoc.title = document.title;
121
+ document.querySelectorAll('link[rel="stylesheet"], style').forEach((node) => {
122
+ popupDoc.head.appendChild(node.cloneNode(true));
123
+ });
124
+ popupDoc.body.className = document.body.className;
125
+ popupDoc.body.style.margin = '0';
126
+ popupDoc.body.style.height = '100%';
127
+ popupDoc.documentElement.style.height = '100%';
128
+ popupDoc.documentElement.style.overflow = 'hidden';
129
+ };
130
+ const openCommentsPopup = () => {
131
+ if (typeof window === 'undefined')
132
+ return null;
133
+ const { width, height } = getUndockedPopupSize();
134
+ const left = Math.round(window.screenX + (window.outerWidth - width) / 2);
135
+ const top = Math.round(window.screenY + (window.outerHeight - height) / 2);
136
+ const features = [
137
+ 'popup=yes',
138
+ `width=${width}`,
139
+ `height=${height}`,
140
+ `left=${left}`,
141
+ `top=${top}`,
142
+ ].join(',');
143
+ const popup = window.open('', POPUP_NAME, features);
144
+ if (!popup)
145
+ return null;
146
+ try {
147
+ popup.resizeTo(width, height);
148
+ popup.moveTo(left, top);
149
+ }
150
+ catch {
151
+ // some browsers block resize/move on already-open named windows
152
+ }
153
+ copyStylesToPopup(popup);
154
+ const container = popup.document.createElement('div');
155
+ container.id = 'tycho-comments-root';
156
+ container.style.height = '100%';
157
+ popup.document.body.appendChild(container);
158
+ return { popup, container };
159
+ };
160
+ const CommentUtils = {
161
+ isDockPosition,
162
+ isDockedPosition,
163
+ clampDrawerWidth,
164
+ clampDrawerHeight,
165
+ readStoredDrawerWidth,
166
+ readStoredDrawerHeight,
167
+ persistDrawerWidth,
168
+ persistDrawerHeight,
169
+ readStoredDockPosition,
170
+ persistDockPosition,
171
+ getUndockedPopupSize,
172
+ copyStylesToPopup,
173
+ openCommentsPopup,
174
+ };
175
+ export default CommentUtils;
@@ -1,48 +1,31 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { Drawer } from '@mui/material';
3
+ import cx from 'classnames';
3
4
  import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
5
+ import { createPortal } from 'react-dom';
4
6
  import { useTranslation } from 'react-i18next';
5
- import { Button, IconButton } from 'tycho-storybook';
7
+ import { Button, DropdownMenu, IconButton } from 'tycho-storybook';
6
8
  import AppLoading from '../../common/AppLoading';
7
9
  import AppPlaceholder from '../../common/AppPlaceholder';
8
10
  import SecurityUtils from '../../functions/SecurityUtils';
9
11
  import CommentAdd from './CommentAdd';
10
12
  import CommentService from './CommentService';
11
13
  import CommentThread from './CommentThread';
14
+ import CommentUtils from './CommentUtils';
12
15
  import './style.scss';
13
- const DRAWER_WIDTH_STORAGE_KEY = 'tycho-comments-drawer-width';
14
- const DRAWER_MIN_WIDTH = 320;
15
- const getDefaultDrawerWidth = () => {
16
- if (typeof window === 'undefined')
17
- return 480;
18
- const { innerWidth } = window;
19
- if (innerWidth <= 767)
20
- return Math.round(innerWidth * 0.9);
21
- if (innerWidth <= 768)
22
- return Math.round(innerWidth * 0.6);
23
- return Math.round(innerWidth * 0.4);
24
- };
25
- const clampDrawerWidth = (width) => {
26
- if (typeof window === 'undefined')
27
- return width;
28
- const maxWidth = Math.min(Math.round(window.innerWidth * 0.9), window.innerWidth - 48);
29
- return Math.min(Math.max(width, DRAWER_MIN_WIDTH), maxWidth);
30
- };
31
- const readStoredDrawerWidth = () => {
32
- try {
33
- const stored = localStorage.getItem(DRAWER_WIDTH_STORAGE_KEY);
34
- if (stored) {
35
- const parsed = Number(stored);
36
- if (!Number.isNaN(parsed) && parsed > 0) {
37
- return clampDrawerWidth(parsed);
38
- }
39
- }
40
- }
41
- catch {
42
- // ignore storage access errors
43
- }
44
- return clampDrawerWidth(getDefaultDrawerWidth());
45
- };
16
+ function CommentsPanel({ dockPosition, showResizeHandle, onResizePointerDown, children, }) {
17
+ const isHorizontal = dockPosition === 'bottom';
18
+ return (_jsxs("div", { className: cx('comments-container', {
19
+ 'dock-left': dockPosition === 'left',
20
+ 'dock-right': dockPosition === 'right',
21
+ 'dock-bottom': dockPosition === 'bottom',
22
+ 'dock-undocked': dockPosition === 'undocked',
23
+ }), id: "comment-top", children: [showResizeHandle && (_jsx("div", { className: "comments-resize-handle", onPointerDown: onResizePointerDown, role: "separator", "aria-orientation": isHorizontal ? 'horizontal' : 'vertical', "aria-label": "Resize comments panel" })), children] }));
24
+ }
25
+ function CommentsHeader({ hasEditAccess, onToggleAdd, onClose, dockMenuOptions, }) {
26
+ const { t } = useTranslation('comments');
27
+ return (_jsxs("div", { className: "header", children: [_jsx(IconButton, { name: "close", size: "small", mode: "ghost", onClick: onClose }), _jsx("span", { className: "title", children: t('label.title.comments') }), _jsxs("div", { className: "actions", children: [hasEditAccess && (_jsx(Button, { text: t('button.add'), icon: "add", mode: "outlined", size: "small", className: "edit-button", onClick: onToggleAdd })), _jsx(DropdownMenu, { list: dockMenuOptions })] })] }));
28
+ }
46
29
  export default function Comments({ uid, keywords, references, mode, onClose, onChange, initialOpenAdd = false, }) {
47
30
  const { t } = useTranslation('comments');
48
31
  const [openAddComment, setOpenAddComment] = useState(initialOpenAdd);
@@ -50,8 +33,21 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
50
33
  const [comments, setComments] = useState();
51
34
  const [users, setUsers] = useState([]);
52
35
  const [reply, setReply] = useState();
53
- const [drawerWidth, setDrawerWidth] = useState(readStoredDrawerWidth);
36
+ const [drawerWidth, setDrawerWidth] = useState(CommentUtils.readStoredDrawerWidth);
37
+ const [drawerHeight, setDrawerHeight] = useState(CommentUtils.readStoredDrawerHeight);
38
+ const [dockPosition, setDockPosition] = useState(CommentUtils.readStoredDockPosition);
39
+ const [externalContainer, setExternalContainer] = useState(null);
54
40
  const commentAddRef = useRef(null);
41
+ const popupRef = useRef(null);
42
+ const popupPollRef = useRef(null);
43
+ const ignorePopupCloseRef = useRef(false);
44
+ const lastDockedRef = useRef(null);
45
+ if (lastDockedRef.current === null) {
46
+ const initial = CommentUtils.readStoredDockPosition();
47
+ lastDockedRef.current = CommentUtils.isDockedPosition(initial)
48
+ ? initial
49
+ : 'right';
50
+ }
55
51
  const load = async () => {
56
52
  try {
57
53
  const [commentsResponse, usersResponse] = await Promise.all([
@@ -63,12 +59,120 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
63
59
  }
64
60
  catch (error) {
65
61
  console.error('Error loading comments or users', error);
66
- // optionally handle specific error state here
67
62
  }
68
63
  };
69
64
  const hasEditAccess = useMemo(() => {
70
65
  return SecurityUtils.hasAccess(uid, ['ADMIN', 'EDITOR'], mode);
71
66
  }, [uid, mode]);
67
+ const clearPopupPoll = useCallback(() => {
68
+ if (popupPollRef.current != null) {
69
+ window.clearInterval(popupPollRef.current);
70
+ popupPollRef.current = null;
71
+ }
72
+ }, []);
73
+ const redockFromPopup = useCallback(() => {
74
+ clearPopupPoll();
75
+ popupRef.current = null;
76
+ setExternalContainer(null);
77
+ if (ignorePopupCloseRef.current) {
78
+ ignorePopupCloseRef.current = false;
79
+ return;
80
+ }
81
+ setDockPosition((prev) => {
82
+ if (prev !== 'undocked')
83
+ return prev;
84
+ const next = lastDockedRef.current ?? 'right';
85
+ CommentUtils.persistDockPosition(next);
86
+ return next;
87
+ });
88
+ }, [clearPopupPoll]);
89
+ const closePopup = useCallback(() => {
90
+ clearPopupPoll();
91
+ const popup = popupRef.current;
92
+ if (popup && !popup.closed) {
93
+ ignorePopupCloseRef.current = true;
94
+ popup.close();
95
+ }
96
+ popupRef.current = null;
97
+ setExternalContainer(null);
98
+ }, [clearPopupPoll]);
99
+ const handleClose = useCallback(() => {
100
+ closePopup();
101
+ onClose();
102
+ }, [closePopup, onClose]);
103
+ const openUndockedWindow = useCallback(() => {
104
+ const existing = popupRef.current;
105
+ if (existing && !existing.closed) {
106
+ existing.focus();
107
+ setDockPosition('undocked');
108
+ return;
109
+ }
110
+ const opened = CommentUtils.openCommentsPopup();
111
+ if (!opened)
112
+ return;
113
+ const { popup, container } = opened;
114
+ ignorePopupCloseRef.current = false;
115
+ popupRef.current = popup;
116
+ setExternalContainer(container);
117
+ setDockPosition('undocked');
118
+ popup.addEventListener('beforeunload', () => {
119
+ redockFromPopup();
120
+ });
121
+ clearPopupPoll();
122
+ popupPollRef.current = window.setInterval(() => {
123
+ if (!popupRef.current || popupRef.current.closed) {
124
+ redockFromPopup();
125
+ }
126
+ }, 500);
127
+ }, [clearPopupPoll, redockFromPopup]);
128
+ const setDockedPosition = useCallback((position) => {
129
+ lastDockedRef.current = position;
130
+ CommentUtils.persistDockPosition(position);
131
+ closePopup();
132
+ setDockPosition(position);
133
+ }, [closePopup]);
134
+ const dockMenuOptions = useMemo(() => [
135
+ {
136
+ label: t('menu.undock'),
137
+ icon: 'open_in_new',
138
+ disabled: dockPosition === 'undocked',
139
+ onClick: () => {
140
+ if (dockPosition === 'undocked')
141
+ return;
142
+ openUndockedWindow();
143
+ },
144
+ },
145
+ {
146
+ label: t('menu.dock.left'),
147
+ icon: 'dock_to_left',
148
+ disabled: dockPosition === 'left',
149
+ onClick: () => {
150
+ if (dockPosition === 'left')
151
+ return;
152
+ setDockedPosition('left');
153
+ },
154
+ },
155
+ {
156
+ label: t('menu.dock.bottom'),
157
+ icon: 'dock_to_bottom',
158
+ disabled: dockPosition === 'bottom',
159
+ onClick: () => {
160
+ if (dockPosition === 'bottom')
161
+ return;
162
+ setDockedPosition('bottom');
163
+ },
164
+ },
165
+ {
166
+ label: t('menu.dock.right'),
167
+ icon: 'dock_to_right',
168
+ disabled: dockPosition === 'right',
169
+ onClick: () => {
170
+ if (dockPosition === 'right')
171
+ return;
172
+ setDockedPosition('right');
173
+ },
174
+ },
175
+ ], [dockPosition, openUndockedWindow, setDockedPosition, t]);
72
176
  useEffect(() => {
73
177
  load();
74
178
  }, []);
@@ -83,26 +187,55 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
83
187
  });
84
188
  return () => cancelAnimationFrame(frame);
85
189
  }, [openAddComment, comments, comment, reply]);
190
+ useEffect(() => {
191
+ const onHostUnload = () => {
192
+ closePopup();
193
+ };
194
+ window.addEventListener('pagehide', onHostUnload);
195
+ window.addEventListener('beforeunload', onHostUnload);
196
+ return () => {
197
+ window.removeEventListener('pagehide', onHostUnload);
198
+ window.removeEventListener('beforeunload', onHostUnload);
199
+ closePopup();
200
+ };
201
+ }, [closePopup]);
86
202
  const handleResizePointerDown = useCallback((event) => {
87
203
  event.preventDefault();
88
204
  const handle = event.currentTarget;
89
205
  handle.setPointerCapture(event.pointerId);
90
206
  const previousUserSelect = document.body.style.userSelect;
91
207
  const previousCursor = document.body.style.cursor;
208
+ const isBottom = dockPosition === 'bottom';
209
+ const isLeft = dockPosition === 'left';
92
210
  document.body.style.userSelect = 'none';
93
- document.body.style.cursor = 'col-resize';
211
+ document.body.style.cursor = isBottom ? 'row-resize' : 'col-resize';
212
+ const computeSize = (clientX, clientY) => {
213
+ if (isBottom) {
214
+ return CommentUtils.clampDrawerHeight(window.innerHeight - clientY);
215
+ }
216
+ if (isLeft) {
217
+ return CommentUtils.clampDrawerWidth(clientX);
218
+ }
219
+ return CommentUtils.clampDrawerWidth(window.innerWidth - clientX);
220
+ };
94
221
  const onPointerMove = (moveEvent) => {
95
- const nextWidth = clampDrawerWidth(window.innerWidth - moveEvent.clientX);
96
- setDrawerWidth(nextWidth);
222
+ const next = computeSize(moveEvent.clientX, moveEvent.clientY);
223
+ if (isBottom) {
224
+ setDrawerHeight(next);
225
+ }
226
+ else {
227
+ setDrawerWidth(next);
228
+ }
97
229
  };
98
230
  const onPointerUp = (upEvent) => {
99
- const nextWidth = clampDrawerWidth(window.innerWidth - upEvent.clientX);
100
- setDrawerWidth(nextWidth);
101
- try {
102
- localStorage.setItem(DRAWER_WIDTH_STORAGE_KEY, String(nextWidth));
231
+ const next = computeSize(upEvent.clientX, upEvent.clientY);
232
+ if (isBottom) {
233
+ setDrawerHeight(next);
234
+ CommentUtils.persistDrawerHeight(next);
103
235
  }
104
- catch {
105
- // ignore storage access errors
236
+ else {
237
+ setDrawerWidth(next);
238
+ CommentUtils.persistDrawerWidth(next);
106
239
  }
107
240
  document.body.style.userSelect = previousUserSelect;
108
241
  document.body.style.cursor = previousCursor;
@@ -114,41 +247,54 @@ export default function Comments({ uid, keywords, references, mode, onClose, onC
114
247
  handle.addEventListener('pointermove', onPointerMove);
115
248
  handle.addEventListener('pointerup', onPointerUp);
116
249
  handle.addEventListener('pointercancel', onPointerUp);
117
- }, []);
118
- return (_jsx(Drawer, { anchor: "right", open: true, onClose: onClose, PaperProps: {
119
- className: 'comments-drawer-paper',
120
- style: { width: drawerWidth },
121
- }, children: _jsxs("div", { className: "comments-container", id: "comment-top", children: [_jsx("div", { className: "comments-resize-handle", onPointerDown: handleResizePointerDown, role: "separator", "aria-orientation": "vertical", "aria-label": "Resize comments panel" }), !comments ? (_jsx(AppLoading, { pageLoading: true })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "header", children: [_jsx(IconButton, { name: "close", size: "small", mode: "ghost", onClick: onClose }), _jsx("span", { className: "title", children: t('label.title.comments') }), _jsx("div", { className: "actions", children: hasEditAccess && (_jsx(Button, { text: t('button.add'), icon: "add", mode: "outlined", size: "small", className: "edit-button", onClick: () => {
122
- setComment(undefined);
123
- setOpenAddComment(!openAddComment);
124
- } })) })] }), _jsxs("div", { className: "body", children: [comments?.length === 0 && (_jsx(AppPlaceholder, { text: t('placeholder.comments.empty'), useMarginTop: true })), _jsx(CommentThread, { comments: comments, hasEditAccess: hasEditAccess, onRemove: (id) => {
125
- const updated = comments.filter((c) => c.id !== id);
126
- setComments(updated);
127
- onChange?.(updated);
128
- }, onUpdate: (thisComment) => {
129
- const updated = comments.map((c) => c.id === thisComment.id ? { ...c, ...thisComment } : c);
130
- setComments(updated);
131
- onChange?.(updated);
132
- }, onEdit: (el) => {
133
- setComment(el);
134
- setOpenAddComment(true);
135
- }, onReply: (el) => {
136
- setReply(el);
137
- setOpenAddComment(true);
138
- } }), hasEditAccess && openAddComment && (_jsx("div", { ref: commentAddRef, children: _jsx(CommentAdd, { uid: uid, mode: mode, users: users, references: references, keywords: keywords, comment: comment, reply: reply, onClose: () => {
139
- setComment(undefined);
140
- setReply(undefined);
141
- setOpenAddComment(false);
142
- }, onChange: (el) => {
143
- if (!comment) {
144
- const updated = [...(comments || []), el];
145
- setComments(updated);
146
- onChange?.(updated);
147
- }
148
- else {
149
- const updated = comments?.map((c) => c.id === comment.id ? el : c) || [];
150
- setComments(updated);
151
- onChange?.(updated);
152
- }
153
- } }) }))] })] }))] }) }));
250
+ }, [dockPosition]);
251
+ const paperStyle = dockPosition === 'bottom'
252
+ ? { height: drawerHeight, width: '100%' }
253
+ : { width: drawerWidth };
254
+ const content = !comments ? (_jsx(AppLoading, { pageLoading: true })) : (_jsxs(_Fragment, { children: [_jsx(CommentsHeader, { hasEditAccess: hasEditAccess, onToggleAdd: () => {
255
+ setComment(undefined);
256
+ setOpenAddComment(!openAddComment);
257
+ }, onClose: handleClose, dockMenuOptions: dockMenuOptions }), _jsxs("div", { className: "body", children: [comments.length === 0 && !openAddComment && (_jsx(AppPlaceholder, { text: t('placeholder.comments.empty'), useMarginTop: true })), _jsx(CommentThread, { comments: comments, hasEditAccess: hasEditAccess, onRemove: (id) => {
258
+ const updated = comments.filter((c) => c.id !== id);
259
+ setComments(updated);
260
+ onChange?.(updated);
261
+ }, onUpdate: (thisComment) => {
262
+ const updated = comments.map((c) => c.id === thisComment.id ? { ...c, ...thisComment } : c);
263
+ setComments(updated);
264
+ onChange?.(updated);
265
+ }, onEdit: (el) => {
266
+ setComment(el);
267
+ setOpenAddComment(true);
268
+ }, onReply: (el) => {
269
+ setReply(el);
270
+ setOpenAddComment(true);
271
+ } }), hasEditAccess && openAddComment && (_jsx("div", { ref: commentAddRef, children: _jsx(CommentAdd, { uid: uid, mode: mode, users: users, references: references, keywords: keywords, comment: comment, reply: reply, onClose: () => {
272
+ setComment(undefined);
273
+ setReply(undefined);
274
+ setOpenAddComment(false);
275
+ }, onChange: (el) => {
276
+ if (!comment) {
277
+ const updated = [...(comments || []), el];
278
+ setComments(updated);
279
+ onChange?.(updated);
280
+ }
281
+ else {
282
+ const updated = comments?.map((c) => (c.id === comment.id ? el : c)) || [];
283
+ setComments(updated);
284
+ onChange?.(updated);
285
+ }
286
+ } }) }))] })] }));
287
+ const panel = (_jsx(CommentsPanel, { dockPosition: dockPosition, showResizeHandle: CommentUtils.isDockedPosition(dockPosition), onResizePointerDown: handleResizePointerDown, children: content }));
288
+ const isDocked = CommentUtils.isDockedPosition(dockPosition);
289
+ const drawerAnchor = isDocked
290
+ ? dockPosition
291
+ : (lastDockedRef.current ?? 'right');
292
+ return (_jsxs(_Fragment, { children: [_jsx(Drawer, { anchor: drawerAnchor, open: isDocked, onClose: handleClose, slotProps: {
293
+ backdrop: { invisible: true },
294
+ }, PaperProps: {
295
+ className: cx('comments-drawer-paper', `dock-${drawerAnchor}`),
296
+ style: paperStyle,
297
+ }, children: isDocked ? panel : null }), dockPosition === 'undocked' &&
298
+ externalContainer &&
299
+ createPortal(_jsx("div", { className: "comments-undocked-root", children: panel }), externalContainer)] }));
154
300
  }
@@ -1,8 +1,26 @@
1
1
  .comments-drawer-paper {
2
- width: 40vw;
3
- max-width: 90vw;
4
- min-width: 320px;
5
2
  overflow: hidden;
3
+
4
+ &:not(.dock-bottom) {
5
+ width: 40vw;
6
+ max-width: 90vw;
7
+ min-width: 320px;
8
+ }
9
+
10
+ &.dock-bottom {
11
+ width: 100%;
12
+ max-width: none;
13
+ min-width: 0;
14
+ max-height: 90vh;
15
+ min-height: 200px;
16
+ }
17
+ }
18
+
19
+ .comments-undocked-root {
20
+ width: 100%;
21
+ height: 100%;
22
+ overflow: hidden;
23
+ background-color: var(--background-default);
6
24
  }
7
25
 
8
26
  .comments-container {
@@ -15,21 +33,12 @@
15
33
 
16
34
  > .comments-resize-handle {
17
35
  position: absolute;
18
- top: 0;
19
- left: 0;
20
- width: 6px;
21
- height: 100%;
22
- cursor: col-resize;
23
36
  z-index: 2;
24
37
  touch-action: none;
25
38
 
26
39
  &::after {
27
40
  content: '';
28
41
  position: absolute;
29
- top: 0;
30
- left: 0;
31
- width: 2px;
32
- height: 100%;
33
42
  background-color: transparent;
34
43
  transition: background-color 0.15s ease;
35
44
  }
@@ -40,6 +49,51 @@
40
49
  }
41
50
  }
42
51
 
52
+ &.dock-right > .comments-resize-handle {
53
+ top: 0;
54
+ left: 0;
55
+ width: 6px;
56
+ height: 100%;
57
+ cursor: col-resize;
58
+
59
+ &::after {
60
+ top: 0;
61
+ left: 0;
62
+ width: 2px;
63
+ height: 100%;
64
+ }
65
+ }
66
+
67
+ &.dock-left > .comments-resize-handle {
68
+ top: 0;
69
+ right: 0;
70
+ width: 6px;
71
+ height: 100%;
72
+ cursor: col-resize;
73
+
74
+ &::after {
75
+ top: 0;
76
+ right: 0;
77
+ width: 2px;
78
+ height: 100%;
79
+ }
80
+ }
81
+
82
+ &.dock-bottom > .comments-resize-handle {
83
+ top: 0;
84
+ left: 0;
85
+ width: 100%;
86
+ height: 6px;
87
+ cursor: row-resize;
88
+
89
+ &::after {
90
+ top: 0;
91
+ left: 0;
92
+ width: 100%;
93
+ height: 2px;
94
+ }
95
+ }
96
+
43
97
  > .header {
44
98
  display: flex;
45
99
  align-items: center;
@@ -54,10 +108,12 @@
54
108
 
55
109
  > .actions {
56
110
  display: flex;
111
+ align-items: center;
57
112
  margin-left: auto;
113
+ gap: var(--spacing-100);
58
114
 
59
115
  .edit-button {
60
- margin-right: var(--spacing-100);
116
+ margin-right: 0;
61
117
  }
62
118
  }
63
119
  }
@@ -181,19 +237,19 @@
181
237
  }
182
238
 
183
239
  @media (max-width: 1366px) {
184
- .comments-drawer-paper {
240
+ .comments-drawer-paper:not(.dock-bottom) {
185
241
  width: 40vw;
186
242
  }
187
243
  }
188
244
 
189
245
  @media (max-width: 768px) {
190
- .comments-drawer-paper {
246
+ .comments-drawer-paper:not(.dock-bottom) {
191
247
  width: 60vw;
192
248
  }
193
249
  }
194
250
 
195
251
  @media (max-width: 767px) {
196
- .comments-drawer-paper {
252
+ .comments-drawer-paper:not(.dock-bottom) {
197
253
  width: 90vw;
198
254
  }
199
255
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tycho-components",
3
3
  "private": false,
4
- "version": "0.41.0",
4
+ "version": "0.41.1",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {