From 4ddd6a42cc33072d7e172f3b08c144914bd465e6 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Wed, 4 Mar 2026 13:12:14 -0800 Subject: [PATCH 1/2] Add icons, selection cards, and global find bar components New registry components ported from desktop: - runtime-icons: SVG icons for Python, Deno, UV, Conda, and Pixi - selection-card: SelectionCard and PageDots for wizard/onboarding UIs with BRAND_COLORS preset for common tools - global-find-bar: Floating search bar for notebook-wide find functionality with keyboard navigation (Enter/Shift+Enter/Escape) These complement the search-highlight extension added earlier. --- registry.json | 41 +++++++ registry/cell/GlobalFindBar.tsx | 139 +++++++++++++++++++++++ registry/icons/runtime-icons.tsx | 114 +++++++++++++++++++ registry/ui/selection-card.tsx | 187 +++++++++++++++++++++++++++++++ 4 files changed, 481 insertions(+) create mode 100644 registry/cell/GlobalFindBar.tsx create mode 100644 registry/icons/runtime-icons.tsx create mode 100644 registry/ui/selection-card.tsx diff --git a/registry.json b/registry.json index c266812..3def3dd 100644 --- a/registry.json +++ b/registry.json @@ -1043,6 +1043,47 @@ "@nteract/isolated-renderer" ], "files": [] + }, + { + "name": "global-find-bar", + "type": "registry:component", + "title": "Global Find Bar", + "description": "Floating search bar for notebook-wide find functionality. Supports keyboard navigation (Enter/Shift+Enter for next/prev, Escape to close). Works with search-highlight CodeMirror extension for in-cell highlighting.", + "dependencies": ["lucide-react"], + "files": [ + { + "path": "registry/cell/GlobalFindBar.tsx", + "type": "registry:component", + "target": "components/cell/GlobalFindBar.tsx" + } + ] + }, + { + "name": "runtime-icons", + "type": "registry:component", + "title": "Runtime Icons", + "description": "SVG icons for runtime environments and package managers. Includes DenoIcon, PythonIcon, UvIcon, CondaIcon, and PixiIcon for notebook and onboarding UIs.", + "files": [ + { + "path": "registry/icons/runtime-icons.tsx", + "type": "registry:component", + "target": "components/icons/runtime-icons.tsx" + } + ] + }, + { + "name": "selection-card", + "type": "registry:component", + "title": "Selection Card", + "description": "A stylized selection card component for wizard and onboarding UIs. Includes SelectionCard with icon, title, description, and color-coded selection state, plus PageDots for step navigation and BRAND_COLORS preset for common tools.", + "dependencies": ["lucide-react"], + "files": [ + { + "path": "registry/ui/selection-card.tsx", + "type": "registry:component", + "target": "components/ui/selection-card.tsx" + } + ] } ] } diff --git a/registry/cell/GlobalFindBar.tsx b/registry/cell/GlobalFindBar.tsx new file mode 100644 index 0000000..0fde09a --- /dev/null +++ b/registry/cell/GlobalFindBar.tsx @@ -0,0 +1,139 @@ +"use client"; + +/** + * Global find/search bar for notebook-wide search. + * Complements the search-highlight CodeMirror extension for in-cell highlighting. + */ + +import { ChevronDown, ChevronUp, X } from "lucide-react"; +import { useCallback, useEffect, useRef } from "react"; + +export interface GlobalFindBarProps { + /** Current search query */ + query: string; + /** Total number of matches found */ + matchCount: number; + /** Index of the currently active match (0-based) */ + currentMatchIndex: number; + /** Called when the search query changes */ + onQueryChange: (query: string) => void; + /** Called when user navigates to next match */ + onNextMatch: () => void; + /** Called when user navigates to previous match */ + onPrevMatch: () => void; + /** Called when the find bar is closed */ + onClose: () => void; +} + +/** + * A floating search bar for notebook-wide find functionality. + * Supports keyboard navigation (Enter/Shift+Enter for next/prev, Escape to close). + * + * @example + * ```tsx + * setCurrentMatch((i) => (i + 1) % matches.length)} + * onPrevMatch={() => setCurrentMatch((i) => (i - 1 + matches.length) % matches.length)} + * onClose={() => setShowFindBar(false)} + * /> + * ``` + */ +export function GlobalFindBar({ + query, + matchCount, + currentMatchIndex, + onQueryChange, + onNextMatch, + onPrevMatch, + onClose, +}: GlobalFindBarProps) { + const inputRef = useRef(null); + + // Focus input on mount + useEffect(() => { + requestAnimationFrame(() => { + inputRef.current?.focus(); + inputRef.current?.select(); + }); + }, []); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + if (e.key === "Escape") { + e.preventDefault(); + onClose(); + } else if (e.key === "Enter" && e.shiftKey) { + e.preventDefault(); + onPrevMatch(); + } else if (e.key === "Enter") { + e.preventDefault(); + onNextMatch(); + } + }, + [onClose, onNextMatch, onPrevMatch], + ); + + const matchLabel = + query && matchCount > 0 + ? `${currentMatchIndex + 1} of ${matchCount}` + : query + ? "No results" + : ""; + + return ( +
+
+ onQueryChange(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Find in notebook..." + autoComplete="off" + autoCorrect="off" + autoCapitalize="off" + spellCheck={false} + className="h-7 w-full rounded border border-input bg-transparent px-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" + aria-label="Search notebook" + /> +
+ + {matchLabel} + + + + +
+ ); +} diff --git a/registry/icons/runtime-icons.tsx b/registry/icons/runtime-icons.tsx new file mode 100644 index 0000000..10731c0 --- /dev/null +++ b/registry/icons/runtime-icons.tsx @@ -0,0 +1,114 @@ +/** + * Runtime and package manager icons for notebook environments. + * Includes icons for Python, Deno, UV, Conda, and Pixi. + */ + +/** Deno logo icon (from tabler icons) */ +export function DenoIcon({ className }: { className?: string }) { + return ( + + + + + + ); +} + +/** Python logo icon (from tabler icons) */ +export function PythonIcon({ className }: { className?: string }) { + return ( + + + + + + + + ); +} + +/** uv logo icon */ +export function UvIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +/** Conda logo icon (from conda/conda repo) */ +export function CondaIcon({ className }: { className?: string }) { + return ( + + + + + + + + ); +} + +/** Prefix/Pixi logo icon (P mark from prefix.dev) */ +export function PixiIcon({ className }: { className?: string }) { + return ( + + + + ); +} diff --git a/registry/ui/selection-card.tsx b/registry/ui/selection-card.tsx new file mode 100644 index 0000000..f8372ae --- /dev/null +++ b/registry/ui/selection-card.tsx @@ -0,0 +1,187 @@ +"use client"; + +/** + * SelectionCard and PageDots components for wizard/onboarding UIs. + * SelectionCard is a stylized button with icon, title, description, and selected state. + * PageDots provides navigation indicators for multi-step wizards. + */ + +import { Check } from "lucide-react"; +import { cn } from "@/lib/utils"; + +/** Color classes for selection card theming */ +export interface SelectionCardColorClass { + /** Background color when selected (e.g., "bg-blue-500/10") */ + bg: string; + /** Text color when selected (e.g., "text-blue-600 dark:text-blue-400") */ + text: string; + /** Ring color when selected (e.g., "ring-blue-500") */ + ring: string; + /** Icon background color when selected (e.g., "bg-blue-500/20") */ + iconBg: string; +} + +export interface SelectionCardProps { + /** Whether this card is currently selected */ + selected: boolean; + /** Click handler */ + onClick: () => void; + /** Icon component to render */ + icon: React.ComponentType<{ className?: string }>; + /** Card title */ + title: string; + /** Optional subtitle below title */ + subtitle?: string; + /** Description text */ + description: string; + /** Color classes for theming */ + colorClass: SelectionCardColorClass; + /** Additional CSS classes */ + className?: string; +} + +/** + * A selectable card component for wizard/onboarding selection UIs. + * Displays an icon, title, optional subtitle, and description with + * color-coded styling based on selection state. + */ +export function SelectionCard({ + selected, + onClick, + icon: Icon, + title, + subtitle, + description, + colorClass, + className, +}: SelectionCardProps) { + return ( + + ); +} + +export interface PageDotsProps { + /** Current page (1-indexed) */ + current: number; + /** Total number of pages */ + total: number; + /** Additional CSS classes */ + className?: string; +} + +/** + * Navigation dots for multi-step wizards. + * Displays filled dots for the current page and hollow dots for others. + */ +export function PageDots({ current, total, className }: PageDotsProps) { + return ( +
+ {Array.from({ length: total }, (_, i) => ( +
+ ))} +
+ ); +} + +/** Pre-configured brand colors for common runtime/package manager icons */ +export const BRAND_COLORS = { + python: { + bg: "bg-blue-500/10", + text: "text-blue-600 dark:text-blue-400", + ring: "ring-blue-500", + iconBg: "bg-blue-500/20", + }, + deno: { + bg: "bg-emerald-500/10", + text: "text-emerald-600 dark:text-emerald-400", + ring: "ring-emerald-500", + iconBg: "bg-emerald-500/20", + }, + uv: { + bg: "bg-fuchsia-500/10", + text: "text-fuchsia-600 dark:text-fuchsia-400", + ring: "ring-fuchsia-500", + iconBg: "bg-fuchsia-500/20", + }, + conda: { + bg: "bg-green-500/10", + text: "text-green-600 dark:text-green-400", + ring: "ring-green-500", + iconBg: "bg-green-500/20", + }, + pixi: { + bg: "bg-yellow-500/10", + text: "text-yellow-600 dark:text-yellow-400", + ring: "ring-yellow-500", + iconBg: "bg-yellow-500/20", + }, +} as const satisfies Record; From fd34574e22f11e9f9bac2704b69f2c4b354bcb78 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Wed, 4 Mar 2026 15:04:41 -0800 Subject: [PATCH 2/2] Add documentation for icons, selection cards, and global find bar - GlobalFindBar: keyboard navigation, CodeMirror integration examples - Runtime Icons: all 5 icons with sizing examples and color guidance - Selection Card: full wizard example with PageDots and BRAND_COLORS Added new UI section to docs navigation. --- app/components/GlobalFindBarDemo.tsx | 38 +++++ app/components/RuntimeIconsDemo.tsx | 40 +++++ app/components/SelectionCardDemo.tsx | 125 ++++++++++++++ content/docs/cell/global-find-bar.mdx | 117 +++++++++++++ content/docs/meta.json | 4 +- content/docs/ui/runtime-icons.mdx | 123 ++++++++++++++ content/docs/ui/selection-card.mdx | 226 ++++++++++++++++++++++++++ 7 files changed, 672 insertions(+), 1 deletion(-) create mode 100644 app/components/GlobalFindBarDemo.tsx create mode 100644 app/components/RuntimeIconsDemo.tsx create mode 100644 app/components/SelectionCardDemo.tsx create mode 100644 content/docs/cell/global-find-bar.mdx create mode 100644 content/docs/ui/runtime-icons.mdx create mode 100644 content/docs/ui/selection-card.mdx diff --git a/app/components/GlobalFindBarDemo.tsx b/app/components/GlobalFindBarDemo.tsx new file mode 100644 index 0000000..3d14109 --- /dev/null +++ b/app/components/GlobalFindBarDemo.tsx @@ -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 ( +
+ setQuery("")} + /> +
+ ); +} diff --git a/app/components/RuntimeIconsDemo.tsx b/app/components/RuntimeIconsDemo.tsx new file mode 100644 index 0000000..ee31d13 --- /dev/null +++ b/app/components/RuntimeIconsDemo.tsx @@ -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 ( +
+ {icons.map(({ Icon, name, color }) => ( +
+ + {name} +
+ ))} +
+ ); +} diff --git a/app/components/SelectionCardDemo.tsx b/app/components/SelectionCardDemo.tsx new file mode 100644 index 0000000..e3f71b5 --- /dev/null +++ b/app/components/SelectionCardDemo.tsx @@ -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(null); + const [pythonEnv, setPythonEnv] = useState(null); + const [page, setPage] = useState(1); + + return ( +
+ {page === 1 && ( + <> +

+ Choose your preferred runtime +

+
+ setRuntime("python")} + icon={PythonIcon} + title="Python" + description="Scientific computing & data science" + colorClass={BRAND_COLORS.python} + /> + setRuntime("deno")} + icon={DenoIcon} + title="Deno" + description="TypeScript/JS notebooks" + colorClass={BRAND_COLORS.deno} + /> +
+ + )} + + {page === 2 && ( + <> +

+ Choose your package manager +

+
+ setPythonEnv("uv")} + icon={UvIcon} + title="UV" + description="PyPI & pip-compatible" + colorClass={BRAND_COLORS.uv} + /> + setPythonEnv("conda")} + icon={CondaIcon} + title="Conda" + description="Scientific stack & private channels" + colorClass={BRAND_COLORS.conda} + /> +
+ + )} + +
+ {page === 2 && ( + + )} + + {page === 1 && runtime && ( + + )} +
+
+ ); +} + +export function PageDotsDemo() { + const [current, setCurrent] = useState(1); + + return ( +
+ + + +
+ ); +} diff --git a/content/docs/cell/global-find-bar.mdx b/content/docs/cell/global-find-bar.mdx new file mode 100644 index 0000000..b043c99 --- /dev/null +++ b/content/docs/cell/global-find-bar.mdx @@ -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'; + +
+ +
+ +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 + + + + ```bash + npx shadcn@latest add @nteract/global-find-bar + ``` + + + + Copy from [nteract/elements](https://github.com/nteract/elements/blob/main/registry/cell/GlobalFindBar.tsx). + + + +## Usage + +```tsx +import { GlobalFindBar } from "@/components/cell/GlobalFindBar" + +function NotebookHeader() { + const [query, setQuery] = useState("") + const [matches, setMatches] = useState([]) + const [currentMatch, setCurrentMatch] = useState(0) + + return ( + 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 ( + <> + setQuery("")} + /> + + {cells.map((cell) => ( + + ))} + + ) +} +``` + +## 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 | diff --git a/content/docs/meta.json b/content/docs/meta.json index d1caefd..34a8270 100644 --- a/content/docs/meta.json +++ b/content/docs/meta.json @@ -10,6 +10,8 @@ "---Outputs---", "...outputs", "---Widgets---", - "...widgets" + "...widgets", + "---UI---", + "...ui" ] } diff --git a/content/docs/ui/runtime-icons.mdx b/content/docs/ui/runtime-icons.mdx new file mode 100644 index 0000000..6efa88b --- /dev/null +++ b/content/docs/ui/runtime-icons.mdx @@ -0,0 +1,123 @@ +--- +title: Runtime Icons +description: SVG icons for runtime environments and package managers +icon: Cpu +--- + +import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; +import { RegistrySetup } from '@/components/docs/registry-setup'; +import { RuntimeIconsDemo } from '@/app/components/RuntimeIconsDemo'; + +
+ +
+ +A collection of SVG icons for common runtime environments and package managers used in notebook applications. + +## Icons Included + +- **PythonIcon** - Python programming language +- **DenoIcon** - Deno JavaScript/TypeScript runtime +- **UvIcon** - UV Python package manager +- **CondaIcon** - Conda package and environment manager +- **PixiIcon** - Pixi package manager (from prefix.dev) + +## Installation + + + + ```bash + npx shadcn@latest add @nteract/runtime-icons + ``` + + + + Copy from [nteract/elements](https://github.com/nteract/elements/blob/main/registry/icons/runtime-icons.tsx). + + + +## Usage + +```tsx +import { + PythonIcon, + DenoIcon, + UvIcon, + CondaIcon, + PixiIcon, +} from "@/components/icons/runtime-icons" + +function RuntimeSelector() { + return ( +
+ + + + + +
+ ) +} +``` + +## Examples + +### Small Size + +
+ +
+ +```tsx + +``` + +### Medium Size + +
+ +
+ +```tsx + +``` + +### Large Size + +
+ +
+ +```tsx + +``` + +### With Colors + +All icons accept a `className` prop for styling, including colors: + +```tsx + + + + + +``` + +## Props + +All icons share the same props interface: + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `className` | `string` | — | CSS classes for sizing and styling | + +## Icon Details + +| Icon | Source | Style | +| --- | --- | --- | +| `PythonIcon` | Tabler Icons | Stroke-based | +| `DenoIcon` | Tabler Icons | Stroke-based | +| `UvIcon` | UV branding | Fill-based | +| `CondaIcon` | Conda repository | Fill + stroke | +| `PixiIcon` | prefix.dev | Fill-based | diff --git a/content/docs/ui/selection-card.mdx b/content/docs/ui/selection-card.mdx new file mode 100644 index 0000000..167f1c8 --- /dev/null +++ b/content/docs/ui/selection-card.mdx @@ -0,0 +1,226 @@ +--- +title: Selection Card +description: Stylized selection cards for wizard and onboarding UIs +icon: LayoutGrid +--- + +import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; +import { RegistrySetup } from '@/components/docs/registry-setup'; +import { SelectionCardDemo, PageDotsDemo } from '@/app/components/SelectionCardDemo'; + +
+ +
+ +A collection of components for building wizard and onboarding UIs with stylized selection cards and navigation indicators. + +## Components + +- **SelectionCard** - A stylized button with icon, title, description, and selected state +- **PageDots** - Navigation dots for multi-step wizards +- **BRAND_COLORS** - Pre-configured color schemes for common tools + +## Installation + + + + ```bash + npx shadcn@latest add @nteract/selection-card + ``` + + + + Copy from [nteract/elements](https://github.com/nteract/elements/blob/main/registry/ui/selection-card.tsx). + + + +## Usage + +### SelectionCard + +```tsx +import { SelectionCard, BRAND_COLORS } from "@/components/ui/selection-card" +import { PythonIcon } from "@/components/icons/runtime-icons" + +function RuntimeSelector() { + const [selected, setSelected] = useState(null) + + return ( + setSelected("python")} + icon={PythonIcon} + title="Python" + description="Scientific computing & data science" + colorClass={BRAND_COLORS.python} + /> + ) +} +``` + +### PageDots + +
+ +
+ +```tsx +import { PageDots } from "@/components/ui/selection-card" + +function WizardNavigation() { + const [currentPage, setCurrentPage] = useState(1) + const totalPages = 4 + + return +} +``` + +## BRAND_COLORS + +Pre-configured color schemes for common runtime and package manager icons: + +```tsx +import { BRAND_COLORS } from "@/components/ui/selection-card" + +// Available colors: +BRAND_COLORS.python // Blue theme +BRAND_COLORS.deno // Emerald theme +BRAND_COLORS.uv // Fuchsia theme +BRAND_COLORS.conda // Green theme +BRAND_COLORS.pixi // Yellow theme +``` + +Each color scheme includes: + +```typescript +interface SelectionCardColorClass { + bg: string; // Background when selected (e.g., "bg-blue-500/10") + text: string; // Text color when selected + ring: string; // Ring color when selected + iconBg: string; // Icon background when selected +} +``` + +## Custom Colors + +You can create custom color schemes: + +```tsx +const customColor = { + bg: "bg-purple-500/10", + text: "text-purple-600 dark:text-purple-400", + ring: "ring-purple-500", + iconBg: "bg-purple-500/20", +} + + +``` + +## SelectionCard Props + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `selected` | `boolean` | — | Whether this card is currently selected | +| `onClick` | `() => void` | — | Click handler | +| `icon` | `ComponentType<{ className?: string }>` | — | Icon component to render | +| `title` | `string` | — | Card title | +| `subtitle` | `string` | — | Optional subtitle below title | +| `description` | `string` | — | Description text | +| `colorClass` | `SelectionCardColorClass` | — | Color classes for theming | +| `className` | `string` | — | Additional CSS classes | + +## PageDots Props + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `current` | `number` | — | Current page (1-indexed) | +| `total` | `number` | — | Total number of pages | +| `className` | `string` | — | Additional CSS classes | + +## Complete Example + +Here's a full onboarding wizard example: + +```tsx +import { useState } from "react" +import { SelectionCard, PageDots, BRAND_COLORS } from "@/components/ui/selection-card" +import { PythonIcon, DenoIcon, UvIcon, CondaIcon } from "@/components/icons/runtime-icons" +import { Button } from "@/components/ui/button" + +function OnboardingWizard() { + const [page, setPage] = useState(1) + const [runtime, setRuntime] = useState<"python" | "deno" | null>(null) + const [pythonEnv, setPythonEnv] = useState<"uv" | "conda" | null>(null) + + return ( +
+ {page === 1 && ( + <> +

Choose your runtime

+
+ setRuntime("python")} + icon={PythonIcon} + title="Python" + description="Scientific computing" + colorClass={BRAND_COLORS.python} + /> + setRuntime("deno")} + icon={DenoIcon} + title="Deno" + description="TypeScript/JS" + colorClass={BRAND_COLORS.deno} + /> +
+ + )} + + {page === 2 && ( + <> +

Choose your package manager

+
+ setPythonEnv("uv")} + icon={UvIcon} + title="UV" + description="PyPI compatible" + colorClass={BRAND_COLORS.uv} + /> + setPythonEnv("conda")} + icon={CondaIcon} + title="Conda" + description="Scientific stack" + colorClass={BRAND_COLORS.conda} + /> +
+ + )} + +
+ {page > 1 && ( + + )} + + {page < 2 && runtime && ( + + )} +
+
+ ) +} +```