diff --git a/apps/native/.storybook/mocks/tauri-runtime.ts b/apps/native/.storybook/mocks/tauri-runtime.ts index ed72b52de..233c02362 100644 --- a/apps/native/.storybook/mocks/tauri-runtime.ts +++ b/apps/native/.storybook/mocks/tauri-runtime.ts @@ -262,6 +262,14 @@ export const storybookDarwinAPI = { cached: async () => baseGitStatus(), commit: async () => ({ hash: "mock123", evolveState: baseEvolveState() }), stash: async () => okResult(), + fileDiffContents: async (_filenames: string[]) => ({}), + stageAll: async () => undefined, + unstageAll: async () => undefined, + restoreAll: async () => undefined, + checkoutNewBranch: async (branchName: string) => ({ ok: true, branch: branchName }), + checkoutBranch: async () => undefined, + checkoutMainBranch: async () => undefined, + mergeBranch: async () => undefined, }, darwin: { evolve: async () => ({ diff --git a/apps/native/.storybook/vitest.setup.ts b/apps/native/.storybook/vitest.setup.ts index 2e5fe204a..a750c2f1d 100644 --- a/apps/native/.storybook/vitest.setup.ts +++ b/apps/native/.storybook/vitest.setup.ts @@ -31,7 +31,19 @@ function normalizeSnapshotRoot(root: Element): string { editor.appendChild(placeholder); } - return normalizeAnimations(clone.innerHTML); + for (const editor of clone.querySelectorAll(".monaco-diff-editor, .monaco-editor")) { + editor.replaceChildren(); + const placeholder = document.createElement("div"); + placeholder.setAttribute("data-slot", "monaco-editor-placeholder"); + editor.appendChild(placeholder); + } + + let html = normalizeAnimations(clone.innerHTML); + // Monaco assigns auto-incrementing model IDs that vary by test-suite order. + html = html.replace(/inmemory:\/\/model\/\d+/g, "inmemory://model/N"); + // data-keybinding-context values are similarly auto-incremented. + html = html.replace(/data-keybinding-context="\d+"/g, 'data-keybinding-context="N"'); + return html; } // Automatically snapshot every story after it renders diff --git a/apps/native/package.json b/apps/native/package.json index ce793efa9..0e7cf9558 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -67,10 +67,12 @@ "cmdk": "^1.1.1", "execa": "^9.6.0", "lucide-react": "^1.14.0", + "@monaco-editor/react": "^4.7.0", "monaco-editor": "^0.55.1", "monaco-editor-textmate": "^4.0.0", "monaco-textmate": "^3.0.1", "motion": "^12.35.2", + "onigasm": "^2.2.5", "react": "^19.2.3", "react-dom": "^19.1.0", "react-icons": "^5.5.0", diff --git a/apps/native/src-tauri/examples/specta_gen_ts.rs b/apps/native/src-tauri/examples/specta_gen_ts.rs index 7b4f741a7..76d37e173 100644 --- a/apps/native/src-tauri/examples/specta_gen_ts.rs +++ b/apps/native/src-tauri/examples/specta_gen_ts.rs @@ -66,6 +66,7 @@ fn main() { .register::() .register::() .register::() + .register::() .register::() .register::() .register::() diff --git a/apps/native/src-tauri/src/commands/git.rs b/apps/native/src-tauri/src/commands/git.rs index 34b2538a0..7a943bc41 100644 --- a/apps/native/src-tauri/src/commands/git.rs +++ b/apps/native/src-tauri/src/commands/git.rs @@ -4,6 +4,31 @@ use crate::storage::store; use crate::{db, git, shared_types}; use tauri::AppHandle; +/// Initializes a git repository in the config directory if one doesn't exist. +#[tauri::command] +pub async fn git_init_repo(app: AppHandle) -> Result { + let dir = store::ensure_config_dir_exists(&app).map_err(|e| capture_err("git_init_repo", e))?; + git::init_repo(&dir).map_err(|e| capture_err("git_init_repo", e))?; + Ok(shared_types::OkResult::yes()) +} + +/// Returns original (HEAD) and modified (working-tree) content for each requested file. +#[tauri::command] +pub async fn git_file_diff_contents( + app: AppHandle, + filenames: Vec, +) -> Result, String> { + let dir = + store::get_config_dir(&app).map_err(|e| capture_err("git_file_diff_contents", e))?; + Ok(filenames + .into_iter() + .map(|f| { + let (original, modified) = git::exec::file_diff_contents(&dir, &f); + (f, shared_types::FileDiffContents { original, modified }) + }) + .collect()) +} + /// Returns the current git status of the config directory. #[tauri::command] pub async fn git_status(app: AppHandle) -> Result { diff --git a/apps/native/src-tauri/src/commands/ui_prefs.rs b/apps/native/src-tauri/src/commands/ui_prefs.rs index 240c2b298..69d6bcd30 100644 --- a/apps/native/src-tauri/src/commands/ui_prefs.rs +++ b/apps/native/src-tauri/src/commands/ui_prefs.rs @@ -57,6 +57,10 @@ pub async fn ui_get_prefs(app: AppHandle) -> Result Result Result<(), String> { } } } + // Clear state for next open + *lsp_state().lock().await = None; + let _ = app_handle.emit("lsp:exit", ()); }); // Spawn stderr reader — log for debugging. diff --git a/apps/native/src-tauri/src/git/exec.rs b/apps/native/src-tauri/src/git/exec.rs index cbad01f04..b9848d8a4 100644 --- a/apps/native/src-tauri/src/git/exec.rs +++ b/apps/native/src-tauri/src/git/exec.rs @@ -115,6 +115,29 @@ pub fn get_nix_diff(dir: &str) -> Result { Ok(diff) } +pub fn repo_root(dir: &str) -> String { + git_command() + .args(["rev-parse", "--show-toplevel"]) + .current_dir(dir) + .output() + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .unwrap_or_else(|_| dir.to_string()) +} + +/// Returns (original, modified) file content for a single file: HEAD content and working-tree content. +/// Returns empty strings for new files (no HEAD) or deleted files (not on disk). +pub fn file_diff_contents(dir: &str, filename: &str) -> (String, String) { + let original = git_command() + .args(["show", &format!("HEAD:{filename}")]) + .current_dir(dir) + .output() + .map(|o| String::from_utf8_lossy(&o.stdout).into_owned()) + .unwrap_or_default(); + let modified = std::fs::read_to_string(std::path::Path::new(&repo_root(dir)).join(filename)) + .unwrap_or_default(); + (original, modified) +} + /// Returns a diff of tracked changes, optionally restricted to a path glob. /// Falls back to staged+unstaged when HEAD is unborn (fresh repo with no commits). fn get_tracked_diff(dir: &str, path_filter: Option<&str>) -> Result { diff --git a/apps/native/src-tauri/src/main.rs b/apps/native/src-tauri/src/main.rs index f03b149c4..1aa861a89 100644 --- a/apps/native/src-tauri/src/main.rs +++ b/apps/native/src-tauri/src/main.rs @@ -490,11 +490,13 @@ fn run_gui_mode( commands::homebrew::homebrew_apply_diff, commands::homebrew::homebrew_get_state_diff, // Git + commands::git::git_init_repo, commands::git::git_status, commands::git::git_status_and_cache, commands::git::git_cached, commands::git::git_commit, commands::git::git_stash, + commands::git::git_file_diff_contents, // Darwin/Nix commands::evolve::darwin_evolve, commands::evolve::darwin_evolve_cancel, diff --git a/apps/native/src-tauri/src/shared_types/git.rs b/apps/native/src-tauri/src/shared_types/git.rs index cbb665a61..d14d2b3ce 100644 --- a/apps/native/src-tauri/src/shared_types/git.rs +++ b/apps/native/src-tauri/src/shared_types/git.rs @@ -4,6 +4,14 @@ use specta::Type; use super::evolve::EvolveState; use crate::sqlite_types::{Change, ChangeSet, ChangeSummary}; +/// HEAD content vs working-tree content for a file, used by the diff tab Monaco DiffEditor. +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +#[serde(rename_all = "camelCase")] +pub struct FileDiffContents { + pub original: String, + pub modified: String, +} + /// Type of change for a file in git status. #[derive(Debug, Clone, Copy, Serialize, Deserialize, Type)] #[serde(rename_all = "camelCase")] diff --git a/apps/native/src-tauri/src/shared_types/prefs.rs b/apps/native/src-tauri/src/shared_types/prefs.rs index f7261e1f1..3c124e631 100644 --- a/apps/native/src-tauri/src/shared_types/prefs.rs +++ b/apps/native/src-tauri/src/shared_types/prefs.rs @@ -39,6 +39,8 @@ pub struct UiPrefs { pub auto_summarize_on_focus: bool, /// Whether Homebrew state should be scanned on app startup. pub scan_homebrew_on_startup: bool, + /// Whether the change view defaults to the Diff tab instead of Summary. + pub default_to_diff_tab: bool, /// Whether developer-only UI/actions are enabled. pub developer_mode: bool, /// Version pinned by the user, when update pinning is active. @@ -84,6 +86,8 @@ pub struct UiPrefsUpdate { pub auto_summarize_on_focus: Option, /// Startup Homebrew scan preference update. pub scan_homebrew_on_startup: Option, + /// Default-to-diff-tab preference update. + pub default_to_diff_tab: Option, /// Developer mode preference update. pub developer_mode: Option, /// `None` -> field not sent; `Some(None)` -> clear the pinned version. diff --git a/apps/native/src-tauri/src/storage/store.rs b/apps/native/src-tauri/src/storage/store.rs index 4d05ac4b4..2bd3af67c 100644 --- a/apps/native/src-tauri/src/storage/store.rs +++ b/apps/native/src-tauri/src/storage/store.rs @@ -34,6 +34,9 @@ pub const AUTO_SUMMARIZE_ON_FOCUS_KEY: &str = "autoSummarizeOnFocus"; // Startup scan preference keys pub const SCAN_HOMEBREW_ON_STARTUP_KEY: &str = "scanHomebrewOnStartup"; +// Default-tab preference keys +pub const DEFAULT_TO_DIFF_TAB_KEY: &str = "defaultToDiffTab"; + // Developer-mode preference keys pub const DEVELOPER_MODE_KEY: &str = "developerMode"; pub const PINNED_VERSION_KEY: &str = "pinnedVersion"; diff --git a/apps/native/src-tauri/src/summarize/sumlog.rs b/apps/native/src-tauri/src/summarize/sumlog.rs index bbaa8dc7b..93f0c75f7 100644 --- a/apps/native/src-tauri/src/summarize/sumlog.rs +++ b/apps/native/src-tauri/src/summarize/sumlog.rs @@ -9,8 +9,8 @@ pub const FIND_EXISTING: bool = false; pub const GROUP_EXISTING: bool = false; pub const SIMPLIFY_GROUP: bool = false; -pub const FRESH_CHANGESET: bool = false; -pub const EVOLVED_CHANGESET: bool = false; +pub const FRESH_CHANGESET: bool = true; +pub const EVOLVED_CHANGESET: bool = true; pub const QUEUE_SUMMARIZER: bool = false; // ── Imports ─────────────────────────────────────────────────────────────────── diff --git a/apps/native/src/components/kibo-ui/code-block/__snapshots__/code-block.stories.tsx.snap b/apps/native/src/components/kibo-ui/code-block/__snapshots__/code-block.stories.tsx.snap deleted file mode 100644 index dccf55fa2..000000000 --- a/apps/native/src/components/kibo-ui/code-block/__snapshots__/code-block.stories.tsx.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Multi File 1`] = `"
flake.nix
{  description = "nixmac demo";  outputs = { self, nixpkgs }: {    darwinConfigurations.demo = nixpkgs.lib.darwinSystem {      modules = [ ./darwin-configuration.nix ];    };  };}
"`; diff --git a/apps/native/src/components/kibo-ui/code-block/code-block.stories.tsx b/apps/native/src/components/kibo-ui/code-block/code-block.stories.tsx deleted file mode 100644 index 68caf5172..000000000 --- a/apps/native/src/components/kibo-ui/code-block/code-block.stories.tsx +++ /dev/null @@ -1,78 +0,0 @@ -// @ts-nocheck - Storybook 10 alpha types have inference issues (resolves to `never`) -import preview from "#storybook/preview"; -import { - type BundledLanguage, - CodeBlock, - CodeBlockBody, - CodeBlockContent, - CodeBlockCopyButton, - CodeBlockFiles, - CodeBlockFilename, - CodeBlockHeader, - CodeBlockItem, - CodeBlockSelect, - CodeBlockSelectContent, - CodeBlockSelectItem, - CodeBlockSelectTrigger, - CodeBlockSelectValue, -} from "./index"; - -const meta = preview.meta({ - title: "Kibo UI/CodeBlock", - component: CodeBlock, - parameters: { layout: "centered" }, - tags: ["autodocs"], -}); - -export default meta; - -const files = [ - { - language: "nix", - filename: "flake.nix", - code: `{\n description = \"nixmac demo\";\n\n outputs = { self, nixpkgs }: {\n darwinConfigurations.demo = nixpkgs.lib.darwinSystem {\n modules = [ ./darwin-configuration.nix ];\n };\n };\n}`, - }, - { - language: "typescript", - filename: "settings.ts", - code: `export const provider = \"codex\";\nexport const maxIterations = 25;\n\nexport function ready() {\n return provider.length > 0;\n}`, - }, -]; - -export const MultiFile = meta.story({ - render: () => ( - - - - {(item) => ( - - {item.filename} - - )} - - - - - - - {(item) => ( - - {item.filename} - - )} - - - - - - {(item) => ( - - - {item.code} - - - )} - - - ), -}); diff --git a/apps/native/src/components/kibo-ui/code-block/index.tsx b/apps/native/src/components/kibo-ui/code-block/index.tsx deleted file mode 100644 index ec7709b04..000000000 --- a/apps/native/src/components/kibo-ui/code-block/index.tsx +++ /dev/null @@ -1,582 +0,0 @@ -"use client"; - -import { useControllableState } from "@radix-ui/react-use-controllable-state"; -import { - transformerNotationDiff, - transformerNotationErrorLevel, - transformerNotationFocus, - transformerNotationHighlight, - transformerNotationWordHighlight, -} from "@shikijs/transformers"; -import { CheckIcon, CopyIcon } from "lucide-react"; -import type { ComponentProps, HTMLAttributes, ReactElement, ReactNode } from "react"; -import { cloneElement, createContext, useContext, useEffect, useState } from "react"; -import type { IconType } from "react-icons"; -import { - SiAstro, - SiBiome, - SiBower, - SiBun, - SiC, - SiCircleci, - SiCoffeescript, - SiCplusplus, - SiCss, - SiCssmodules, - SiDart, - SiDocker, - SiDocusaurus, - SiDotenv, - SiEditorconfig, - SiEslint, - SiGatsby, - SiGitignoredotio, - SiGnubash, - SiGo, - SiGraphql, - SiGrunt, - SiGulp, - SiHandlebarsdotjs, - SiHtml5, - SiJavascript, - SiJest, - SiJson, - SiLess, - SiMarkdown, - SiMdx, - SiMintlify, - SiMocha, - SiMysql, - SiNextdotjs, - SiPerl, - SiPhp, - SiPostcss, - SiPrettier, - SiPrisma, - SiPug, - SiPython, - SiR, - SiReact, - SiReadme, - SiRedis, - SiRemix, - SiRive, - SiRollupdotjs, - SiRuby, - SiSanity, - SiSass, - SiScala, - SiSentry, - SiShadcnui, - SiStorybook, - SiStylelint, - SiSublimetext, - SiSvelte, - SiSvg, - SiSwift, - SiTailwindcss, - SiToml, - SiTypescript, - SiVercel, - SiVite, - SiVuedotjs, - SiWebassembly, -} from "react-icons/si"; -import { type BundledLanguage, type CodeOptionsMultipleThemes, codeToHtml } from "shiki"; -import { Button } from "@/components/ui/button"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { cn } from "@/lib/utils"; - -export type { BundledLanguage } from "shiki"; - -const filenameIconMap = { - ".env": SiDotenv, - "*.astro": SiAstro, - "biome.json": SiBiome, - ".bowerrc": SiBower, - "bun.lockb": SiBun, - "*.c": SiC, - "*.cpp": SiCplusplus, - ".circleci/config.yml": SiCircleci, - "*.coffee": SiCoffeescript, - "*.module.css": SiCssmodules, - "*.css": SiCss, - "*.dart": SiDart, - Dockerfile: SiDocker, - "docusaurus.config.js": SiDocusaurus, - ".editorconfig": SiEditorconfig, - ".eslintrc": SiEslint, - "eslint.config.*": SiEslint, - "gatsby-config.*": SiGatsby, - ".gitignore": SiGitignoredotio, - "*.go": SiGo, - "*.graphql": SiGraphql, - "*.sh": SiGnubash, - "Gruntfile.*": SiGrunt, - "gulpfile.*": SiGulp, - "*.hbs": SiHandlebarsdotjs, - "*.html": SiHtml5, - "*.js": SiJavascript, - "*.json": SiJson, - "*.test.js": SiJest, - "*.less": SiLess, - "*.md": SiMarkdown, - "*.mdx": SiMdx, - "mintlify.json": SiMintlify, - "mocha.opts": SiMocha, - "*.mustache": SiHandlebarsdotjs, - "*.sql": SiMysql, - "next.config.*": SiNextdotjs, - "*.pl": SiPerl, - "*.php": SiPhp, - "postcss.config.*": SiPostcss, - "prettier.config.*": SiPrettier, - "*.prisma": SiPrisma, - "*.pug": SiPug, - "*.py": SiPython, - "*.r": SiR, - "*.rb": SiRuby, - "*.jsx": SiReact, - "*.tsx": SiReact, - "readme.md": SiReadme, - "*.rdb": SiRedis, - "remix.config.*": SiRemix, - "*.riv": SiRive, - "rollup.config.*": SiRollupdotjs, - "sanity.config.*": SiSanity, - "*.sass": SiSass, - "*.scss": SiSass, - "*.sc": SiScala, - "*.scala": SiScala, - "sentry.client.config.*": SiSentry, - "components.json": SiShadcnui, - "storybook.config.*": SiStorybook, - "stylelint.config.*": SiStylelint, - ".sublime-settings": SiSublimetext, - "*.svelte": SiSvelte, - "*.svg": SiSvg, - "*.swift": SiSwift, - "tailwind.config.*": SiTailwindcss, - "*.toml": SiToml, - "*.ts": SiTypescript, - "vercel.json": SiVercel, - "vite.config.*": SiVite, - "*.vue": SiVuedotjs, - "*.wasm": SiWebassembly, -}; - -const lineNumberClassNames = cn( - "[&_code]:[counter-reset:line]", - "[&_code]:[counter-increment:line_0]", - "[&_.line]:before:content-[counter(line)]", - "[&_.line]:before:inline-block", - "[&_.line]:before:[counter-increment:line]", - "[&_.line]:before:w-4", - "[&_.line]:before:mr-4", - "[&_.line]:before:text-[13px]", - "[&_.line]:before:text-right", - "[&_.line]:before:text-muted-foreground/50", - "[&_.line]:before:font-mono", - "[&_.line]:before:select-none", -); - -const darkModeClassNames = cn( - "dark:[&_.shiki]:!text-[var(--shiki-dark)]", - // "dark:[&_.shiki]:!bg-[var(--shiki-dark-bg)]", - "dark:[&_.shiki]:![font-style:var(--shiki-dark-font-style)]", - "dark:[&_.shiki]:![font-weight:var(--shiki-dark-font-weight)]", - "dark:[&_.shiki]:![text-decoration:var(--shiki-dark-text-decoration)]", - "dark:[&_.shiki_span]:!text-[var(--shiki-dark)]", - "dark:[&_.shiki_span]:![font-style:var(--shiki-dark-font-style)]", - "dark:[&_.shiki_span]:![font-weight:var(--shiki-dark-font-weight)]", - "dark:[&_.shiki_span]:![text-decoration:var(--shiki-dark-text-decoration)]", -); - -const lineHighlightClassNames = cn( - "[&_.line.highlighted]:bg-blue-50", - "[&_.line.highlighted]:after:bg-blue-500", - "[&_.line.highlighted]:after:absolute", - "[&_.line.highlighted]:after:left-0", - "[&_.line.highlighted]:after:top-0", - "[&_.line.highlighted]:after:bottom-0", - "[&_.line.highlighted]:after:w-0.5", - "dark:[&_.line.highlighted]:!bg-blue-500/10", -); - -const lineDiffClassNames = cn( - "[&_.line.diff]:after:absolute", - "[&_.line.diff]:after:left-0", - "[&_.line.diff]:after:top-0", - "[&_.line.diff]:after:bottom-0", - "[&_.line.diff]:after:w-0.5", - "[&_.line.diff.add]:bg-emerald-50", - "[&_.line.diff.add]:after:bg-emerald-500", - "[&_.line.diff.remove]:bg-rose-50", - "[&_.line.diff.remove]:after:bg-rose-500", - "dark:[&_.line.diff.add]:!bg-emerald-500/10", - "dark:[&_.line.diff.remove]:!bg-rose-500/10", -); - -const lineFocusedClassNames = cn( - "[&_code:has(.focused)_.line]:blur-[2px]", - "[&_code:has(.focused)_.line.focused]:blur-none", -); - -const wordHighlightClassNames = cn( - "[&_.highlighted-word]:bg-blue-50", - "dark:[&_.highlighted-word]:!bg-blue-500/10", -); - -const codeBlockClassName = cn( - "mt-0 bg-background text-xs", - "[&_pre]:py-4", - // "[&_.shiki]:!bg-[var(--shiki-bg)]", - "[&_.shiki]:!bg-transparent", - "[&_code]:min-w-full", - "[&_code]:grid", - "[&_code]:overflow-x-auto", - "[&_code]:bg-transparent", - "[&_.line]:px-4", - "[&_.line]:min-w-full", - "[&_.line]:relative", -); - -const highlight = ( - html: string, - language?: BundledLanguage, - themes?: CodeOptionsMultipleThemes["themes"], -) => - codeToHtml(html, { - lang: language ?? "typescript", - themes: themes ?? { - light: "github-light", - dark: "github-dark-default", - }, - transformers: [ - transformerNotationDiff({ - matchAlgorithm: "v3", - }), - transformerNotationHighlight({ - matchAlgorithm: "v3", - }), - transformerNotationWordHighlight({ - matchAlgorithm: "v3", - }), - transformerNotationFocus({ - matchAlgorithm: "v3", - }), - transformerNotationErrorLevel({ - matchAlgorithm: "v3", - }), - ], - }); - -type CodeBlockData = { - language: string; - filename: string; - code: string; -}; - -type CodeBlockContextType = { - value: string | undefined; - onValueChange: ((value: string) => void) | undefined; - data: CodeBlockData[]; -}; - -const CodeBlockContext = createContext({ - value: undefined, - onValueChange: undefined, - data: [], -}); - -type CodeBlockProps = HTMLAttributes & { - defaultValue?: string; - value?: string; - onValueChange?: (value: string) => void; - data: CodeBlockData[]; -}; - -export const CodeBlock = ({ - value: controlledValue, - onValueChange: controlledOnValueChange, - defaultValue, - className, - data, - ...props -}: CodeBlockProps) => { - const [value, onValueChange] = useControllableState({ - defaultProp: defaultValue ?? "", - prop: controlledValue, - onChange: controlledOnValueChange, - }); - - return ( - -
- - ); -}; - -type CodeBlockHeaderProps = HTMLAttributes; - -export const CodeBlockHeader = ({ className, ...props }: CodeBlockHeaderProps) => ( -
-); - -type CodeBlockFilesProps = Omit, "children"> & { - children: (item: CodeBlockData) => ReactNode; -}; - -export const CodeBlockFiles = ({ className, children, ...props }: CodeBlockFilesProps) => { - const { data } = useContext(CodeBlockContext); - - return ( -
- {data.map(children)} -
- ); -}; - -type CodeBlockFilenameProps = HTMLAttributes & { - icon?: IconType; - value?: string; -}; - -export const CodeBlockFilename = ({ - className, - icon, - value, - children, - ...props -}: CodeBlockFilenameProps) => { - const { value: activeValue } = useContext(CodeBlockContext); - const defaultIcon = Object.entries(filenameIconMap).find(([pattern]) => { - const regex = new RegExp( - `^${pattern.replace(/\\/g, "\\\\").replace(/\./g, "\\.").replace(/\*/g, ".*")}$`, - ); - return regex.test(children as string); - })?.[1]; - const Icon = icon ?? defaultIcon; - - if (value !== activeValue) { - return null; - } - - return ( -
- {Icon && } - {children} -
- ); -}; - -type CodeBlockSelectProps = ComponentProps; - -export const CodeBlockSelect = (props: CodeBlockSelectProps) => { - const { value, onValueChange } = useContext(CodeBlockContext); - - return