diff --git a/.changeset/hip-hoops-kneel.md b/.changeset/hip-hoops-kneel.md new file mode 100644 index 000000000..4bd67c2ad --- /dev/null +++ b/.changeset/hip-hoops-kneel.md @@ -0,0 +1,5 @@ +--- +"@knocklabs/react": patch +--- + +[Guides] Add FocusChin to highlight focus mode w/ dedicated controls diff --git a/packages/react/src/modules/guide/components/Toolbar/V2/FocusChin.tsx b/packages/react/src/modules/guide/components/Toolbar/V2/FocusChin.tsx new file mode 100644 index 000000000..337239604 --- /dev/null +++ b/packages/react/src/modules/guide/components/Toolbar/V2/FocusChin.tsx @@ -0,0 +1,166 @@ +import { useGuideContext, useStore } from "@knocklabs/react-core"; +import { Button } from "@telegraph/button"; +import { Box, Stack } from "@telegraph/layout"; +import { Tooltip } from "@telegraph/tooltip"; +import { Text } from "@telegraph/typography"; +import { ChevronLeft, ChevronRight, X } from "lucide-react"; +import * as React from "react"; + +import { GUIDE_ROW_DATA_SELECTOR } from "./GuideRow"; +import { InspectionResultOk } from "./useInspectGuideClientStore"; + +// Extra scroll overshoot so the focused guide plus ~1-2 neighbors are visible, +// reducing how often consecutive next/prev clicks trigger a scroll. +const SCROLL_OVERSHOOT = 60; + +const maybeScrollGuideIntoView = (container: HTMLElement, guideKey: string) => { + requestAnimationFrame(() => { + const el = container.querySelector( + `[${GUIDE_ROW_DATA_SELECTOR}="${CSS.escape(guideKey)}"]`, + ); + if (!el || !(el instanceof HTMLElement)) return; + + const containerRect = container.getBoundingClientRect(); + const elRect = el.getBoundingClientRect(); + + if (elRect.top < containerRect.top) { + container.scrollTo({ + top: + container.scrollTop - + (containerRect.top - elRect.top) - + SCROLL_OVERSHOOT, + behavior: "smooth", + }); + } else if (elRect.bottom > containerRect.bottom) { + container.scrollTo({ + top: + container.scrollTop + + (elRect.bottom - containerRect.bottom) + + SCROLL_OVERSHOOT, + behavior: "smooth", + }); + } + }); +}; + +type Props = { + guides: InspectionResultOk["guides"]; + guideListRef: React.RefObject; +}; + +export const FocusChin = ({ guides, guideListRef }: Props) => { + const { client } = useGuideContext(); + const { debugSettings } = useStore(client.store, (state) => ({ + debugSettings: state.debug, + })); + + const focusedKeys = Object.keys(debugSettings?.focusedGuideKeys || {}); + + const isFocused = focusedKeys.length > 0; + if (!isFocused) { + return null; + } + + const currentKey = focusedKeys[0]!; + + return ( + + + + Focus mode: {currentKey} + + + +