Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions app/components/GlobalFindBarDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client";

import { useState } from "react";
import { GlobalFindBar } from "@/registry/cell/GlobalFindBar";

export function GlobalFindBarDemo() {
const [query, setQuery] = useState("");
const [currentMatch, setCurrentMatch] = useState(0);

// Simulate finding matches
const matchCount = query.length > 0 ? Math.min(query.length * 2, 15) : 0;

const handleNext = () => {
if (matchCount > 0) {
setCurrentMatch((prev) => (prev + 1) % matchCount);
}
};

const handlePrev = () => {
if (matchCount > 0) {
setCurrentMatch((prev) => (prev - 1 + matchCount) % matchCount);
}
};

return (
<div className="rounded-lg border overflow-hidden">
<GlobalFindBar
query={query}
matchCount={matchCount}
currentMatchIndex={currentMatch}
onQueryChange={setQuery}
onNextMatch={handleNext}
onPrevMatch={handlePrev}
onClose={() => setQuery("")}
/>
</div>
);
}
40 changes: 40 additions & 0 deletions app/components/RuntimeIconsDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use client";

import {
CondaIcon,
DenoIcon,
PixiIcon,
PythonIcon,
UvIcon,
} from "@/registry/icons/runtime-icons";

interface RuntimeIconsDemoProps {
size?: "sm" | "md" | "lg";
}

export function RuntimeIconsDemo({ size = "md" }: RuntimeIconsDemoProps) {
const sizeClass = {
sm: "h-6 w-6",
md: "h-10 w-10",
lg: "h-16 w-16",
}[size];

const icons = [
{ Icon: PythonIcon, name: "Python", color: "text-blue-500" },
{ Icon: DenoIcon, name: "Deno", color: "text-emerald-500" },
{ Icon: UvIcon, name: "UV", color: "text-fuchsia-500" },
{ Icon: CondaIcon, name: "Conda", color: "text-green-500" },
{ Icon: PixiIcon, name: "Pixi", color: "text-yellow-500" },
];

return (
<div className="flex flex-wrap gap-6 items-end">
{icons.map(({ Icon, name, color }) => (
<div key={name} className="flex flex-col items-center gap-2">
<Icon className={`${sizeClass} ${color}`} />
<span className="text-xs text-muted-foreground">{name}</span>
</div>
))}
</div>
);
}
125 changes: 125 additions & 0 deletions app/components/SelectionCardDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"use client";

import { useState } from "react";
import {
CondaIcon,
DenoIcon,
PythonIcon,
UvIcon,
} from "@/registry/icons/runtime-icons";
import {
BRAND_COLORS,
PageDots,
SelectionCard,
} from "@/registry/ui/selection-card";

type Runtime = "python" | "deno" | null;
type PythonEnv = "uv" | "conda" | null;

export function SelectionCardDemo() {
const [runtime, setRuntime] = useState<Runtime>(null);
const [pythonEnv, setPythonEnv] = useState<PythonEnv>(null);
const [page, setPage] = useState(1);

return (
<div className="space-y-6">
{page === 1 && (
<>
<p className="text-sm text-muted-foreground text-center">
Choose your preferred runtime
</p>
<div className="flex items-center justify-center gap-6">
<SelectionCard
selected={runtime === "python"}
onClick={() => setRuntime("python")}
icon={PythonIcon}
title="Python"
description="Scientific computing & data science"
colorClass={BRAND_COLORS.python}
/>
<SelectionCard
selected={runtime === "deno"}
onClick={() => setRuntime("deno")}
icon={DenoIcon}
title="Deno"
description="TypeScript/JS notebooks"
colorClass={BRAND_COLORS.deno}
/>
</div>
</>
)}

{page === 2 && (
<>
<p className="text-sm text-muted-foreground text-center">
Choose your package manager
</p>
<div className="flex items-center justify-center gap-6">
<SelectionCard
selected={pythonEnv === "uv"}
onClick={() => setPythonEnv("uv")}
icon={UvIcon}
title="UV"
description="PyPI & pip-compatible"
colorClass={BRAND_COLORS.uv}
/>
<SelectionCard
selected={pythonEnv === "conda"}
onClick={() => setPythonEnv("conda")}
icon={CondaIcon}
title="Conda"
description="Scientific stack & private channels"
colorClass={BRAND_COLORS.conda}
/>
</div>
</>
)}

<div className="flex items-center justify-center gap-4">
{page === 2 && (
<button
type="button"
onClick={() => setPage(1)}
className="text-sm text-muted-foreground hover:text-foreground"
>
Back
</button>
)}
<PageDots current={page} total={2} />
{page === 1 && runtime && (
<button
type="button"
onClick={() => setPage(2)}
className="text-sm text-muted-foreground hover:text-foreground"
>
Next
</button>
)}
</div>
</div>
);
}

