Skip to content

Commit

Permalink
Merge pull request #105 from basehub-ai/toolbar
Browse files Browse the repository at this point in the history
Toolbar QA
  • Loading branch information
moransantiago authored May 31, 2024
2 parents 6a00bd4 + 33819d9 commit e462c15
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 190 deletions.
5 changes: 5 additions & 0 deletions .changeset/gentle-birds-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"basehub": patch
---

Refresh on draft mode activation, styling corrections, full drag handle.
113 changes: 57 additions & 56 deletions packages/basehub/src/next/toolbar/client-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as React from "react";
import s from "./toolbar.module.scss";
import { Tooltip } from "./components/tooltip";
import { DragHandle } from "./components/drag-handle";
import { useRouter } from "next/navigation";
import { BranchSwitcher } from "./components/branch-swticher";
import debounce from "lodash.debounce";

Expand All @@ -29,19 +28,17 @@ export const ClientToolbar = ({
shouldAutoEnableDraft: boolean | undefined;
seekAndStoreBshbPreviewToken: (type?: "url-only") => string | undefined;
}) => {
const router = useRouter();
const [toolbarRef, setToolbarRef] = React.useState<HTMLDivElement | null>(
null
);
const dragHandleRef = React.useRef<DragHandle>(null);
const tooltipRef = React.useRef<Tooltip>(null);
const [isDragging, setIsDragging] = React.useState(false);
const [message, setMessage] = React.useState("");
const [loading, setLoading] = React.useState(false);

const displayMessage = React.useCallback(
(message: string) => {
setMessage(message);
tooltipRef.current?.checkOverflow();
setTimeout(() => setMessage(""), 5000);
},
[setMessage]
Expand All @@ -54,7 +51,7 @@ export const ClientToolbar = ({
.then(({ status, response }) => {
if (status === 200) {
// refresh
router.refresh();
window.location.reload();
} else if ("error" in response) {
displayMessage(`Draft mode activation error: ${response.error}`);
} else {
Expand All @@ -63,7 +60,7 @@ export const ClientToolbar = ({
})
.finally(() => setLoading(false));
},
[enableDraftMode, displayMessage, router]
[enableDraftMode, displayMessage]
);

const [hasAutoEnabledDraftOnce, setHasAutoEnabledDraftOnce] =
Expand Down Expand Up @@ -94,6 +91,10 @@ export const ClientToolbar = ({
hasAutoEnabledDraftOnce,
]);

React.useLayoutEffect(() => {
tooltipRef.current?.checkOverflow();
}, [message]);

const getStoredToolbarPosition = React.useCallback(() => {
if (!toolbarRef) return;

Expand Down Expand Up @@ -193,58 +194,58 @@ export const ClientToolbar = ({

return (
<div className={s.wrapper} ref={(ref) => setToolbarRef(ref)}>
<div className={s.root} data-draft-active={isForcedDraft || draft}>
{/* branch switcher */}
<BranchSwitcher isForcedDraft={isForcedDraft} draft={draft} />

{/* draft mode button */}
<Tooltip
content={message || tooltip}
ref={tooltipRef}
disabled={isDragging}
forceVisible={Boolean(message)}
>
<button
className={s.draft}
data-active={isForcedDraft || draft}
data-loading={loading}
disabled={isForcedDraft}
onClick={() => {
if (loading) return;

if (draft) {
disableDraftMode().then(() => {
const pathname = window.location.pathname;
const urlWithoutPreview = pathname.replace(
/(\?|&)bshb-preview=[^&]*/,
""
);
router.push(urlWithoutPreview);
});
} else {
const previewToken =
bshbPreviewToken ?? seekAndStoreBshbPreviewToken();
if (!previewToken) {
return displayMessage("No preview token found");
}
<DragHandle
ref={dragHandleRef}
onDrag={(pos) => {
dragToolbar(pos);
tooltipRef.current?.checkOverflow();
}}
>
<div className={s.root} data-draft-active={isForcedDraft || draft}>
{/* branch switcher */}
<BranchSwitcher isForcedDraft={isForcedDraft} draft={draft} />

triggerDraftMode(previewToken);
}
}}
{/* draft mode button */}
<Tooltip
content={message || tooltip}
ref={tooltipRef}
forceVisible={Boolean(message)}
>
{draft || isForcedDraft ? <EyeIcon /> : <EyeDashedIcon />}
</button>
</Tooltip>

<DragHandle
onDrag={(pos) => {
dragToolbar(pos);
tooltipRef.current?.checkOverflow();
}}
onDragStart={() => setIsDragging(true)}
onDragEnd={() => setIsDragging(false)}
/>
</div>
<button
className={s.draft}
data-active={isForcedDraft || draft}
data-loading={loading}
disabled={isForcedDraft || loading}
onClick={() => {
if (loading || dragHandleRef.current?.hasDragged) return;

if (draft) {
setLoading(true);
disableDraftMode()
.then(() => {
const url = new URL(window.location.href);
url.searchParams.delete("bshb-preview");
url.searchParams.delete("__vercel_draft");

window.location.href = url.toString();
})
.finally(() => setLoading(false));
} else {
const previewToken =
bshbPreviewToken ?? seekAndStoreBshbPreviewToken();
if (!previewToken) {
return displayMessage("No preview token found");
}

triggerDraftMode(previewToken);
}
}}
>
{draft || isForcedDraft ? <EyeIcon /> : <EyeDashedIcon />}
</button>
</Tooltip>
</div>
</DragHandle>
</div>
);
};
Expand Down
149 changes: 79 additions & 70 deletions packages/basehub/src/next/toolbar/components/drag-handle.tsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,95 @@
import * as React from "react";
import s from "../toolbar.module.scss";

export const DragHandle = ({
onDrag,
onDragStart,
onDragEnd,
}: {
onDrag: ({ x, y }: { x: number; y: number }) => void;
onDragStart: () => void;
onDragEnd: () => void;
}) => {
const [isDragging, setIsDragging] = React.useState(false);
export type DragHandle = { hasDragged: boolean };

React.useLayoutEffect(() => {
if (!isDragging) return;
export const DragHandle = React.forwardRef(
(
{
onDrag,
children,
}: {
onDrag: ({ x, y }: { x: number; y: number }) => void;
children: React.ReactNode;
},
ref
) => {
const [isDragging, setIsDragging] = React.useState(false);
const initialPointer = React.useRef({ x: 0, y: 0 });
const initialToolbar = React.useRef({ x: 0, y: 0 });
const hasDragged = React.useRef(false);

const handleDrag = (e: PointerEvent) => {
if (!isDragging) return;
React.useImperativeHandle(ref, () => ({
hasDragged: hasDragged.current,
}));

const x = Math.round(e.clientX);
const y = Math.round(e.clientY);
const handleDrag = React.useCallback(
(e: PointerEvent) => {
if (!isDragging) return;

onDrag({ x, y });
};
const deltaX = e.clientX - initialPointer.current.x;
const deltaY = e.clientY - initialPointer.current.y;
const newToolbarX = initialToolbar.current.x + deltaX;
const newToolbarY = initialToolbar.current.y + deltaY;

window.addEventListener("pointermove", handleDrag);
// set hasDragged to true if the pointer has moved more than 5 px from the initial position
if (Math.abs(deltaX) > 2 || Math.abs(deltaY) > 2) {
hasDragged.current = true;
}

return () => {
window.removeEventListener("pointermove", handleDrag);
};
}, [isDragging, onDrag]);
onDrag({ x: newToolbarX, y: newToolbarY });
},
[isDragging, onDrag]
);

React.useLayoutEffect(() => {
// disable drag on pointer up
if (!isDragging) return;
React.useLayoutEffect(() => {
if (!isDragging) return;

const handlePointerUp = () => {
setIsDragging(false);
onDragEnd();
};
window.addEventListener("pointermove", handleDrag);

window.addEventListener("pointerup", handlePointerUp);
return () => {
window.removeEventListener("pointermove", handleDrag);
};
}, [isDragging, onDrag, handleDrag]);

return () => {
window.removeEventListener("pointerup", handlePointerUp);
};
}, [isDragging, onDragEnd]);
React.useLayoutEffect(() => {
// disable drag on pointer up
if (!isDragging) {
hasDragged.current = false;
return
}

return (
<button
className={`${s.dragHandle} ${isDragging ? s.dragging : ""}`}
onPointerDown={() => {
setIsDragging(true);
onDragStart();
}}
onPointerUp={() => {
const handlePointerUp = () => {
setIsDragging(false);
if (isDragging) {
onDragEnd();
}
}}
>
<DragIcon />
</button>
);
};
};

window.addEventListener("pointerup", handlePointerUp);

return () => {
window.removeEventListener("pointerup", handlePointerUp);
};
}, [isDragging]);

const DragIcon = () => {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none">
<path
fill="#F3F3F3"
fillRule="evenodd"
d="M8 1.75a.5.5 0 0 1 .5.5V6a.5.5 0 0 1-1 0V2.25a.5.5 0 0 1 .5-.5ZM8 9.5a.5.5 0 0 1 .5.5v3.75a.5.5 0 0 1-1 0V10a.5.5 0 0 1 .5-.5ZM9.5 8a.5.5 0 0 1 .5-.5h3.75a.5.5 0 1 1 0 1H10a.5.5 0 0 1-.5-.5ZM1.75 8a.5.5 0 0 1 .5-.5H6a.5.5 0 0 1 0 1H2.25a.5.5 0 0 1-.5-.5Z"
clipRule="evenodd"
/>
<path
fill="#F3F3F3"
fillRule="evenodd"
d="M3.818 5.933a.45.45 0 0 1 0 .636L2.386 8.001l1.432 1.432a.45.45 0 0 1-.636.636l-1.75-1.75a.45.45 0 0 1 0-.636l1.75-1.75a.45.45 0 0 1 .636 0ZM5.932 12.183a.45.45 0 0 1 .636 0L8 13.614l1.432-1.431a.45.45 0 1 1 .636.636l-1.75 1.75a.45.45 0 0 1-.636 0l-1.75-1.75a.45.45 0 0 1 0-.636ZM12.182 5.933a.45.45 0 0 1 .636 0l1.75 1.75a.45.45 0 0 1 0 .636l-1.75 1.75a.45.45 0 1 1-.636-.636L13.614 8l-1.432-1.432a.45.45 0 0 1 0-.636ZM7.682 1.433a.45.45 0 0 1 .636 0l1.75 1.75a.45.45 0 1 1-.636.636L8 2.387 6.568 3.82a.45.45 0 1 1-.636-.636l1.75-1.75Z"
clipRule="evenodd"
/>
</svg>
);
};
return (
<span
draggable
className={`${s.dragHandle} ${isDragging ? s.dragging : ""}`}
onPointerDown={(e) => {
e.stopPropagation();
const handle = e.currentTarget as HTMLSpanElement | null;
if (!handle) return;
initialPointer.current = { x: e.clientX, y: e.clientY };
const rect = handle.getBoundingClientRect();
initialToolbar.current.x = rect.left;
initialToolbar.current.y = rect.top;
setIsDragging(true);
}}
onPointerUp={() => {
setIsDragging(false);
}}
>
{children}
</span>
);
}
);
Loading

0 comments on commit e462c15

Please sign in to comment.