Skip to content

Commit d59e98e

Browse files
authored
Add icons, selection cards, and global find bar components (#196)
1 parent 9b524df commit d59e98e

File tree

11 files changed

+1153
-1
lines changed

11 files changed

+1153
-1
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { GlobalFindBar } from "@/registry/cell/GlobalFindBar";
5+
6+
export function GlobalFindBarDemo() {
7+
const [query, setQuery] = useState("");
8+
const [currentMatch, setCurrentMatch] = useState(0);
9+
10+
// Simulate finding matches
11+
const matchCount = query.length > 0 ? Math.min(query.length * 2, 15) : 0;
12+
13+
const handleNext = () => {
14+
if (matchCount > 0) {
15+
setCurrentMatch((prev) => (prev + 1) % matchCount);
16+
}
17+
};
18+
19+
const handlePrev = () => {
20+
if (matchCount > 0) {
21+
setCurrentMatch((prev) => (prev - 1 + matchCount) % matchCount);
22+
}
23+
};
24+
25+
return (
26+
<div className="rounded-lg border overflow-hidden">
27+
<GlobalFindBar
28+
query={query}
29+
matchCount={matchCount}
30+
currentMatchIndex={currentMatch}
31+
onQueryChange={setQuery}
32+
onNextMatch={handleNext}
33+
onPrevMatch={handlePrev}
34+
onClose={() => setQuery("")}
35+
/>
36+
</div>
37+
);
38+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"use client";
2+
3+
import {
4+
CondaIcon,
5+
DenoIcon,
6+
PixiIcon,
7+
PythonIcon,
8+
UvIcon,
9+
} from "@/registry/icons/runtime-icons";
10+
11+
interface RuntimeIconsDemoProps {
12+
size?: "sm" | "md" | "lg";
13+
}
14+
15+
export function RuntimeIconsDemo({ size = "md" }: RuntimeIconsDemoProps) {
16+
const sizeClass = {
17+
sm: "h-6 w-6",
18+
md: "h-10 w-10",
19+
lg: "h-16 w-16",
20+
}[size];
21+
22+
const icons = [
23+
{ Icon: PythonIcon, name: "Python", color: "text-blue-500" },
24+
{ Icon: DenoIcon, name: "Deno", color: "text-emerald-500" },
25+
{ Icon: UvIcon, name: "UV", color: "text-fuchsia-500" },
26+
{ Icon: CondaIcon, name: "Conda", color: "text-green-500" },
27+
{ Icon: PixiIcon, name: "Pixi", color: "text-yellow-500" },
28+
];
29+
30+
return (
31+
<div className="flex flex-wrap gap-6 items-end">
32+
{icons.map(({ Icon, name, color }) => (
33+
<div key={name} className="flex flex-col items-center gap-2">
34+
<Icon className={`${sizeClass} ${color}`} />
35+
<span className="text-xs text-muted-foreground">{name}</span>
36+
</div>
37+
))}
38+
</div>
39+
);
40+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import {
5+
CondaIcon,
6+
DenoIcon,
7+
PythonIcon,
8+
UvIcon,
9+
} from "@/registry/icons/runtime-icons";
10+
import {
11+
BRAND_COLORS,
12+
PageDots,
13+
SelectionCard,
14+
} from "@/registry/ui/selection-card";
15+
16+
type Runtime = "python" | "deno" | null;
17+
type PythonEnv = "uv" | "conda" | null;
18+
19+
export function SelectionCardDemo() {
20+
const [runtime, setRuntime] = useState<Runtime>(null);
21+
const [pythonEnv, setPythonEnv] = useState<PythonEnv>(null);
22+
const [page, setPage] = useState(1);
23+
24+
return (
25+
<div className="space-y-6">
26+
{page === 1 && (
27+
<>
28+
<p className="text-sm text-muted-foreground text-center">
29+
Choose your preferred runtime
30+
</p>
31+
<div className="flex items-center justify-center gap-6">
32+
<SelectionCard
33+
selected={runtime === "python"}
34+
onClick={() => setRuntime("python")}
35+
icon={PythonIcon}
36+
title="Python"
37+
description="Scientific computing & data science"
38+
colorClass={BRAND_COLORS.python}
39+
/>
40+
<SelectionCard
41+
selected={runtime === "deno"}
42+
onClick={() => setRuntime("deno")}
43+
icon={DenoIcon}
44+
title="Deno"
45+
description="TypeScript/JS notebooks"
46+
colorClass={BRAND_COLORS.deno}
47+
/>
48+
</div>
49+
</>
50+
)}
51+
52+
{page === 2 && (
53+
<>
54+
<p className="text-sm text-muted-foreground text-center">
55+
Choose your package manager
56+
</p>
57+
<div className="flex items-center justify-center gap-6">
58+
<SelectionCard
59+
selected={pythonEnv === "uv"}
60+
onClick={() => setPythonEnv("uv")}
61+
icon={UvIcon}
62+
title="UV"
63+
description="PyPI & pip-compatible"
64+
colorClass={BRAND_COLORS.uv}
65+
/>
66+
<SelectionCard
67+
selected={pythonEnv === "conda"}
68+
onClick={() => setPythonEnv("conda")}
69+
icon={CondaIcon}
70+
title="Conda"
71+
description="Scientific stack & private channels"
72+
colorClass={BRAND_COLORS.conda}
73+
/>
74+
</div>
75+
</>
76+
)}
77+
78+
<div className="flex items-center justify-center gap-4">
79+
{page === 2 && (
80+
<button
81+
type="button"
82+
onClick={() => setPage(1)}
83+
className="text-sm text-muted-foreground hover:text-foreground"
84+
>
85+
Back
86+
</button>
87+
)}
88+
<PageDots current={page} total={2} />
89+
{page === 1 && runtime && (
90+
<button
91+
type="button"
92+
onClick={() => setPage(2)}
93+
className="text-sm text-muted-foreground hover:text-foreground"
94+
>
95+
Next
96+
</button>
97+
)}
98+
</div>
99+
</div>
100+
);
101+
}
102+
103+
export function PageDotsDemo() {
104+
const [current, setCurrent] = useState(1);
105+
106+
return (
107+
<div className="flex items-center gap-4">
108+
<button
109+
type="button"
110+
onClick={() => setCurrent((p) => Math.max(1, p - 1))}
111+
className="text-sm px-2 py-1 rounded border"
112+
>
113+
Prev
114+
</button>
115+
<PageDots current={current} total={4} />
116+
<button
117+
type="button"
118+
onClick={() => setCurrent((p) => Math.min(4, p + 1))}
119+
className="text-sm px-2 py-1 rounded border"
120+
>
121+
Next
122+
</button>
123+
</div>
124+
);
125+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
---
2+
title: GlobalFindBar
3+
description: Floating search bar for notebook-wide find functionality with keyboard navigation
4+
icon: Search
5+
---
6+
7+
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
8+
import { RegistrySetup } from '@/components/docs/registry-setup';
9+
import { GlobalFindBarDemo } from '@/app/components/GlobalFindBarDemo';
10+
11+
<div className="my-8">
12+
<GlobalFindBarDemo />
13+
</div>
14+
15+
A floating search bar component for notebook-wide find functionality. Supports keyboard navigation for efficient searching through cells.
16+
17+
## Features
18+
19+
- **Keyboard navigation**: Enter for next match, Shift+Enter for previous, Escape to close
20+
- **Match counter**: Shows current position and total matches
21+
- **Auto-focus**: Input field is focused and selected on mount
22+
- **Accessible**: Full ARIA labels and keyboard support
23+
24+
## Installation
25+
26+
<Tabs items={['CLI', 'Manual']}>
27+
<Tab value="CLI">
28+
```bash
29+
npx shadcn@latest add @nteract/global-find-bar
30+
```
31+
<RegistrySetup />
32+
</Tab>
33+
<Tab value="Manual">
34+
Copy from [nteract/elements](https://github.com/nteract/elements/blob/main/registry/cell/GlobalFindBar.tsx).
35+
</Tab>
36+
</Tabs>
37+
38+
## Usage
39+
40+
```tsx
41+
import { GlobalFindBar } from "@/components/cell/GlobalFindBar"
42+
43+
function NotebookHeader() {
44+
const [query, setQuery] = useState("")
45+
const [matches, setMatches] = useState<Match[]>([])
46+
const [currentMatch, setCurrentMatch] = useState(0)
47+
48+
return (
49+
<GlobalFindBar
50+
query={query}
51+
matchCount={matches.length}
52+
currentMatchIndex={currentMatch}
53+
onQueryChange={setQuery}
54+
onNextMatch={() => setCurrentMatch((i) => (i + 1) % matches.length)}
55+
onPrevMatch={() => setCurrentMatch((i) => (i - 1 + matches.length) % matches.length)}
56+
onClose={() => setShowFindBar(false)}
57+
/>
58+
)
59+
}
60+
```
61+
62+
## Integration with CodeMirror
63+
64+
GlobalFindBar pairs well with the `searchHighlight` CodeMirror extension for highlighting matches within cells:
65+
66+
```tsx
67+
import { GlobalFindBar } from "@/components/cell/GlobalFindBar"
68+
import { searchHighlight } from "@/components/editor/search-highlight"
69+
import { CodeMirrorEditor } from "@/components/editor/codemirror-editor"
70+
71+
function SearchableNotebook() {
72+
const [query, setQuery] = useState("")
73+
const [activeOffset, setActiveOffset] = useState(-1)
74+
75+
return (
76+
<>
77+
<GlobalFindBar
78+
query={query}
79+
matchCount={matchCount}
80+
currentMatchIndex={currentMatch}
81+
onQueryChange={setQuery}
82+
onNextMatch={handleNextMatch}
83+
onPrevMatch={handlePrevMatch}
84+
onClose={() => setQuery("")}
85+
/>
86+
87+
{cells.map((cell) => (
88+
<CodeMirrorEditor
89+
key={cell.id}
90+
value={cell.source}
91+
extensions={[searchHighlight(query, activeOffset)]}
92+
/>
93+
))}
94+
</>
95+
)
96+
}
97+
```
98+
99+
## Props
100+
101+
| Prop | Type | Default | Description |
102+
| --- | --- | --- | --- |
103+
| `query` | `string` || Current search query |
104+
| `matchCount` | `number` || Total number of matches found |
105+
| `currentMatchIndex` | `number` || Index of the currently active match (0-based) |
106+
| `onQueryChange` | `(query: string) => void` || Called when the search query changes |
107+
| `onNextMatch` | `() => void` || Called when user navigates to next match |
108+
| `onPrevMatch` | `() => void` || Called when user navigates to previous match |
109+
| `onClose` | `() => void` || Called when the find bar is closed |
110+
111+
## Keyboard Shortcuts
112+
113+
| Key | Action |
114+
| --- | --- |
115+
| `Enter` | Go to next match |
116+
| `Shift + Enter` | Go to previous match |
117+
| `Escape` | Close the find bar |

content/docs/meta.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
"---Outputs---",
1111
"...outputs",
1212
"---Widgets---",
13-
"...widgets"
13+
"...widgets",
14+
"---UI---",
15+
"...ui"
1416
]
1517
}

0 commit comments

Comments
 (0)