'use client'; import { type IconType, 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 '@icons-pack/react-simple-icons'; import { useControllableState } from '@radix-ui/react-use-controllable-state'; import { CheckIcon, CopyIcon } from 'lucide-react'; import type { ComponentProps, HTMLAttributes, ReactElement, ReactNode, } from 'react'; import { cloneElement, createContext, useContext, useEffect, useState, } from 'react'; import { Button } from '@/components/ui/shadcn-io/button'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/shadcn-io/select'; import { cn } from '@/lib/utils'; export type BundledLanguage = string; 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-sm', '[&_pre]:py-4', '[&_.shiki]:!bg-[var(--shiki-bg)]', '[&_code]:w-full', '[&_code]:grid', '[&_code]:overflow-x-auto', '[&_code]:bg-transparent', '[&_.line]:px-4', '[&_.line]:w-full', '[&_.line]:relative' ); 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: [], }); export 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 (
); }; export type CodeBlockHeaderProps = HTMLAttributes; export const CodeBlockHeader = ({ className, ...props }: CodeBlockHeaderProps) => (
); export type CodeBlockFilesProps = Omit< HTMLAttributes, 'children' > & { children: (item: CodeBlockData) => ReactNode; }; export const CodeBlockFiles = ({ className, children, ...props }: CodeBlockFilesProps) => { const { data } = useContext(CodeBlockContext); return (
{data.map(children)}
); }; export 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}
); }; export type CodeBlockSelectProps = ComponentProps; export const CodeBlockSelect = (props: CodeBlockSelectProps) => { const { value, onValueChange } = useContext(CodeBlockContext); return