timc1/kbar

Was anyone successful setting this up with Next.js v13 app/ folder structure?

lucgagan opened this issue · 4 comments

I am exploring different options for command bars, and this one stands out as by far the most polished. However, I don't see any mention of how this works with React server components (RSC). Reading the documentation, it seems that I need to wrap the app in a provider, which is not going to work.

Is there a reason I cannot export the entire thing as a separate client-side component? i.e.

'use client';

import {
  KBarAnimator,
  KBarPortal,
  KBarPositioner,
  KBarProvider,
  KBarSearch,
} from 'kbar';

const actions = [
  {
    id: 'blog',
    keywords: 'writing words',
    name: 'Blog',
    perform: () => (window.location.pathname = 'blog'),
    shortcut: ['b'],
  },
  {
    id: 'contact',
    keywords: 'email',
    name: 'Contact',
    perform: () => (window.location.pathname = 'contact'),
    shortcut: ['c'],
  },
];

export const CommandBar = () => {
  return (
    <KBarProvider actions={actions}>
      <KBarPortal>
        <KBarPositioner>
          <KBarAnimator>
            <KBarSearch />
          </KBarAnimator>
        </KBarPositioner>
      </KBarPortal>
    </KBarProvider>
  );
};

I couldn't get it to work when I tried to export the entire thing as a client component but I did get it to work by exporting KBarProvider as client in one file and KBarPortal, KBarPositioner... etc in another file.

Basic setup looks something like this:
src/components/MyKBar.tsx

import {
  KBarAnimator,
  KBarPortal,
  KBarPositioner,
  KBarSearch,
} from 'kbar';

export default function MyKBar() {
  return (
    <KBarProvider actions={actions}>
      <KBarPortal>
        <KBarPositioner>
          <KBarAnimator>
            <KBarSearch />
          </KBarAnimator>
        </KBarPositioner>
      </KBarPortal>
    </KBarProvider>
  );
};

src/components/MyKBarProvider.tsx

"use client"
import { KBarProvider, Action } from "kbar";
import MyKBar from "./myKBar";

export default function MyKBarProvider({
  children,
}: {
  children: React.ReactNode;
}) {
 const MyKBarActions = [
 // your actions here
]
  return (
    <>
      <KBarProvider actions={MyKBarActions}>
        <MyKBar />
        {children}
      </KBarProvider>
    </>
  );
}

Now you can you can use MyKBarProvider in whatever file as a client component but you still do have to wrap some children in it.

I use it in my root layout at src/app/layout.tsx.

import MyKBarProvider from "@/components/kbar/MyKBarProvider";
import "./globals.css";

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <main className="flex min-h-screen w-full flex-col items-center justify-center ">
            <MyKBarProvider>
              {children}
            </MyKBarProvider>
        </main>
      </body>
    </html>
  );
}

I managed to make it work (https://ray.run/), but ... I've put all kbar logic into a single client side component.

In contrast, in your case, you are provider to a layout, which is a server-side component, so that won't work.

I got it working almost exactly as you're doing:

KBarLayout

'use client';
import { StarIcon } from '@radix-ui/react-icons';
import {
  Action,
  KBarAnimator,
  KBarPortal,
  KBarPositioner,
  KBarProvider,
  KBarSearch,
} from 'kbar';
import { useRouter } from 'next/navigation';

import RenderResults from '@/src/components/kbar/RenderResults';

function KBarLayout(props) {
  const router = useRouter();

  const actions: Action[] = [
  ....
  ];

  return (
    <KBarProvider actions={actions}>
      <KBarPortal>
        <KBarPositioner>
          <KBarAnimator className='max-w-3xlLspInfo w-3/6 bg-blue-100/10 backdrop-blur-lg rounded-lg overflow-hidden'>
            <KBarSearch className='py-4 px-5 text-md w-full outline-none border-none bg-blue-500/10 backdrop-blur-lg text-slate-100' />
            <RenderResults />
          </KBarAnimator>
        </KBarPositioner>
      </KBarPortal>
      {props.children}
    </KBarProvider>
  );
}

export default KBarLayout;

And using it in my own layout:

import { Fragment } from 'react';
import Navbar from '@/src/components/ui/navbar/navbar';
import KBarLayout from './KBarLayout';
import SideMenu from '@/src/components/SideMenu';
import clsx from 'clsx';

function Layout(props) {
  // useContext(null);

  return (
    <Fragment>
      <Navbar />
      <KBarLayout>
        <div className='flex flex-row min-h-screen drawer '>
          <aside
            className={clsx([
              'sticky top-0 bottom-0 h-fit overflow-y-auto bg-base-200  z-20', //lg:drawer-open
            ])}
          >
            <input id='my-drawer' type='checkbox' className='drawer-toggle' />
            <SideMenu />
          </aside>
          <main className='min-h-screen pb-20 flex-1 mt-20 drawer-content'>
            {props.children}
          </main>
        </div>
      </KBarLayout>
    </Fragment>
  );
}

export default Layout;

Hope it helps.

Hey! This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.