diff --git a/CHANGELOG.md b/CHANGELOG.md index 60573efc..d43222fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## [1.0.17] - 2026-05-01 + +### Fixed + +- Added TOML language preset to CodeGroup to fix missing icon (#235 by @20syldev) +- Removed unintended clipboard copy on Accordion open (#234 by @20syldev) + ## [1.0.16] - 2026-04-17 ### Changed diff --git a/packages/components/package.json b/packages/components/package.json index 697be17f..4d35e9de 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@mintlify/components", - "version": "1.0.16", + "version": "1.0.17", "description": "Mintlify open-source UI components", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/components/src/components/accordion/accordion-url-utils.ts b/packages/components/src/components/accordion/accordion-url-utils.ts index eec4ab1c..65b2e1b3 100644 --- a/packages/components/src/components/accordion/accordion-url-utils.ts +++ b/packages/components/src/components/accordion/accordion-url-utils.ts @@ -1,5 +1,4 @@ import isEqual from "lodash/isEqual"; -import { copyToClipboard } from "@/utils/copy-to-clipboard"; const CONNECTING_CHARACTER = ":"; @@ -42,8 +41,6 @@ const updateAndCopyUrl = () => { const idsString = ids.join(CONNECTING_CHARACTER); const newUrl = buildHistoryUrl(idsString); - copyToClipboard(newUrl); - window.history.replaceState( { ...window.history.state, as: newUrl, url: newUrl }, "", diff --git a/packages/components/src/components/code-block/code-block.stories.tsx b/packages/components/src/components/code-block/code-block.stories.tsx index b7dc7ca3..bb303593 100644 --- a/packages/components/src/components/code-block/code-block.stories.tsx +++ b/packages/components/src/components/code-block/code-block.stories.tsx @@ -464,3 +464,43 @@ export const WithCustomClassName: Story = { ), }; + +export const MDXIndents: Story = { + render: () => ( + + {`import { + a, + b, + } from 'pkg'; + + async function main() { + console.log('hello'); + }`} + + ), +}; + +export const MDXIndentsDeeplyNested: Story = { + render: () => ( + + {`function outer() { + function middle() { + function inner() { + function deepest() { + if (true) { + for (let i = 0; i < 10; i++) { + while (i > 0) { + return 42; + } + } + } + } + return deepest(); + } + return inner(); + } + return middle(); + }`} + + ), +}; diff --git a/packages/components/src/components/code-block/code-block.tsx b/packages/components/src/components/code-block/code-block.tsx index 61ac6772..047d1e0c 100644 --- a/packages/components/src/components/code-block/code-block.tsx +++ b/packages/components/src/components/code-block/code-block.tsx @@ -2,8 +2,8 @@ import type { ReactNode, RefObject } from "react"; import { Classes } from "@/constants/selectors"; import { cn } from "@/utils/cn"; -import { getNodeText } from "@/utils/get-node-text"; import type { CodeBlockTheme, CodeStyling } from "@/utils/shiki/code-styling"; +import { getCodeString } from "@/utils/shiki/lib"; import { BaseCodeBlock } from "./base-code-block"; import { CodeHeader } from "./code-header"; @@ -98,7 +98,7 @@ const CodeBlock = function CodeBlock(params: CodeBlockProps) { copyButtonProps, } = params; - const codeString = getNodeText(children); + const codeString = getCodeString(children, className, true); const hasGrayBackgroundContainer = !!filename || !!icon; return ( diff --git a/packages/components/src/components/code-group/code-group.tsx b/packages/components/src/components/code-group/code-group.tsx index 6f285ccd..c41ce8ad 100644 --- a/packages/components/src/components/code-group/code-group.tsx +++ b/packages/components/src/components/code-group/code-group.tsx @@ -18,8 +18,8 @@ import { import { Icon as ComponentIcon } from "@/components/icon"; import { Classes } from "@/constants/selectors"; import { cn } from "@/utils/cn"; -import { getNodeText } from "@/utils/get-node-text"; import type { CodeBlockTheme, CodeStyling } from "@/utils/shiki/code-styling"; +import { getCodeString } from "@/utils/shiki/lib"; import { LanguageDropdown } from "./language-dropdown"; @@ -220,7 +220,11 @@ const CodeGroup = ({ {feedbackButton && feedbackButton} {askAiButton && askAiButton} diff --git a/packages/components/src/utils/shiki/lib.ts b/packages/components/src/utils/shiki/lib.ts index 99c9bab0..0accfe7d 100644 --- a/packages/components/src/utils/shiki/lib.ts +++ b/packages/components/src/utils/shiki/lib.ts @@ -3,6 +3,14 @@ import { type ReactNode, useMemo } from "react"; import { getNodeText } from "@/utils/get-node-text"; import { SHIKI_CLASSNAME } from "@/utils/shiki/constants"; +const lineIndentRegex = /^( *)/; +const closingStructureRegex = /^([}\])]|<\/)/; + +function getIndent(line: string): number { + const match = line.match(lineIndentRegex); + return match ? match[1].length : 0; +} + function findShikiClassName(children: unknown): boolean { if (!children || typeof children !== "object") { return false; @@ -37,6 +45,49 @@ function findShikiClassName(children: unknown): boolean { return false; } +function dedentCode(code: string): string { + const lines = code.split("\n"); + if (lines.length <= 1) { + return code; + } + + const relevantLines = lines.filter((line) => line.trim() !== ""); + if (relevantLines.length === 0) { + return code; + } + + const firstLine = relevantLines[0]; + const lastLine = relevantLines.at(-1) ?? firstLine; + const firstIndent = getIndent(firstLine); + const lastIndent = getIndent(lastLine); + const isTemplatePolluted = + firstIndent < lastIndent && closingStructureRegex.test(lastLine.trim()); + + if (isTemplatePolluted) { + const firstNonEmptyIndex = lines.findIndex((line) => line.trim() !== ""); + const tail = relevantLines.slice(1); + if (tail.length === 0) { + return code; + } + const minIndent = Math.min(...tail.map(getIndent)); + if (minIndent === 0) { + return code; + } + return lines + .map((line, i) => + i <= firstNonEmptyIndex ? line : line.slice(minIndent) + ) + .join("\n"); + } + + const minIndent = Math.min(...relevantLines.map(getIndent)); + if (minIndent === 0) { + return code; + } + + return lines.map((line) => line.slice(minIndent)).join("\n"); +} + function getCodeString( children: ReactNode, className?: string, @@ -50,7 +101,7 @@ function getCodeString( const codeString = getNodeText(children); - return codeString; + return dedentCode(codeString); } function calculateCodeLinesFromHtml(html: string | undefined): number { diff --git a/packages/components/src/utils/shiki/snippet-presets.ts b/packages/components/src/utils/shiki/snippet-presets.ts index 5041cb74..be19dd81 100644 --- a/packages/components/src/utils/shiki/snippet-presets.ts +++ b/packages/components/src/utils/shiki/snippet-presets.ts @@ -10,7 +10,7 @@ type SnippetPreset = { /** Shiki language for syntax highlighting */ shikiLanguage: string; /** httpsnippet config for code generation */ - httpSnippet: { + httpSnippet?: { target: string; client?: string; }; @@ -145,6 +145,11 @@ const SNIPPET_PRESETS: SnippetPreset[] = [ shikiLanguage: "dart", httpSnippet: { target: "dart" }, }, + { + key: "toml", + displayName: "TOML", + shikiLanguage: "yaml", + }, ]; const presetLookup = new Map();