mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Add user list component + snap to user functionality (#1749)
This commit is contained in:
parent
8f65e37dac
commit
ca87ca6fe9
18 changed files with 333 additions and 32 deletions
14
src/components/Avatar.scss
Normal file
14
src/components/Avatar.scss
Normal file
|
@ -0,0 +1,14 @@
|
|||
@import "../css/_variables";
|
||||
|
||||
.Avatar {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 1.25rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: $oc-white;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
}
|
25
src/components/Avatar.tsx
Normal file
25
src/components/Avatar.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import "./Avatar.scss";
|
||||
|
||||
import React from "react";
|
||||
|
||||
type AvatarProps = {
|
||||
children: string;
|
||||
className?: string;
|
||||
onClick: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
color: string;
|
||||
};
|
||||
|
||||
export const Avatar = ({
|
||||
children,
|
||||
className,
|
||||
color,
|
||||
onClick,
|
||||
}: AvatarProps) => (
|
||||
<div
|
||||
className={`Avatar ${className}`}
|
||||
style={{ background: color }}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
|
@ -10,6 +10,7 @@ import { ActionManager } from "../actions/manager";
|
|||
import { Island } from "./Island";
|
||||
import Stack from "./Stack";
|
||||
import { FixedSideContainer } from "./FixedSideContainer";
|
||||
import { UserList } from "./UserList";
|
||||
import { LockIcon } from "./LockIcon";
|
||||
import { ExportDialog, ExportCB } from "./ExportDialog";
|
||||
import { LanguageList } from "./LanguageList";
|
||||
|
@ -28,6 +29,7 @@ import { LoadingMessage } from "./LoadingMessage";
|
|||
import { CLASSES } from "../constants";
|
||||
import { shield } from "./icons";
|
||||
import { GitHubCorner } from "./GitHubCorner";
|
||||
import { Tooltip } from "./Tooltip";
|
||||
|
||||
import "./LayerUI.scss";
|
||||
|
||||
|
@ -61,6 +63,7 @@ const LayerUI = ({
|
|||
}: LayerUIProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
// TODO: Extend tooltip component and use here.
|
||||
const renderEncryptedIcon = () => (
|
||||
<a
|
||||
className={`encrypted-icon tooltip zen-mode-visibility ${
|
||||
|
@ -203,7 +206,23 @@ const LayerUI = ({
|
|||
</Stack.Col>
|
||||
)}
|
||||
</Section>
|
||||
<div />
|
||||
<UserList
|
||||
className={`zen-mode-transition ${
|
||||
zenModeEnabled && "transition-right"
|
||||
}`}
|
||||
>
|
||||
{Array.from(appState.collaborators)
|
||||
// Collaborator is either not initialized or is actually the current user.
|
||||
.filter(([_, client]) => Object.keys(client).length !== 0)
|
||||
.map(([clientId, client]) => (
|
||||
<Tooltip
|
||||
label={client.username || "Unknown user"}
|
||||
key={clientId}
|
||||
>
|
||||
{actionManager.renderAction("goToCollaborator", clientId)}
|
||||
</Tooltip>
|
||||
))}
|
||||
</UserList>
|
||||
</div>
|
||||
{
|
||||
<div
|
||||
|
|
|
@ -16,6 +16,7 @@ import { RoomDialog } from "./RoomDialog";
|
|||
import { SCROLLBAR_WIDTH, SCROLLBAR_MARGIN } from "../scene/scrollbars";
|
||||
import { LockIcon } from "./LockIcon";
|
||||
import { LoadingMessage } from "./LoadingMessage";
|
||||
import { UserList } from "./UserList";
|
||||
|
||||
type MobileMenuProps = {
|
||||
appState: AppState;
|
||||
|
@ -105,6 +106,22 @@ export const MobileMenu = ({
|
|||
}}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{t("labels.collaborators")}</legend>
|
||||
<UserList mobile>
|
||||
{Array.from(appState.collaborators)
|
||||
// Collaborator is either not initialized or is actually the current user.
|
||||
.filter(([_, client]) => Object.keys(client).length !== 0)
|
||||
.map(([clientId, client]) => (
|
||||
<React.Fragment key={clientId}>
|
||||
{actionManager.renderAction(
|
||||
"goToCollaborator",
|
||||
clientId,
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</UserList>
|
||||
</fieldset>
|
||||
</Stack.Col>
|
||||
</div>
|
||||
</Section>
|
||||
|
|
48
src/components/Tooltip.scss
Normal file
48
src/components/Tooltip.scss
Normal file
|
@ -0,0 +1,48 @@
|
|||
@import "../css/_variables";
|
||||
|
||||
.Tooltip {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.Tooltip__label {
|
||||
--arrow-size: 4px;
|
||||
visibility: hidden;
|
||||
width: 10ch;
|
||||
background: $oc-black;
|
||||
color: $oc-white;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
font-size: 0.7rem;
|
||||
line-height: 1.5;
|
||||
top: calc(100% + var(--arrow-size) + 3px);
|
||||
// extra pixel offset for unknown reasons
|
||||
left: calc(-50% + var(--arrow-size) / 2 - 1px);
|
||||
word-wrap: break-word;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
border: var(--arrow-size) solid transparent;
|
||||
border-bottom-color: $oc-black;
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: calc(50% - var(--arrow-size));
|
||||
}
|
||||
}
|
||||
|
||||
// the following 3 rules ensure that the tooltip doesn't show (nor affect
|
||||
// the cursor) when you drag over when you draw on canvas, but at the same
|
||||
// time it still works when clicking on the link/shield
|
||||
body:active .Tooltip:not(:hover) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body:not(:active) .Tooltip:hover .Tooltip__label {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.Tooltip__label:hover {
|
||||
visibility: visible;
|
||||
}
|
15
src/components/Tooltip.tsx
Normal file
15
src/components/Tooltip.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import "./Tooltip.scss";
|
||||
|
||||
import React from "react";
|
||||
|
||||
type TooltipProps = {
|
||||
children: React.ReactNode;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export const Tooltip = ({ children, label }: TooltipProps) => (
|
||||
<div className="Tooltip">
|
||||
<span className="Tooltip__label">{label}</span>
|
||||
{children}
|
||||
</div>
|
||||
);
|
22
src/components/UserList.css
Normal file
22
src/components/UserList.css
Normal file
|
@ -0,0 +1,22 @@
|
|||
.UserList {
|
||||
pointer-events: none;
|
||||
/*github corner*/
|
||||
padding: var(--space-factor) 40px var(--space-factor) var(--space-factor);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.UserList > * {
|
||||
pointer-events: all;
|
||||
margin: 0 0 var(--space-factor) var(--space-factor);
|
||||
}
|
||||
|
||||
.UserList_mobile {
|
||||
padding: 0;
|
||||
justify-content: normal;
|
||||
}
|
||||
|
||||
.UserList_mobile > * {
|
||||
margin: 0 var(--space-factor) var(--space-factor) 0;
|
||||
}
|
23
src/components/UserList.tsx
Normal file
23
src/components/UserList.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import "./UserList.css";
|
||||
|
||||
import React from "react";
|
||||
|
||||
type UserListProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
mobile?: boolean;
|
||||
};
|
||||
|
||||
export const UserList = ({ children, className, mobile }: UserListProps) => {
|
||||
let compClassName = "UserList";
|
||||
|
||||
if (className) {
|
||||
compClassName += ` ${className}`;
|
||||
}
|
||||
|
||||
if (mobile) {
|
||||
compClassName += " UserList_mobile";
|
||||
}
|
||||
|
||||
return <div className={compClassName}>{children}</div>;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue