|
|
@@ -1,24 +1,33 @@
|
|
|
import { EditorView } from '@codemirror/view';
|
|
|
|
|
|
+// Shared design tokens
|
|
|
+const AVATAR_SIZE = '20px';
|
|
|
+const IDLE_OPACITY = '0.6';
|
|
|
+
|
|
|
export const richCursorsTheme = EditorView.baseTheme({
|
|
|
- // Caret line
|
|
|
+ // Caret line — negative margins cancel out border width to avoid text shift.
|
|
|
+ // Modeled after yRemoteSelectionsTheme in y-codemirror.next.
|
|
|
'.cm-yRichCaret': {
|
|
|
position: 'relative',
|
|
|
- borderLeft: '2px solid',
|
|
|
+ borderLeft: '1px solid',
|
|
|
+ borderRight: '1px solid',
|
|
|
+ marginLeft: '-1px',
|
|
|
+ marginRight: '-1px',
|
|
|
+ boxSizing: 'border-box',
|
|
|
+ display: 'inline',
|
|
|
},
|
|
|
|
|
|
- // Overlay flag — positioned below the caret
|
|
|
+ // Overlay flag — positioned below the caret.
|
|
|
+ // pointer-events: auto so the avatar itself is a hover/click target.
|
|
|
'.cm-yRichCursorFlag': {
|
|
|
position: 'absolute',
|
|
|
top: '100%',
|
|
|
- left: '-8px',
|
|
|
+ left: '-9px', // center the avatar on the 1px caret
|
|
|
zIndex: '10',
|
|
|
- pointerEvents: 'none',
|
|
|
- opacity: '0.4',
|
|
|
+ opacity: IDLE_OPACITY,
|
|
|
transition: 'opacity 0.3s ease',
|
|
|
},
|
|
|
- '.cm-yRichCaret:hover .cm-yRichCursorFlag': {
|
|
|
- pointerEvents: 'auto',
|
|
|
+ '.cm-yRichCaret:hover .cm-yRichCursorFlag, .cm-yRichCursorFlag:hover': {
|
|
|
opacity: '1',
|
|
|
},
|
|
|
'.cm-yRichCursorFlag.cm-yRichCursorActive': {
|
|
|
@@ -27,37 +36,40 @@ export const richCursorsTheme = EditorView.baseTheme({
|
|
|
|
|
|
// Avatar image
|
|
|
'.cm-yRichCursorAvatar': {
|
|
|
- width: '16px',
|
|
|
- height: '16px',
|
|
|
+ width: AVATAR_SIZE,
|
|
|
+ height: AVATAR_SIZE,
|
|
|
borderRadius: '50%',
|
|
|
display: 'block',
|
|
|
},
|
|
|
|
|
|
// Initials fallback
|
|
|
'.cm-yRichCursorInitials': {
|
|
|
- width: '16px',
|
|
|
- height: '16px',
|
|
|
+ width: AVATAR_SIZE,
|
|
|
+ height: AVATAR_SIZE,
|
|
|
borderRadius: '50%',
|
|
|
display: 'flex',
|
|
|
alignItems: 'center',
|
|
|
justifyContent: 'center',
|
|
|
color: 'white',
|
|
|
- fontSize: '8px',
|
|
|
+ fontSize: '9px',
|
|
|
fontWeight: 'bold',
|
|
|
},
|
|
|
|
|
|
- // Name label — hidden by default, shown on hover
|
|
|
+ // Name label — hidden by default, shown on hover.
|
|
|
+ // Sits behind the avatar; left border-radius matches the avatar circle.
|
|
|
'.cm-yRichCursorInfo': {
|
|
|
display: 'none',
|
|
|
position: 'absolute',
|
|
|
top: '0',
|
|
|
- left: '20px',
|
|
|
+ left: '0',
|
|
|
+ zIndex: '-1',
|
|
|
whiteSpace: 'nowrap',
|
|
|
- padding: '2px 6px',
|
|
|
- borderRadius: '3px',
|
|
|
+ padding: `0 6px 0 calc(${AVATAR_SIZE} + 4px)`,
|
|
|
+ borderRadius: `calc(${AVATAR_SIZE} / 2) 3px 3px calc(${AVATAR_SIZE} / 2)`,
|
|
|
color: 'white',
|
|
|
fontSize: '12px',
|
|
|
- lineHeight: '16px',
|
|
|
+ height: AVATAR_SIZE,
|
|
|
+ lineHeight: AVATAR_SIZE,
|
|
|
},
|
|
|
'.cm-yRichCursorFlag:hover .cm-yRichCursorInfo': {
|
|
|
display: 'block',
|
|
|
@@ -86,7 +98,7 @@ export const richCursorsTheme = EditorView.baseTheme({
|
|
|
display: 'flex',
|
|
|
alignItems: 'center',
|
|
|
gap: '2px',
|
|
|
- opacity: '0.4',
|
|
|
+ opacity: IDLE_OPACITY,
|
|
|
transition: 'opacity 0.3s ease',
|
|
|
},
|
|
|
'.cm-offScreenIndicator.cm-yRichCursorActive': {
|
|
|
@@ -97,19 +109,19 @@ export const richCursorsTheme = EditorView.baseTheme({
|
|
|
lineHeight: '1',
|
|
|
},
|
|
|
'.cm-offScreenAvatar': {
|
|
|
- width: '16px',
|
|
|
- height: '16px',
|
|
|
+ width: AVATAR_SIZE,
|
|
|
+ height: AVATAR_SIZE,
|
|
|
borderRadius: '50%',
|
|
|
},
|
|
|
'.cm-offScreenInitials': {
|
|
|
- width: '16px',
|
|
|
- height: '16px',
|
|
|
+ width: AVATAR_SIZE,
|
|
|
+ height: AVATAR_SIZE,
|
|
|
borderRadius: '50%',
|
|
|
display: 'flex',
|
|
|
alignItems: 'center',
|
|
|
justifyContent: 'center',
|
|
|
color: 'white',
|
|
|
- fontSize: '8px',
|
|
|
+ fontSize: '9px',
|
|
|
fontWeight: 'bold',
|
|
|
},
|
|
|
});
|