feat(canvas): add shared workspace access and controls

This commit is contained in:
2026-07-10 14:47:19 +08:00
parent f288eb1365
commit 14eaece0ac
18 changed files with 1149 additions and 80 deletions
@@ -5,6 +5,17 @@
gap: 6px;
}
.workspace-title.is-disabled .workspace-title-name,
.workspace-title.is-disabled .title-ghost {
color: #a7adb5;
cursor: default;
}
.workspace-title.is-disabled .workspace-title-name:hover,
.workspace-title.is-disabled .title-ghost:hover {
background: transparent;
}
.moteva-mark {
width: 1.8rem;
height: 1.8rem;
@@ -10,6 +10,7 @@ type WorkspaceTitleProps = {
backHomeLabel: string;
copyLabel: string;
moreLabel: string;
disabled?: boolean;
onBackHome: () => void;
onRename: (title: string) => Promise<void>;
};
@@ -22,6 +23,7 @@ export function WorkspaceTitle({
backHomeLabel,
copyLabel,
moreLabel,
disabled = false,
onBackHome,
onRename
}: WorkspaceTitleProps) {
@@ -37,6 +39,10 @@ export function WorkspaceTitle({
if (!editing) setDraftTitle(title);
}, [editing, title]);
useEffect(() => {
if (disabled) setEditing(false);
}, [disabled]);
useEffect(() => {
if (!editing) return;
const input = inputRef.current;
@@ -46,7 +52,7 @@ export function WorkspaceTitle({
}, [editing]);
const startEditing = () => {
if (renaming) return;
if (disabled || renaming) return;
setDraftTitle(trimmedTitle);
setEditing(true);
};
@@ -57,7 +63,7 @@ export function WorkspaceTitle({
};
const commitEditing = async () => {
if (committingRef.current) return;
if (disabled || committingRef.current) return;
const nextTitle = draftTitle.trim();
if (!nextTitle || nextTitle === trimmedTitle) {
cancelEditing();
@@ -77,7 +83,7 @@ export function WorkspaceTitle({
};
return (
<div className="workspace-title">
<div className={`workspace-title ${disabled ? "is-disabled" : ""}`} aria-disabled={disabled}>
<button className="moteva-mark" type="button" onClick={onBackHome} aria-label={backHomeLabel}>
<BrandMark className="workspace-brand-mark" />
</button>
@@ -88,6 +94,7 @@ export function WorkspaceTitle({
value={draftTitle}
placeholder={placeholder}
readOnly={renaming}
disabled={disabled}
onChange={(event) => setDraftTitle(event.target.value)}
onBlur={() => void commitEditing()}
onKeyDown={(event) => {
@@ -104,14 +111,14 @@ export function WorkspaceTitle({
{renaming && <Loader2 className="spin" size={13} />}
</span>
) : (
<button className="workspace-title-name" type="button" title={displayTitle} aria-label={renameLabel} onClick={startEditing}>
<button className="workspace-title-name" type="button" title={displayTitle} aria-label={renameLabel} onClick={startEditing} disabled={disabled}>
<strong>{displayTitle}</strong>
</button>
)}
<button className="title-ghost" type="button" aria-label={copyLabel}>
<button className="title-ghost" type="button" aria-label={copyLabel} disabled={disabled}>
<Copy size={14} />
</button>
<button className="title-ghost" type="button" aria-label={moreLabel}>
<button className="title-ghost" type="button" aria-label={moreLabel} disabled={disabled}>
<ChevronDown size={14} />
</button>
</div>