export function PageDotsDemo() {
const [current, setCurrent] = useState(1);

return (
<div className="flex items-center gap-4">
<button
type="button"
onClick={() => setCurrent((p) => Math.max(1, p - 1))}
className="text-sm px-2 py-1 rounded border"
>
Prev
</button>
<PageDots current={current} total={4} />
<button
type="button"
onClick={() => setCurrent((p) => Math.min(4, p + 1))}
className="text-sm px-2 py-1 rounded border"
>
Next
</button>
</div>
);
}
117 changes: 117 additions & 0 deletions content/docs/cell/global-find-bar.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
title: GlobalFindBar
description: Floating search bar for notebook-wide find functionality with keyboard navigation
icon: Search
---

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { RegistrySetup } from '@/components/docs/registry-setup';
import { GlobalFindBarDemo } from '@/app/components/GlobalFindBarDemo';

<div className="my-8">
<GlobalFindBarDemo />
</div>

A floating search bar component for notebook-wide find functionality. Supports keyboard navigation for efficient searching through cells.

## Features

- **Keyboard navigation**: Enter for next match, Shift+Enter for previous, Escape to close
- **Match counter**: Shows current position and total matches
- **Auto-focus**: Input field is focused and selected on mount
- **Accessible**: Full ARIA labels and keyboard support

## Installation

<Tabs items={['CLI', 'Manual']}>
<Tab value="CLI">
```bash
npx shadcn@latest add @nteract/global-find-bar
```
<RegistrySetup />
</Tab>
<Tab value="Manual">
Copy from [nteract/elements](https://github.com/nteract/elements/blob/main/registry/cell/GlobalFindBar.tsx).
</Tab>
</Tabs>

## Usage

```tsx
import { GlobalFindBar } from "@/components/cell/GlobalFindBar"

function NotebookHeader() {
const [query, setQuery] = useState("")
const [matches, setMatches] = useState<Match[]>([])
const [currentMatch, setCurrentMatch] = useState(0)

return (
<GlobalFindBar
query={query}
matchCount={matches.length}
currentMatchIndex={currentMatch}
onQueryChange={setQuery}
onNextMatch={() => setCurrentMatch((i) => (i + 1) % matches.length)}
onPrevMatch={() => setCurrentMatch((i) => (i - 1 + matches.length) % matches.length)}
onClose={() => setShowFindBar(false)}
/>
)
}
```

## Integration with CodeMirror

GlobalFindBar pairs well with the `searchHighlight` CodeMirror extension for highlighting matches within cells:

```tsx
import { GlobalFindBar } from "@/components/cell/GlobalFindBar"
import { searchHighlight } from "@/components/editor/search-highlight"
import { CodeMirrorEditor } from "@/components/editor/codemirror-editor"

function SearchableNotebook() {
const [query, setQuery] = useState("")
const [activeOffset, setActiveOffset] = useState(-1)

return (
<>
<GlobalFindBar
query={query}
matchCount={matchCount}
currentMatchIndex={currentMatch}
onQueryChange={setQuery}
onNextMatch={handleNextMatch}
onPrevMatch={handlePrevMatch}
onClose={() => setQuery("")}
/>

{cells.map((cell) => (
<CodeMirrorEditor
key={cell.id}
value={cell.source}
extensions={[searchHighlight(query, activeOffset)]}
/>
))}
</>
)
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `query` | `string` | — | Current search query |
| `matchCount` | `number` | — | Total number of matches found |
| `currentMatchIndex` | `number` | — | Index of the currently active match (0-based) |
| `onQueryChange` | `(query: string) => void` | — | Called when the search query changes |
| `onNextMatch` | `() => void` | — | Called when user navigates to next match |
| `onPrevMatch` | `() => void` | — | Called when user navigates to previous match |
| `onClose` | `() => void` | — | Called when the find bar is closed |

## Keyboard Shortcuts

| Key | Action |
| --- | --- |
| `Enter` | Go to next match |
| `Shift + Enter` | Go to previous match |
| `Escape` | Close the find bar |
4 changes: 3 additions & 1 deletion content/docs/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
"---Outputs---",
"...outputs",
"---Widgets---",
"...widgets"
"...widgets",
"---UI---",
"...ui"
]
}
Loading