-
Notifications
You must be signed in to change notification settings - Fork 92
feat(docs): add Kapa.ai sidebar integration #1113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7e7b6fe
933d4c8
b0c3bed
235689d
12d258d
21a3078
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,3 +3,86 @@ | |
| @import 'fumadocs-ui/css/preset.css'; | ||
|
|
||
| @source '../node_modules/fumadocs-ui/dist/**/*.js'; | ||
|
|
||
| /* ---- Kapa sidebar content shift ---- */ | ||
| html.kapa-sidebar-open body { | ||
| width: calc(100vw - 500px) !important; | ||
| overflow-x: hidden; | ||
| transition: width 0.3s ease; | ||
| } | ||
|
|
||
| html.kapa-sidebar-open nav { | ||
| right: 500px !important; | ||
| overflow: hidden; | ||
| transition: right 0.3s ease; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The global |
||
| } | ||
|
|
||
| html.kapa-sidebar-open .kapa-trigger-btn { | ||
| display: none !important; | ||
| } | ||
|
|
||
| nav { | ||
| transition: right 0.3s ease; | ||
| } | ||
|
|
||
| body { | ||
| transition: width 0.3s ease; | ||
| } | ||
|
|
||
| @media (max-width: 768px) { | ||
| html.kapa-sidebar-open body { | ||
| width: 100vw !important; | ||
| } | ||
| } | ||
|
|
||
| /* Kapa trigger button */ | ||
| .kapa-trigger-btn { | ||
| display: inline-flex; | ||
| align-items: center; | ||
| gap: 0.5rem; | ||
| cursor: pointer; | ||
| background: var(--fd-background); | ||
| color: var(--fd-foreground); | ||
| font-weight: 500; | ||
| font-size: 0.875rem; | ||
| padding: 0.5rem 1rem; | ||
| border-radius: 9999px; | ||
| border: 1px solid var(--fd-border); | ||
| transition: background-color 0.15s; | ||
| } | ||
|
|
||
| .kapa-trigger-btn:hover { | ||
| background: var(--fd-muted); | ||
| } | ||
|
|
||
| /* Floating Kapa button (bottom-right) */ | ||
| .kapa-floating-btn { | ||
| position: fixed; | ||
| bottom: 1.5rem; | ||
| right: 1.5rem; | ||
| z-index: 999; | ||
| display: inline-flex; | ||
| align-items: center; | ||
| gap: 0.5rem; | ||
| cursor: pointer; | ||
| background: #298dff; | ||
| color: white; | ||
| font-weight: 600; | ||
| font-size: 0.875rem; | ||
| padding: 0.75rem 1.25rem; | ||
| border-radius: 9999px; | ||
| border: none; | ||
| box-shadow: 0 4px 12px rgba(41, 141, 255, 0.4); | ||
| transition: | ||
| background-color 0.15s, | ||
| box-shadow 0.15s; | ||
| } | ||
|
|
||
| .kapa-floating-btn:hover { | ||
| background: #1b6fd1; | ||
| box-shadow: 0 6px 16px rgba(41, 141, 255, 0.5); | ||
| } | ||
|
|
||
| html.kapa-sidebar-open .kapa-floating-btn { | ||
| display: none !important; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| // Copyright (c) Mysten Labs, Inc. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| 'use client'; | ||
|
|
||
| import { useEffect, useState } from 'react'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused |
||
|
|
||
| declare global { | ||
| interface Window { | ||
| Kapa?: { | ||
| open: (query?: string) => void; | ||
| close: () => void; | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| const OPEN_CLASS = 'kapa-sidebar-open'; | ||
|
|
||
| export function KapaSidebar() { | ||
| useEffect(() => { | ||
| let kapaOpen = false; | ||
| let hookedRef: ((query?: string) => void) | null = null; | ||
|
|
||
| function syncClass() { | ||
| document.documentElement.classList.toggle(OPEN_CLASS, kapaOpen); | ||
| } | ||
|
|
||
| function hookKapa() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Monkeypatch is never restored; remount double-wraps. Cleanup only clears the interval — |
||
| if (!window.Kapa || !window.Kapa.open || window.Kapa.open === hookedRef) return; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this how they recommend building this out, this feels like a pretty hacky way to manage a sidebar |
||
|
|
||
| const origOpen = window.Kapa.open; | ||
| const origClose = window.Kapa.close; | ||
|
|
||
| window.Kapa.open = function (...args: Parameters<typeof origOpen>) { | ||
| kapaOpen = true; | ||
| syncClass(); | ||
| return origOpen.apply(this, args); | ||
| }; | ||
|
|
||
| window.Kapa.close = function (...args: Parameters<typeof origClose>) { | ||
| kapaOpen = false; | ||
| syncClass(); | ||
| return origClose.apply(this, args); | ||
| }; | ||
|
|
||
| hookedRef = window.Kapa.open; | ||
| } | ||
|
|
||
| function isSidebarVisible() { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This detection heuristic is likely unnecessary and fragile. The |
||
| const x = window.innerWidth - 50; | ||
| const y = window.innerHeight / 2; | ||
| const el = document.elementFromPoint(x, y); | ||
| if (!el) return false; | ||
| if (el === document.body || el === document.documentElement) return false; | ||
| const root = document.getElementById('nd-docs-layout') || document.querySelector('main'); | ||
| if (root && root.contains(el)) return false; | ||
| return true; | ||
| } | ||
|
|
||
| const interval = setInterval(() => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Polling overwrites the monkeypatch state, causing flicker. Every 300ms this interval recomputes |
||
| hookKapa(); | ||
| const visible = isSidebarVisible(); | ||
| if (visible && !kapaOpen) { | ||
| kapaOpen = true; | ||
| } else if (!visible && kapaOpen) { | ||
| kapaOpen = false; | ||
| } | ||
| syncClass(); | ||
| }, 300); | ||
|
|
||
| return () => clearInterval(interval); | ||
| }, []); | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| export function KapaButton() { | ||
| return ( | ||
| <button type="button" onClick={() => window.Kapa?.open()} className="kapa-floating-btn"> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Button is a silent no-op before the async script loads: |
||
| <svg | ||
| width="20" | ||
| height="20" | ||
| viewBox="0 0 24 24" | ||
| fill="none" | ||
| stroke="currentColor" | ||
| strokeWidth="2" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| > | ||
| <path d="M8 15h8a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2z" /> | ||
| <path d="M9 18h6" /> | ||
| <path d="M10 22h4" /> | ||
| <path d="M10 18v4" /> | ||
| <path d="M14 18v4" /> | ||
| </svg> | ||
| <span>Ask SDK AI</span> | ||
| </button> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded 500px shift isn't tied to Kapa's actual sidebar width.
calc(100vw - 500px)(and thenav right: 500px) assume the widget is exactly 500px — if Kapa's sidebar width differs now or later, content overlaps or leaves a gap.