diff --git a/.circleci/orbs/code-infra.yml b/.circleci/orbs/code-infra.yml index 7a9972903..b5212fa79 100644 --- a/.circleci/orbs/code-infra.yml +++ b/.circleci/orbs/code-infra.yml @@ -82,6 +82,9 @@ commands: eslint: description: 'Runs ESLint on the codebase' steps: + - run: + name: Build docs-infra for ESLint plugins + command: pnpm run docs:lib - restore_cache: keys: - eslint-cache-{{ checksum "pnpm-lock.yaml" }} diff --git a/docs/app/bench/docs-infra/components/code-highlighter/demos/CodeContent.tsx b/docs/app/bench/docs-infra/components/code-highlighter/demos/CodeContent.tsx index b6af1cfe3..b22272da9 100644 --- a/docs/app/bench/docs-infra/components/code-highlighter/demos/CodeContent.tsx +++ b/docs/app/bench/docs-infra/components/code-highlighter/demos/CodeContent.tsx @@ -10,7 +10,9 @@ import styles from './CodeContent.module.css'; import '../../../../../docs-infra/components/code-highlighter/demos/syntax.css'; export function CodeContent(props: ContentProps<{}>) { + // @focus-start @padding 1 const code = useCode(props, { preClassName: styles.codeBlock }); return
{code.selectedFile}
; + // @focus-end } diff --git a/docs/app/bench/docs-infra/components/code-highlighter/demos/CodeContentLoading.tsx b/docs/app/bench/docs-infra/components/code-highlighter/demos/CodeContentLoading.tsx index 1008f6ad1..3c62f1d2a 100644 --- a/docs/app/bench/docs-infra/components/code-highlighter/demos/CodeContentLoading.tsx +++ b/docs/app/bench/docs-infra/components/code-highlighter/demos/CodeContentLoading.tsx @@ -9,9 +9,11 @@ import '../../../../../docs-infra/components/code-highlighter/demos/syntax.css'; export function CodeContentLoading(props: ContentLoadingProps<{}>) { return (
+ {/* @focus-start */}
{props.source}
+ {/* @focus-end */}
); } diff --git a/docs/app/bench/docs-infra/components/code-highlighter/demos/code/page.tsx b/docs/app/bench/docs-infra/components/code-highlighter/demos/code/page.tsx index f61f0f8fc..94910edd0 100644 --- a/docs/app/bench/docs-infra/components/code-highlighter/demos/code/page.tsx +++ b/docs/app/bench/docs-infra/components/code-highlighter/demos/code/page.tsx @@ -11,6 +11,7 @@ const sourceParser = createParseSource(); export default function Page() { return ( + // @focus-start {code} + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeController.tsx b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeController.tsx index bb2436740..515ff7a3e 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeController.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeController.tsx @@ -5,6 +5,7 @@ import { CodeControllerContext } from '@mui/internal-docs-infra/CodeControllerCo import type { ControlledCode } from '@mui/internal-docs-infra/CodeHighlighter/types'; export function CodeController({ children }: { children: React.ReactNode }) { + // @focus-start @padding 1 const [code, setCode] = React.useState(undefined); const contextValue = React.useMemo(() => ({ code, setCode }), [code, setCode]); @@ -12,4 +13,5 @@ export function CodeController({ children }: { children: React.ReactNode }) { return ( {children} ); + // @focus-end } diff --git a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditor.tsx b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditor.tsx index d3dbee261..171e4a180 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditor.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditor.tsx @@ -20,6 +20,7 @@ function greet(name) { export function CodeEditor() { return ( + // @focus-start + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.module.css b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.module.css index 53de3604c..09e4337c9 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.module.css +++ b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.module.css @@ -27,15 +27,26 @@ display: flex; } .code { - padding: 6px; + padding: 6px 0; } .codeBlock { margin: 0; - padding: 6px; + padding: 6px 0; overflow-x: auto; } +/* Code element inside pre — block so frames stretch to the widest line */ +.codeBlock :global(code) { + display: block; + min-width: fit-content; +} + +.codeBlock :global(.frame) { + display: block; + padding: 0 12px; +} + .codeBlock:focus-visible { outline: none; } diff --git a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.tsx b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.tsx index d2a6fbf48..634f44db1 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/code-editor/CodeEditorContent.tsx @@ -10,6 +10,7 @@ import styles from './CodeEditorContent.module.css'; import '../../../code-highlighter/demos/syntax.css'; export function CodeEditorContent(props: ContentProps) { + // @focus-start @padding 1 const preRef = React.useRef(null); const code = useCode(props, { preClassName: styles.codeBlock, preRef }); @@ -47,4 +48,5 @@ export function CodeEditorContent(props: ContentProps) {
{code.selectedFile}
); + // @focus-end } diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoController.tsx b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoController.tsx index b42c1ca94..09d2cff26 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoController.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoController.tsx @@ -27,6 +27,7 @@ function Runner({ code }: { code: string }) { } export function DemoController({ children }: { children: React.ReactNode }) { + // @focus-start @padding 1 const [code, setCode] = React.useState(undefined); const components = React.useMemo( @@ -56,4 +57,5 @@ export function DemoController({ children }: { children: React.ReactNode }) { return ( {children} ); + // @focus-end } diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLive.tsx b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLive.tsx index e634b8568..bdeaa435b 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLive.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLive.tsx @@ -5,10 +5,12 @@ import { DemoCheckboxBasic } from './demo-basic'; export function DemoLive() { return ( + // @focus-start + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.module.css b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.module.css index 6507f1bed..47c2764bc 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.module.css +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.module.css @@ -62,15 +62,26 @@ } .code { - padding: 10px 6px; + padding: 10px 0; } .codeBlock { margin: 0; - padding: 6px; + padding: 6px 0; overflow-x: auto; } +/* Code element inside pre — block so frames stretch to the widest line */ +.codeBlock :global(code) { + display: block; + min-width: fit-content; +} + +.codeBlock :global(.frame) { + display: block; + padding: 0 12px; +} + .codeBlock:focus-visible { outline: none; } diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.tsx b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.tsx index 100cbd866..b6a55998f 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/DemoLiveContent.tsx @@ -16,6 +16,7 @@ const variantNames: Record = { }; export function DemoLiveContent(props: ContentProps) { + // @focus-start @padding 1 const preRef = React.useRef(null); const demo = useDemo(props, { preClassName: styles.codeBlock, preRef }); @@ -85,4 +86,5 @@ export function DemoLiveContent(props: ContentProps) { ); + // @focus-end } diff --git a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/demo-basic/CheckboxBasic.tsx b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/demo-basic/CheckboxBasic.tsx index 5f008e747..68a44a51b 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/demo-live/demo-basic/CheckboxBasic.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/demo-live/demo-basic/CheckboxBasic.tsx @@ -4,8 +4,10 @@ import { Checkbox } from '@/components/Checkbox'; export default function CheckboxBasic() { return (
+ {/* @focus-start */}

Type Whatever You Want Below

+ {/* @focus-end */}
); } diff --git a/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileContent.tsx b/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileContent.tsx index c0cf80e6a..6b8ebef9b 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileContent.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileContent.tsx @@ -10,6 +10,7 @@ import styles from '../code-editor/CodeEditorContent.module.css'; import '../../../code-highlighter/demos/syntax.css'; export function MultiFileContent(props: ContentProps) { + // @focus-start @padding 1 const preRef = React.useRef(null); const code = useCode(props, { preClassName: styles.codeBlock, preRef }); @@ -43,4 +44,5 @@ export function MultiFileContent(props: ContentProps) {
{code.selectedFile}
); + // @focus-end } diff --git a/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileEditor.tsx b/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileEditor.tsx index 8f5affceb..9552bc272 100644 --- a/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileEditor.tsx +++ b/docs/app/docs-infra/components/code-controller-context/demos/multi-file/MultiFileEditor.tsx @@ -43,6 +43,7 @@ p { export function MultiFileEditor() { return ( + // @focus-start + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/CodeBlock.tsx b/docs/app/docs-infra/components/code-highlighter/demos/CodeBlock.tsx index 29e0e1a90..051be9fe5 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/CodeBlock.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/CodeBlock.tsx @@ -19,6 +19,7 @@ export function Code({ fileName?: string; }) { return ( + // @focus-start {children} + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/CodeContent.module.css b/docs/app/docs-infra/components/code-highlighter/demos/CodeContent.module.css index 5c85bfde9..0023c8da9 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/CodeContent.module.css +++ b/docs/app/docs-infra/components/code-highlighter/demos/CodeContent.module.css @@ -56,15 +56,26 @@ } .code { - padding: 10px 6px; + padding: 10px 0; } .codeBlock { margin: 0; - padding: 6px; + padding: 6px 0; overflow-x: auto; } +/* Code element inside pre — block so frames stretch to the widest line */ +.codeBlock :global(code) { + display: block; + min-width: fit-content; +} + +.codeBlock :global(.frame) { + display: block; + padding: 0 12px; +} + .codeBlock :global(.frame[data-lined]) { display: block; white-space: normal; @@ -75,27 +86,55 @@ white-space: pre; } +/* Highlighted frames get rounded corners and background */ +.codeBlock :global(.frame[data-frame-type='highlighted']), +.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { + background: #ebe4ff; + border-radius: 8px; + margin: 0 6px; + padding: 0 6px; +} + +/* Line-level highlight inside a frame (nested emphasis) */ .codeBlock :global(.line[data-hl]) { background: #ebe4ff; margin: 0 -6px; padding: 0 6px; } -.codeBlock :global(:not(.line)[data-hl]) { - background: #e1d9ff; +.codeBlock :global(mark) { + background: #ebe4ff; border-radius: 4px; } +.codeBlock :global(mark[data-hl]) { + background: #e1d9ff; +} + +.codeBlock :global(mark[data-hl='strong']) { + background: #d4c8ff; +} + +.codeBlock :global(mark[data-hl-part='start']) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.codeBlock :global(mark[data-hl-part='middle']) { + border-radius: 0; +} + +.codeBlock :global(mark[data-hl-part='end']) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + .codeBlock :global(.line[data-hl='strong']) { background: #e1d9ff; margin: 0 -6px; padding: 0 6px; } -.codeBlock :global(.line[data-hl='strong'][data-hl-position='end']) { - position: relative; -} - .codeBlock :global(.line[data-hl-position='single']) { border-radius: 8px; } @@ -110,16 +149,6 @@ border-bottom-right-radius: 8px; } -.codeBlock :global(.line[data-hl]:has(+ .line[data-hl='strong'][data-hl-position='start'])) { - margin-bottom: -8px; - padding-bottom: 8px; -} - -.codeBlock :global(.line[data-hl='strong'][data-hl-position='end']) + :global(.line[data-hl]) { - margin-top: -8px; - padding-top: 8px; -} - .codeBlock :global(.line[data-hl-description])::after { content: attr(data-hl-description); float: right; @@ -129,3 +158,21 @@ padding: 2px 4px; margin-right: -6px; } + +.codeBlock :global(.frame[data-frame-description])::before { + content: attr(data-frame-description); + float: right; + background: #8145b5; + border-radius: 8px; + color: white; + padding: 2px 4px; +} + +/* Truncated frames: adjust rounding so visible/hidden halves connect */ +.codeBlock :global(.frame[data-frame-truncated='visible']) { + border-radius: 8px 8px 0 0; +} + +.codeBlock :global(.frame[data-frame-truncated='hidden']) { + border-radius: 0 0 8px 8px; +} diff --git a/docs/app/docs-infra/components/code-highlighter/demos/CodeContent.tsx b/docs/app/docs-infra/components/code-highlighter/demos/CodeContent.tsx index f15c7008c..559edf1a8 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/CodeContent.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/CodeContent.tsx @@ -16,6 +16,7 @@ const variantNames: Record = { }; export function CodeContent(props: ContentProps) { + // @focus-start const code = useCode(props, { preClassName: styles.codeBlock }); const hasJsTransform = code.availableTransforms.includes('js'); @@ -85,4 +86,5 @@ export function CodeContent(props: ContentProps) { ); + // @focus-end } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/DemoContent.module.css b/docs/app/docs-infra/components/code-highlighter/demos/DemoContent.module.css index 189488d57..4eea4dc41 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/DemoContent.module.css +++ b/docs/app/docs-infra/components/code-highlighter/demos/DemoContent.module.css @@ -63,15 +63,26 @@ } .code { - padding: 10px 6px; + padding: 10px 0; } .codeBlock { margin: 0; - padding: 6px; + padding: 6px 0; overflow-x: auto; } +/* Code element inside pre — block so frames stretch to the widest line */ +.codeBlock :global(code) { + display: block; + min-width: fit-content; +} + +.codeBlock :global(.frame) { + display: block; + padding: 0 12px; +} + .codeBlock :global(.frame[data-lined]) { display: block; white-space: normal; @@ -82,27 +93,41 @@ white-space: pre; } +/* Highlighted frames get rounded corners and background */ +.codeBlock :global(.frame[data-frame-type='highlighted']), +.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { + background: #ebe4ff; + border-radius: 8px; + margin: 0 6px; + padding: 0 6px; +} + +/* Line-level highlight inside a frame (nested emphasis) */ .codeBlock :global(.line[data-hl]) { background: #ebe4ff; margin: 0 -6px; padding: 0 6px; } -.codeBlock :global(:not(.line)[data-hl]) { - background: #e1d9ff; +.codeBlock :global(mark) { + background: #ebe4ff; border-radius: 4px; } +.codeBlock :global(mark[data-hl]) { + background: #e1d9ff; +} + +.codeBlock :global(mark[data-hl='strong']) { + background: #d4c8ff; +} + .codeBlock :global(.line[data-hl='strong']) { background: #e1d9ff; margin: 0 -6px; padding: 0 6px; } -.codeBlock :global(.line[data-hl='strong'][data-hl-position='end']) { - position: relative; -} - .codeBlock :global(.line[data-hl-position='single']) { border-radius: 8px; } @@ -117,16 +142,6 @@ border-bottom-right-radius: 8px; } -.codeBlock :global(.line[data-hl]:has(+ .line[data-hl='strong'][data-hl-position='start'])) { - margin-bottom: -8px; - padding-bottom: 8px; -} - -.codeBlock :global(.line[data-hl='strong'][data-hl-position='end']) + :global(.line[data-hl]) { - margin-top: -8px; - padding-top: 8px; -} - .codeBlock :global(.line[data-hl-description])::after { content: attr(data-hl-description); float: right; @@ -136,3 +151,12 @@ padding: 2px 4px; margin-right: -6px; } + +.codeBlock :global(.frame[data-frame-description])::before { + content: attr(data-frame-description); + float: right; + background: #8145b5; + border-radius: 8px; + color: white; + padding: 2px 4px; +} diff --git a/docs/app/docs-infra/components/code-highlighter/demos/DemoContent.tsx b/docs/app/docs-infra/components/code-highlighter/demos/DemoContent.tsx index 08d6f08ed..bb74f4cc3 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/DemoContent.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/DemoContent.tsx @@ -16,6 +16,7 @@ const variantNames: Record = { }; export function DemoContent(props: ContentProps) { + // @focus-start const demo = useDemo(props, { preClassName: styles.codeBlock }); const hasJsTransform = demo.availableTransforms.includes('js'); @@ -82,4 +83,5 @@ export function DemoContent(props: ContentProps) { ); + // @focus-end } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/code-basic/BasicCode.tsx b/docs/app/docs-infra/components/code-highlighter/demos/code-basic/BasicCode.tsx index 093206112..bf4d23d95 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/code-basic/BasicCode.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/code-basic/BasicCode.tsx @@ -2,5 +2,8 @@ import * as React from 'react'; import { Code } from '../CodeBlock'; export function BasicCode() { - return {`console.log('Hello, world!');`}; + return ( + // @focus + {`console.log('Hello, world!');`} + ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-idle/BasicCode.tsx b/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-idle/BasicCode.tsx index a75d04ead..e54933609 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-idle/BasicCode.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-idle/BasicCode.tsx @@ -2,5 +2,8 @@ import * as React from 'react'; import { Code } from './Code'; export function BasicCode() { - return {`console.log('Hello, world!');`}; + return ( + // @focus + {`console.log('Hello, world!');`} + ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-idle/Code.tsx b/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-idle/Code.tsx index 72b75b3ce..e3fa7f597 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-idle/Code.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-idle/Code.tsx @@ -10,6 +10,7 @@ const sourceParser = createParseSource(); export function Code({ children, fileName }: { children: string; fileName?: string }) { return ( + // @focus-start {children} + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-init/BasicCode.tsx b/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-init/BasicCode.tsx index a75d04ead..e54933609 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-init/BasicCode.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-init/BasicCode.tsx @@ -2,5 +2,8 @@ import * as React from 'react'; import { Code } from './Code'; export function BasicCode() { - return {`console.log('Hello, world!');`}; + return ( + // @focus + {`console.log('Hello, world!');`} + ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-init/Code.tsx b/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-init/Code.tsx index 72b75b3ce..e3fa7f597 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-init/Code.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/code-highlight-init/Code.tsx @@ -10,6 +10,7 @@ const sourceParser = createParseSource(); export function Code({ children, fileName }: { children: string; fileName?: string }) { return ( + // @focus-start {children} + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/code-transformed/BasicCode.tsx b/docs/app/docs-infra/components/code-highlighter/demos/code-transformed/BasicCode.tsx index 5fc1eefda..dc2573e5d 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/code-transformed/BasicCode.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/code-transformed/BasicCode.tsx @@ -3,6 +3,7 @@ import { Code } from './Code'; export function BasicCode() { return ( + // @focus {`const x: number = 1;\ninterface Props { name: string; }`} ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/code-transformed/Code.tsx b/docs/app/docs-infra/components/code-highlighter/demos/code-transformed/Code.tsx index da1ff3de1..3b519dbdd 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/code-transformed/Code.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/code-transformed/Code.tsx @@ -10,6 +10,7 @@ const sourceTransformers = [TypescriptToJavascriptTransformer]; export function Code({ children, fileName }: { children: string; fileName?: string }) { return ( + // @focus-start {children} + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-files/DemoContentLoading.tsx b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-files/DemoContentLoading.tsx index 34f410482..e6e9f12a0 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-files/DemoContentLoading.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-files/DemoContentLoading.tsx @@ -9,6 +9,7 @@ import loadingStyles from './DemoContentLoading.module.css'; import '../syntax.css'; export function DemoContentLoading(props: ContentLoadingProps) { + // @focus-start const tabs = React.useMemo( () => props.fileNames?.map((name) => ({ @@ -57,4 +58,5 @@ export function DemoContentLoading(props: ContentLoadingProps) { ); + // @focus-end } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-files/demo-red/CheckboxRed.tsx b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-files/demo-red/CheckboxRed.tsx index adc5f88e6..86db42ccc 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-files/demo-red/CheckboxRed.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-files/demo-red/CheckboxRed.tsx @@ -3,5 +3,8 @@ import { Checkbox } from '@/components/Checkbox'; import styles from './CheckboxRed.module.css'; export function CheckboxRed() { - return ; + return ( + // @focus + + ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/DemoContentLoading.tsx b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/DemoContentLoading.tsx index b01246aa3..42212fdf2 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/DemoContentLoading.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/DemoContentLoading.tsx @@ -14,6 +14,7 @@ const variantNames: Record = { }; export function DemoContentLoading(props: ContentLoadingProps) { + // @focus-start const tabs = React.useMemo( () => props.fileNames?.map((name) => ({ @@ -89,4 +90,5 @@ export function DemoContentLoading(props: ContentLoadingProps) { ); + // @focus-end } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/demo-red/css-modules/CheckboxRed.tsx b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/demo-red/css-modules/CheckboxRed.tsx index 1df8d8c02..c38da79d1 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/demo-red/css-modules/CheckboxRed.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/demo-red/css-modules/CheckboxRed.tsx @@ -4,5 +4,8 @@ import { Checkbox } from '@/components/Checkbox'; import styles from './CheckboxRed.module.css'; export function CheckboxRed() { - return ; + return ( + // @focus + + ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/demo-red/tailwind/CheckboxRed.tsx b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/demo-red/tailwind/CheckboxRed.tsx index 6ae30479a..bfe6e82c4 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/demo-red/tailwind/CheckboxRed.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback-all-variants/demo-red/tailwind/CheckboxRed.tsx @@ -2,5 +2,8 @@ import * as React from 'react'; import { Checkbox } from '@/components/Checkbox'; export function CheckboxRed() { - return ; + return ( + // @focus + + ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback/DemoContentLoading.tsx b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback/DemoContentLoading.tsx index 301bb14fb..8babec8af 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback/DemoContentLoading.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback/DemoContentLoading.tsx @@ -8,6 +8,7 @@ import styles from '../DemoContent.module.css'; import '../syntax.css'; export function DemoContentLoading(props: ContentLoadingProps) { + // @focus-start const tabs = React.useMemo( () => props.fileNames?.map((name) => ({ @@ -51,4 +52,5 @@ export function DemoContentLoading(props: ContentLoadingProps) { ); + // @focus-end } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback/demo-red/CheckboxRed.tsx b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback/demo-red/CheckboxRed.tsx index adc5f88e6..86db42ccc 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback/demo-red/CheckboxRed.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/demo-fallback/demo-red/CheckboxRed.tsx @@ -3,5 +3,8 @@ import { Checkbox } from '@/components/Checkbox'; import styles from './CheckboxRed.module.css'; export function CheckboxRed() { - return ; + return ( + // @focus + + ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/demo-server-loaded/demo-basic/CheckboxBasic.tsx b/docs/app/docs-infra/components/code-highlighter/demos/demo-server-loaded/demo-basic/CheckboxBasic.tsx index adcf3533a..30e1d8fa8 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/demo-server-loaded/demo-basic/CheckboxBasic.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/demo-server-loaded/demo-basic/CheckboxBasic.tsx @@ -2,5 +2,8 @@ import * as React from 'react'; import { Checkbox } from '@/components/Checkbox'; export function CheckboxBasic() { - return ; + return ( + // @focus + + ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/demo-variants/demo-default/css-modules/CheckboxRed.tsx b/docs/app/docs-infra/components/code-highlighter/demos/demo-variants/demo-default/css-modules/CheckboxRed.tsx index 1df8d8c02..c38da79d1 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/demo-variants/demo-default/css-modules/CheckboxRed.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/demo-variants/demo-default/css-modules/CheckboxRed.tsx @@ -4,5 +4,8 @@ import { Checkbox } from '@/components/Checkbox'; import styles from './CheckboxRed.module.css'; export function CheckboxRed() { - return ; + return ( + // @focus + + ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/demo-variants/demo-default/tailwind/CheckboxRed.tsx b/docs/app/docs-infra/components/code-highlighter/demos/demo-variants/demo-default/tailwind/CheckboxRed.tsx index f1c788638..6804e9994 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/demo-variants/demo-default/tailwind/CheckboxRed.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/demo-variants/demo-default/tailwind/CheckboxRed.tsx @@ -2,5 +2,8 @@ import * as React from 'react'; import { Checkbox } from '@/components/Checkbox'; export function CheckboxRed() { - return ; + return ( + // @focus + + ); } diff --git a/docs/app/docs-infra/components/code-highlighter/demos/demo/demo-basic/CheckboxBasic.tsx b/docs/app/docs-infra/components/code-highlighter/demos/demo/demo-basic/CheckboxBasic.tsx index adcf3533a..30e1d8fa8 100644 --- a/docs/app/docs-infra/components/code-highlighter/demos/demo/demo-basic/CheckboxBasic.tsx +++ b/docs/app/docs-infra/components/code-highlighter/demos/demo/demo-basic/CheckboxBasic.tsx @@ -2,5 +2,8 @@ import * as React from 'react'; import { Checkbox } from '@/components/Checkbox'; export function CheckboxBasic() { - return ; + return ( + // @focus + + ); } diff --git a/docs/app/docs-infra/components/code-highlighter/types.md b/docs/app/docs-infra/components/code-highlighter/types.md index 05c3cc2bf..5e9e550d1 100644 --- a/docs/app/docs-infra/components/code-highlighter/types.md +++ b/docs/app/docs-infra/components/code-highlighter/types.md @@ -610,7 +610,9 @@ type Externals = { [key: string]: ExternalImportItem[] }; ### HastRoot ```typescript -type HastRoot = { data?: RootData & { totalLines?: number } }; +type HastRoot = { + data?: RootData & { totalLines?: number; collapsible?: boolean; frameSize?: number }; +}; ``` ### LoadFallbackCodeOptions diff --git a/docs/app/docs-infra/components/code-provider/demos/Code.tsx b/docs/app/docs-infra/components/code-provider/demos/Code.tsx index ed07a0a6c..61b1b6db3 100644 --- a/docs/app/docs-infra/components/code-provider/demos/Code.tsx +++ b/docs/app/docs-infra/components/code-provider/demos/Code.tsx @@ -5,8 +5,10 @@ import { CodeContent } from '../../code-highlighter/demos/CodeContent'; export function Code({ children, fileName }: { children: string; fileName?: string }) { return ( + // @focus-start {children} + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-provider/demos/base/BasicCode.tsx b/docs/app/docs-infra/components/code-provider/demos/base/BasicCode.tsx index 9f15cdb0e..6d4e98758 100644 --- a/docs/app/docs-infra/components/code-provider/demos/base/BasicCode.tsx +++ b/docs/app/docs-infra/components/code-provider/demos/base/BasicCode.tsx @@ -4,8 +4,10 @@ import { Code } from '../../../code-highlighter/demos/CodeBlock'; export function BasicCode() { return ( + // @focus-start {`console.log('Hello, world!');`} + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-provider/demos/fetch-demo/CodeProviderGitHub.tsx b/docs/app/docs-infra/components/code-provider/demos/fetch-demo/CodeProviderGitHub.tsx index 456030894..401c1fc46 100644 --- a/docs/app/docs-infra/components/code-provider/demos/fetch-demo/CodeProviderGitHub.tsx +++ b/docs/app/docs-infra/components/code-provider/demos/fetch-demo/CodeProviderGitHub.tsx @@ -90,6 +90,7 @@ const loadSource: LoadSource = async (url: string) => { export function CodeProviderGitHub({ children }: { children: React.ReactNode }) { return ( + // @focus-start {children} + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-provider/demos/fetch-demo/Docs.tsx b/docs/app/docs-infra/components/code-provider/demos/fetch-demo/Docs.tsx index 813c0aa53..e22a98b84 100644 --- a/docs/app/docs-infra/components/code-provider/demos/fetch-demo/Docs.tsx +++ b/docs/app/docs-infra/components/code-provider/demos/fetch-demo/Docs.tsx @@ -4,8 +4,10 @@ import { DemoCheckboxBasic } from './demo-basic'; export function Docs() { return ( + // @focus-start + // @focus-end ); } diff --git a/docs/app/docs-infra/components/code-provider/demos/fetch-demo/demo-basic/CheckboxBasic.tsx b/docs/app/docs-infra/components/code-provider/demos/fetch-demo/demo-basic/CheckboxBasic.tsx index adcf3533a..30e1d8fa8 100644 --- a/docs/app/docs-infra/components/code-provider/demos/fetch-demo/demo-basic/CheckboxBasic.tsx +++ b/docs/app/docs-infra/components/code-provider/demos/fetch-demo/demo-basic/CheckboxBasic.tsx @@ -2,5 +2,8 @@ import * as React from 'react'; import { Checkbox } from '@/components/Checkbox'; export function CheckboxBasic() { - return ; + return ( + // @focus + + ); } diff --git a/docs/app/docs-infra/hooks/use-copier/demos/text-input-copy/TextInputCopy.tsx b/docs/app/docs-infra/hooks/use-copier/demos/text-input-copy/TextInputCopy.tsx index a9e2719bc..909ad1db3 100644 --- a/docs/app/docs-infra/hooks/use-copier/demos/text-input-copy/TextInputCopy.tsx +++ b/docs/app/docs-infra/hooks/use-copier/demos/text-input-copy/TextInputCopy.tsx @@ -4,6 +4,7 @@ import { useCopier } from '@mui/internal-docs-infra/useCopier'; import styles from './TextInputCopy.module.css'; export function TextInputCopy() { + // @focus-start @padding 1 const [text, setText] = React.useState('Hello, copy me!'); const { copy, recentlySuccessful } = useCopier(() => text); @@ -21,4 +22,5 @@ export function TextInputCopy() { ); + // @focus-end } diff --git a/docs/app/docs-infra/hooks/use-preference/demos/variant-selector/VariantSelector.tsx b/docs/app/docs-infra/hooks/use-preference/demos/variant-selector/VariantSelector.tsx index 448c6e43c..3f8721397 100644 --- a/docs/app/docs-infra/hooks/use-preference/demos/variant-selector/VariantSelector.tsx +++ b/docs/app/docs-infra/hooks/use-preference/demos/variant-selector/VariantSelector.tsx @@ -4,6 +4,7 @@ import { usePreference } from '@mui/internal-docs-infra/usePreference'; import styles from './VariantSelector.module.css'; export function VariantSelector() { + // @focus-start @padding 1 const variants = ['contained', 'outlined', 'text']; const [variant, setVariant] = usePreference('variant', variants, () => 'contained'); @@ -30,4 +31,5 @@ export function VariantSelector() { ); + // @focus-end } diff --git a/docs/app/docs-infra/hooks/use-types/demos/TypesTable.tsx b/docs/app/docs-infra/hooks/use-types/demos/TypesTable.tsx index 3768c8299..09014b4e9 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/TypesTable.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/TypesTable.tsx @@ -19,6 +19,7 @@ import styles from './TypesTable.module.css'; export type TypesTableProps = BaseTypesTableProps<{}>; export function TypesTable(props: TypesTableProps) { + // @focus-start @padding 1 // Get the main type and additional types for this export const { type, additionalTypes } = useTypes(props); @@ -37,6 +38,7 @@ export function TypesTable(props: TypesTableProps) { ))} ); + // @focus-end } function TypeMetaDoc(props: { typeMeta: EnhancedTypesMeta }) { diff --git a/docs/app/docs-infra/hooks/use-types/demos/basic/Component.tsx b/docs/app/docs-infra/hooks/use-types/demos/basic/Component.tsx index 5fba658f6..af6da57d9 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/basic/Component.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/basic/Component.tsx @@ -13,6 +13,7 @@ interface Props { * A simple component that displays a title and optional children. */ export function Component(props: Props) { + // @focus-start @padding 1 const handleClick = (event: React.MouseEvent) => { console.warn('Clicked', event); }; @@ -23,4 +24,5 @@ export function Component(props: Props) { {!props.disabled ? props.children : null} ); + // @focus-end } diff --git a/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/Component/Part/ComponentPart.tsx b/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/Component/Part/ComponentPart.tsx index 4c9842d22..77031a99d 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/Component/Part/ComponentPart.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/Component/Part/ComponentPart.tsx @@ -22,6 +22,7 @@ export interface ComponentPartProps { * A simple component that displays a title and optional children. */ export function ComponentPart(props: ComponentPartProps) { + // @focus-start @padding 1 const handleClick = (event: React.MouseEvent) => { console.warn('Clicked', event, props.input); }; @@ -32,6 +33,7 @@ export function ComponentPart(props: ComponentPartProps) { {!props.disabled ? props.children : null} ); + // @focus-end } // eslint-disable-next-line @typescript-eslint/no-namespace -- Using namespace for type grouping as per Base UI convention diff --git a/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/Component/Root/ComponentRoot.tsx b/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/Component/Root/ComponentRoot.tsx index 6b62cb924..47974c02c 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/Component/Root/ComponentRoot.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/Component/Root/ComponentRoot.tsx @@ -38,6 +38,7 @@ export interface ComponentRootProps { * A simple component that displays a title and optional children. */ export function ComponentRoot(props: ComponentRootProps) { + // @focus-start @padding 1 const handleClick = (event: React.MouseEvent) => { console.warn('Clicked', event); @@ -59,6 +60,7 @@ export function ComponentRoot(props: ComponentRootProps) { {!props.disabled ? props.children : null} ); + // @focus-end } // eslint-disable-next-line @typescript-eslint/no-namespace -- Using namespace for type grouping as per Base UI convention diff --git a/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/TypesComponent.tsx b/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/TypesComponent.tsx index 3aa3e79b9..a174bc693 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/TypesComponent.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/blocks-data-attr/TypesComponent.tsx @@ -4,6 +4,7 @@ import { TypesComponent as ComponentTypes, TypesComponentAdditional } from './ty export function TypesComponent() { return (
+ {/* @focus-start */}

Component API

Root

@@ -11,6 +12,7 @@ export function TypesComponent() {

Additional Types

+ {/* @focus-end */}
); } diff --git a/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/AlertDialog/AlertDialogTrigger.tsx b/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/AlertDialog/AlertDialogTrigger.tsx index 049f9c178..366ead7e3 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/AlertDialog/AlertDialogTrigger.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/AlertDialog/AlertDialogTrigger.tsx @@ -33,7 +33,10 @@ export const AlertDialogTrigger = React.forwardRef(function AlertDialogTrigger( props: AlertDialogTriggerProps, ref: React.ForwardedRef, ) { - return ; + return ( + // @focus + + ); }); // eslint-disable-next-line @typescript-eslint/no-namespace, import/export diff --git a/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/Dialog/DialogClose.tsx b/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/Dialog/DialogClose.tsx index 9a2544ad4..80eaa7321 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/Dialog/DialogClose.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/Dialog/DialogClose.tsx @@ -22,6 +22,7 @@ export const DialogClose = React.forwardRef(function DialogClose( props: DialogCloseProps, ref: React.ForwardedRef, ) { + // @focus-start @padding 1 const { render, children, ...other } = props; // In a real implementation, triggerState would come from context @@ -32,6 +33,7 @@ export const DialogClose = React.forwardRef(function DialogClose( {render ? render(triggerState) : children} ); + // @focus-end }); // eslint-disable-next-line @typescript-eslint/no-namespace, import/export diff --git a/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/Dialog/DialogTrigger.tsx b/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/Dialog/DialogTrigger.tsx index a52b109af..da5e659c6 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/Dialog/DialogTrigger.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/Dialog/DialogTrigger.tsx @@ -36,6 +36,7 @@ export const DialogTrigger = React.forwardRef(function DialogTrigger( props: DialogTriggerProps, ref: React.ForwardedRef, ) { + // @focus-start @padding 1 const { className, disabled, children, ...other } = props; const state: DialogTriggerState = React.useMemo( @@ -53,6 +54,7 @@ export const DialogTrigger = React.forwardRef(function DialogTrigger( {children} ); + // @focus-end }); // eslint-disable-next-line @typescript-eslint/no-namespace, import/export diff --git a/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/TypesAlertDialog.tsx b/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/TypesAlertDialog.tsx index fa7299d73..8bf4d14e0 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/TypesAlertDialog.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/blocks-inherited/TypesAlertDialog.tsx @@ -4,11 +4,13 @@ import { TypesAlertDialog as AlertDialogTypes } from './types'; export function TypesAlertDialog() { return (
+ {/* @focus-start */}

Alert Dialog API

Trigger

Close

+ {/* @focus-end */}
); } diff --git a/docs/app/docs-infra/hooks/use-types/demos/blocks/Component/ComponentPart.tsx b/docs/app/docs-infra/hooks/use-types/demos/blocks/Component/ComponentPart.tsx index 64e9069b6..6c9ed948e 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/blocks/Component/ComponentPart.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/blocks/Component/ComponentPart.tsx @@ -13,6 +13,7 @@ interface Props { * A simple component that displays a title and optional children. */ export function ComponentPart(props: Props) { + // @focus-start @padding 1 const handleClick = (event: React.MouseEvent) => { console.warn('Clicked', event); }; @@ -23,4 +24,5 @@ export function ComponentPart(props: Props) { {!props.disabled ? props.children : null} ); + // @focus-end } diff --git a/docs/app/docs-infra/hooks/use-types/demos/blocks/Component/ComponentRoot.tsx b/docs/app/docs-infra/hooks/use-types/demos/blocks/Component/ComponentRoot.tsx index 2c6d2cc0d..b4fc98c8d 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/blocks/Component/ComponentRoot.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/blocks/Component/ComponentRoot.tsx @@ -13,6 +13,7 @@ interface Props { * A simple component that displays a title and optional children. */ export function ComponentRoot(props: Props) { + // @focus-start @padding 1 const handleClick = (event: React.MouseEvent) => { console.warn('Clicked', event); }; @@ -23,4 +24,5 @@ export function ComponentRoot(props: Props) { {!props.disabled ? props.children : null} ); + // @focus-end } diff --git a/docs/app/docs-infra/hooks/use-types/demos/blocks/TypesComponent.tsx b/docs/app/docs-infra/hooks/use-types/demos/blocks/TypesComponent.tsx index 6793b2638..f4e049c8a 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/blocks/TypesComponent.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/blocks/TypesComponent.tsx @@ -4,10 +4,12 @@ import { TypesComponent as ComponentTypes } from './types'; export function TypesComponent() { return (
+ {/* @focus-start */}

ComponentRoot

ComponentPart

+ {/* @focus-end */}
); } diff --git a/docs/app/docs-infra/hooks/use-types/demos/data-attr/Component.tsx b/docs/app/docs-infra/hooks/use-types/demos/data-attr/Component.tsx index 43daa6cbd..00e6d3d46 100644 --- a/docs/app/docs-infra/hooks/use-types/demos/data-attr/Component.tsx +++ b/docs/app/docs-infra/hooks/use-types/demos/data-attr/Component.tsx @@ -14,6 +14,7 @@ interface Props { * A simple component that displays a title and optional children. */ export function Component(props: Props) { + // @focus-start @padding 1 const handleClick = (event: React.MouseEvent) => { console.warn('Clicked', event); }; @@ -24,4 +25,5 @@ export function Component(props: Props) { {!props.disabled ? props.children : null} ); + // @focus-end } diff --git a/docs/app/docs-infra/hooks/use-url-hash-state/demos/tab-navigation/TabNavigation.tsx b/docs/app/docs-infra/hooks/use-url-hash-state/demos/tab-navigation/TabNavigation.tsx index e3e6ef39b..995f880c3 100644 --- a/docs/app/docs-infra/hooks/use-url-hash-state/demos/tab-navigation/TabNavigation.tsx +++ b/docs/app/docs-infra/hooks/use-url-hash-state/demos/tab-navigation/TabNavigation.tsx @@ -14,6 +14,7 @@ const tabs = [ ]; export function TabNavigation() { + // @focus-start @padding 1 const [hash, setHash] = useUrlHashState(); const activeTab = hash || 'overview'; @@ -39,4 +40,5 @@ export function TabNavigation() { ); + // @focus-end } diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/Code.tsx b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/Code.tsx index 1adf091ab..19456e219 100644 --- a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/Code.tsx +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/Code.tsx @@ -20,11 +20,13 @@ const sourceEnhancers = [createEnhanceCodeEmphasis({ paddingFrameMaxSize: 3 })]; */ export function Code({ code }: { code: CodeType }) { return ( + // @focus-start + // @focus-end ); } diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CodeIndent.tsx b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CodeIndent.tsx index 6eed34a39..f262e1d4f 100644 --- a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CodeIndent.tsx +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CodeIndent.tsx @@ -20,11 +20,13 @@ const sourceEnhancers = [enhanceCodeEmphasis]; */ export function CodeIndent({ code }: { code: CodeType }) { return ( + // @focus-start + // @focus-end ); } diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CodeMaxSize.tsx b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CodeMaxSize.tsx new file mode 100644 index 000000000..c6a1cfa53 --- /dev/null +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CodeMaxSize.tsx @@ -0,0 +1,31 @@ +import 'server-only'; + +import * as React from 'react'; +import { CodeHighlighter } from '@mui/internal-docs-infra/CodeHighlighter'; +import type { Code as CodeType } from '@mui/internal-docs-infra/CodeHighlighter/types'; +import { createParseSource } from '@mui/internal-docs-infra/pipeline/parseSource'; +import { createEnhanceCodeEmphasis } from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis'; + +import { CollapsibleContent } from './CollapsibleContent'; + +const sourceParser = createParseSource(); +const sourceEnhancers = [createEnhanceCodeEmphasis({ focusFramesMaxSize: 6 })]; + +/** + * A server component that renders a collapsible code block with focusFramesMaxSize. + * + * Uses `focusFramesMaxSize: 6` so highlighted regions longer than 6 lines + * are split into a focused window from the start with unfocused overflow below. + */ +export function CodeMaxSize({ code }: { code: CodeType }) { + return ( + // @focus-start + + // @focus-end + ); +} diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleContent.module.css b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleContent.module.css index eec6babd9..dce1cc600 100644 --- a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleContent.module.css +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleContent.module.css @@ -5,7 +5,7 @@ } .code { - padding: 10px 6px; + padding: 10px 0 0; } /* Visually hidden but accessible checkbox — provides toggle state without JS */ @@ -22,11 +22,12 @@ } .toggle { - display: block; + display: none; width: 100%; padding: 6px; border: none; border-top: 1px solid #d0cdd7; + border-radius: 0 0 7px 7px; background: #f5f3f8; color: #65636d; cursor: pointer; @@ -35,6 +36,11 @@ text-align: center; } +/* Only show the toggle when the code block has collapsible frames */ +.code:has([data-collapsible]) ~ .toggle { + display: block; +} + .toggle:hover { background: #eae7ef; } @@ -58,16 +64,105 @@ display: inline; } -/* Pre element */ -.codeBlock { +/* Pre element — `pre` qualifier needed to override layout's `.content pre { overflow-x: auto }` */ +pre.codeBlock { margin: 0; - padding: 6px; + --code-padding-bottom: 6px; + --scrollbar-gutter-size: 15px; + padding: var(--code-padding-bottom) 0; + overflow-x: hidden; +} + +/* Fade overlay at the bottom of truncated code blocks. + Uses ::after + transform so the animation is GPU-accelerated. + The pre's computed overflow clips the translated pseudo-element. */ +pre.codeBlock:has(:global(.frame[data-frame-truncated='visible'])) { + position: relative; + padding-bottom: 0; + overflow-y: clip; +} + +pre.codeBlock:has(:global(.frame[data-frame-truncated='visible']))::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 40px; + background: linear-gradient(to bottom, transparent, rgb(255 255 255 / 0.8)); + transition: transform 0.3s ease; + pointer-events: none; +} + +.container:has(.checkbox:checked) + .code + pre.codeBlock:has(:global(.frame[data-frame-truncated='visible'])) { + padding-bottom: var(--code-padding-bottom); +} + +.container:has(.checkbox:checked) + .code + pre.codeBlock:has(:global(.frame[data-frame-truncated='visible']))::after { + transform: translateY(100%); +} + +pre.codeBlock[data-scrollbar-gutter='collapse-from'] { + overflow-x: hidden; + transition: none; +} + +pre.codeBlock[data-scrollbar-gutter='collapse-from'] :global(code) { + margin-bottom: var(--scrollbar-gutter-size); + transition: none; +} + +pre.codeBlock[data-scrollbar-gutter='collapse-to'] { + overflow-x: hidden; +} + +pre.codeBlock[data-scrollbar-gutter='collapse-to'] :global(code) { + margin-bottom: 0; + transition: margin-bottom 0.3s ease; +} + +pre.codeBlock[data-scrollbar-gutter='expand-from'] { + overflow-x: hidden; + transition: none; +} + +pre.codeBlock[data-scrollbar-gutter='expand-from'] :global(code) { + margin-bottom: 0; + transition: none; +} + +pre.codeBlock[data-scrollbar-gutter='expand-to'] { + overflow-x: hidden; +} + +pre.codeBlock[data-scrollbar-gutter='expand-to'] :global(code) { + margin-bottom: var(--scrollbar-gutter-size); + transition: margin-bottom 0.3s ease; +} + +.container:has(.checkbox:checked) .code pre.codeBlock[data-scrollbar-gutter='expand-from'], +.container:has(.checkbox:checked) .code pre.codeBlock[data-scrollbar-gutter='expand-to'] { + overflow-x: hidden; +} + +.container:has(.checkbox:checked) .code pre.codeBlock { overflow-x: auto; } +/* Code element inside pre — block so frames stretch to the widest line */ +.codeBlock :global(code) { + display: block; + min-width: fit-content; +} + /* Frame can contain multiple lines */ .codeBlock :global(.frame) { display: block; + padding: 0 12px; } .codeBlock :global(.frame[data-lined]) { @@ -80,13 +175,33 @@ white-space: pre; } -/* Highlighted line styling */ +/* Highlighted frames get rounded corners and background */ +.codeBlock :global(.frame[data-frame-type='highlighted']), +.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { + background: #ebe4ff; + border-radius: 8px; + margin: 0 6px; + padding: 0 6px; +} + +/* Line-level highlight inside a frame (nested emphasis) */ .codeBlock :global(.line[data-hl]) { background: #ebe4ff; margin: 0 -6px; padding: 0 6px; } +.codeBlock :global(:not(.line)[data-hl]) { + background: #e1d9ff; + border-radius: 4px; +} + +.codeBlock :global(.line[data-hl='strong']) { + background: #e1d9ff; + margin: 0 -6px; + padding: 0 6px; +} + .codeBlock :global(.line[data-hl-position='single']) { border-radius: 8px; } @@ -101,17 +216,74 @@ border-bottom-right-radius: 8px; } +.codeBlock :global(.line[data-hl-description])::after { + content: attr(data-hl-description); + float: right; + background: #8145b5; + border-radius: 8px; + color: white; + padding: 2px 4px; + margin-right: -6px; +} + +.codeBlock :global(.frame[data-frame-description])::before { + content: attr(data-frame-description); + float: right; + background: #8145b5; + border-radius: 8px; + color: white; + padding: 2px 4px; +} + +/* When truncated visible (collapsed): only round top */ +.codeBlock :global(.frame[data-frame-truncated='visible']) { + border-radius: 8px 8px 0 0; +} + +/* Truncated hidden frame is the bottom of the region */ +.codeBlock :global(.frame[data-frame-truncated='hidden']) { + border-radius: 0 0 8px 8px; +} + /* ---- Collapsible frame behavior ---- */ -/* Normal frames and unfocused highlighted frames are hidden by default (collapsed) */ +/* Normal frames and unfocused highlighted/focus frames are hidden by default (collapsed). + `visibility: hidden` removes the frames from the a11y tree, focus order, and + prevents `contenteditable` caret placement while collapsed. `visibility` has + built-in transition semantics that keep the element visible during the + collapse animation and only flip to hidden at the end. */ .codeBlock :global(.frame:not([data-frame-type])), -.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { +.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']), +.codeBlock :global(.frame[data-frame-type='focus-unfocused']) { max-height: 0; overflow: hidden; + overflow-anchor: none; opacity: 0; + visibility: hidden; transition: - max-height 0.3s ease, - opacity 0.3s ease; + max-height 0.3s cubic-bezier(0.5, 0, 0, 1), + opacity 0.2s ease 0.1s, + visibility 0.3s; +} + +@supports (interpolate-size: allow-keywords) { + .codeBlock :global(.frame:not([data-frame-type])), + .codeBlock :global(.frame[data-frame-type='highlighted-unfocused']), + .codeBlock :global(.frame[data-frame-type='focus-unfocused']) { + interpolate-size: allow-keywords; + max-height: unset; + height: 0; + overflow: clip; + transition: + height 0.3s ease, + opacity 0.3s ease, + visibility 0.3s; + } +} + +/* Highlighted-unfocused keeps its background visible — only animate height */ +.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { + opacity: 1; } /* Padding frames appear dimmed when collapsed */ @@ -122,14 +294,48 @@ } /* When checkbox is checked (expanded), show all frames */ -.checkbox:checked ~ .code .codeBlock :global(.frame:not([data-frame-type])), -.checkbox:checked ~ .code .codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { - max-height: 500px; - overflow: visible; +.container:has(.checkbox:checked) .code .codeBlock :global(.frame:not([data-frame-type])), +.container:has(.checkbox:checked) + .code + .codeBlock + :global(.frame[data-frame-type='highlighted-unfocused']), +.container:has(.checkbox:checked) + .code + .codeBlock + :global(.frame[data-frame-type='focus-unfocused']) { + max-height: 2220px; opacity: 1; + visibility: visible; + transition: + max-height 1.5s cubic-bezier(0.25, 0.46, 0.45, 0.94), + opacity 0.15s ease, + visibility 0s; +} + +@supports (interpolate-size: allow-keywords) { + .container:has(.checkbox:checked) .code .codeBlock :global(.frame:not([data-frame-type])), + .container:has(.checkbox:checked) + .code + .codeBlock + :global(.frame[data-frame-type='highlighted-unfocused']), + .container:has(.checkbox:checked) + .code + .codeBlock + :global(.frame[data-frame-type='focus-unfocused']) { + max-height: unset; + height: auto; + overflow: clip; + transition: + height 0.3s ease, + opacity 0.3s ease, + visibility 0s; + } } -.checkbox:checked ~ .code .codeBlock :global(.frame[data-frame-type='padding-top']), -.checkbox:checked ~ .code .codeBlock :global(.frame[data-frame-type='padding-bottom']) { +.container:has(.checkbox:checked) .code .codeBlock :global(.frame[data-frame-type='padding-top']), +.container:has(.checkbox:checked) + .code + .codeBlock + :global(.frame[data-frame-type='padding-bottom']) { opacity: 1; } diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleContent.tsx b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleContent.tsx index 675eb68d7..463bbbcca 100644 --- a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleContent.tsx +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleContent.tsx @@ -4,23 +4,40 @@ import * as React from 'react'; import type { ContentProps } from '@mui/internal-docs-infra/CodeHighlighter/types'; import { useCode } from '@mui/internal-docs-infra/useCode'; import styles from './CollapsibleContent.module.css'; +import { useScrollAnchor } from './useScrollAnchor'; import '../../../components/code-highlighter/demos/syntax.css'; export function CollapsibleContent(props: ContentProps) { + // @focus-start @padding 1 const code = useCode(props, { preClassName: styles.codeBlock }); const id = React.useId(); const checkboxId = `${id}-expand`; + const { containerRef, toggleRef, anchorScroll } = useScrollAnchor(); + const blurPointerFocus = React.useCallback((event: React.FocusEvent) => { + if (!event.currentTarget.matches(':focus-visible')) { + event.currentTarget.blur(); + } + }, []); return ( -
- {/* Visually hidden checkbox provides no-JS toggle state via CSS :checked */} - +
{code.selectedFile}
-
); + // @focus-end } diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleDemoContent.module.css b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleDemoContent.module.css new file mode 100644 index 000000000..b80cac07d --- /dev/null +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleDemoContent.module.css @@ -0,0 +1,410 @@ +.container { + border: 1px solid #d0cdd7; + border-radius: 8px; +} + +.demoSection { + padding: 24px; + border-radius: 7px 7px 0 0; +} + +.codeSection { + border-top: 1px solid #d0cdd7; + overflow: hidden; +} + +.header { + border-bottom: 1px solid #d0cdd7; + height: 48px; + position: relative; +} + +.headerContainer { + position: absolute; + width: 100%; + display: flex; + justify-content: space-between; + gap: 8px; +} + +.headerContainer::before { + content: ''; + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 1px; + margin: -1px; + background-color: #d0cdd7; +} + +.headerActions { + display: flex; + align-items: center; + gap: 8px; + padding-right: 8px; + height: 48px; +} + +.tabContainer { + display: flex; + align-items: center; + gap: 8px; + margin-left: -1px; + padding-bottom: 2px; + overflow-x: auto; +} + +.switchContainer { + display: flex; +} + +.code { + padding: 10px 0 0; + overflow-anchor: none; +} + +/* Visually hidden but accessible checkbox — provides toggle state without JS */ +.checkbox { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + border: 0; +} + +.toggle { + display: none; + width: 100%; + padding: 6px; + border: none; + border-top: 1px solid #d0cdd7; + border-radius: 0 0 7px 7px; + background: #f5f3f8; + color: #65636d; + cursor: pointer; + font-size: 12px; + font-family: var(--font-geist-mono); + text-align: center; +} + +/* Only show the toggle when the code block has collapsible frames */ +.code:has([data-collapsible]) ~ .toggle { + display: block; +} + +.toggle:hover { + background: #eae7ef; +} + +/* Show focus ring on toggle when checkbox receives keyboard focus */ +.checkbox:focus-visible ~ .toggle { + outline: 2px solid #8b5cf6; + outline-offset: -2px; +} + +/* Toggle label text based on checkbox state */ +.collapseLabel { + display: none; +} + +.checkbox:checked ~ .toggle .expandLabel { + display: none; +} + +.checkbox:checked ~ .toggle .collapseLabel { + display: inline; +} + +/* Pre element — `pre` qualifier needed to override layout's `.content pre { overflow-x: auto }` */ +pre.codeBlock { + margin: 0; + --code-padding-bottom: 6px; + --scrollbar-gutter-size: 15px; + padding: var(--code-padding-bottom) 0; + overflow-x: hidden; +} + +/* Fade overlay at the bottom of truncated code blocks. + Uses ::after + transform so the animation is GPU-accelerated. + The pre's computed overflow (hidden/auto) clips the translated + pseudo-element without needing an explicit overflow: clip. */ +pre.codeBlock:has(:global(.frame[data-frame-truncated='visible'])) { + position: relative; + padding-bottom: 0; + overflow-y: clip; +} + +pre.codeBlock:has(:global(.frame[data-frame-truncated='visible']))::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 40px; + background: linear-gradient(to bottom, transparent, rgb(255 255 255 / 0.8)); + transition: transform 0.3s ease; + pointer-events: none; +} + +.codeSection:has(.checkbox:checked) + .code + pre.codeBlock:has(:global(.frame[data-frame-truncated='visible'])) { + padding-bottom: var(--code-padding-bottom); +} + +.codeSection:has(.checkbox:checked) + .code + pre.codeBlock:has(:global(.frame[data-frame-truncated='visible']))::after { + transform: translateY(100%); +} + +pre.codeBlock[data-scrollbar-gutter='collapse-from'] { + overflow-x: hidden; + transition: none; +} + +pre.codeBlock[data-scrollbar-gutter='collapse-from'] :global(code) { + margin-bottom: var(--scrollbar-gutter-size); + transition: none; +} + +pre.codeBlock[data-scrollbar-gutter='collapse-to'] { + overflow-x: hidden; +} + +pre.codeBlock[data-scrollbar-gutter='collapse-to'] :global(code) { + margin-bottom: 0; + transition: margin-bottom 0.3s ease; +} + +pre.codeBlock[data-scrollbar-gutter='expand-from'] { + overflow-x: hidden; + transition: none; +} + +pre.codeBlock[data-scrollbar-gutter='expand-from'] :global(code) { + margin-bottom: 0; + transition: none; +} + +pre.codeBlock[data-scrollbar-gutter='expand-to'] { + overflow-x: hidden; +} + +pre.codeBlock[data-scrollbar-gutter='expand-to'] :global(code) { + margin-bottom: var(--scrollbar-gutter-size); + transition: margin-bottom 0.3s ease; +} + +.codeSection:has(.checkbox:checked) .code pre.codeBlock[data-scrollbar-gutter='expand-from'], +.codeSection:has(.checkbox:checked) .code pre.codeBlock[data-scrollbar-gutter='expand-to'] { + overflow-x: hidden; +} + +.codeSection:has(.checkbox:checked) .code pre.codeBlock { + overflow-x: auto; +} + +/* Code element inside pre — block so frames stretch to the widest line */ +.codeBlock :global(code) { + display: block; + min-width: fit-content; +} + +/* Frame can contain multiple lines */ +.codeBlock :global(.frame) { + display: block; + padding: 0 12px; +} + +.codeBlock :global(.frame[data-lined]) { + display: block; + white-space: normal; +} + +.codeBlock :global(.frame[data-lined] .line) { + display: block; + white-space: pre; +} + +/* Highlighted frames get rounded corners and background */ +.codeBlock :global(.frame[data-frame-type='highlighted']), +.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { + background: #ebe4ff; + border-radius: 8px; + margin: 0 6px; + padding: 0 6px; +} + +/* Line-level highlight inside a frame (nested emphasis) */ +.codeBlock :global(.line[data-hl]) { + background: #ebe4ff; + margin: 0 -6px; + padding: 0 6px; +} + +.codeBlock :global(mark) { + background: #ebe4ff; + border-radius: 4px; +} + +.codeBlock :global(mark[data-hl]) { + background: #e1d9ff; +} + +.codeBlock :global(mark[data-hl='strong']) { + background: #d4c8ff; +} + +.codeBlock :global(.line[data-hl='strong']) { + background: #e1d9ff; + margin: 0 -6px; + padding: 0 6px; +} + +.codeBlock :global(.line[data-hl-position='single']) { + border-radius: 8px; +} + +.codeBlock :global(.line[data-hl-position='start']) { + border-top-left-radius: 8px; + border-top-right-radius: 8px; +} + +.codeBlock :global(.line[data-hl-position='end']) { + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; +} + +.codeBlock :global(.line[data-hl-description])::after { + content: attr(data-hl-description); + float: right; + background: #8145b5; + border-radius: 8px; + color: white; + padding: 2px 4px; + margin-right: -6px; +} + +.codeBlock :global(.frame[data-frame-description])::before { + content: attr(data-frame-description); + float: right; + background: #8145b5; + border-radius: 8px; + color: white; + padding: 2px 4px; +} + +/* When truncated visible (collapsed): only round top */ +.codeBlock :global(.frame[data-frame-truncated='visible']) { + border-radius: 8px 8px 0 0; +} + +/* Truncated hidden frame is the bottom of the region */ +.codeBlock :global(.frame[data-frame-truncated='hidden']) { + border-radius: 0 0 8px 8px; +} + +/* ---- Collapsible frame behavior ---- */ + +/* Normal frames and unfocused highlighted/focus frames are hidden by default (collapsed). + `visibility: hidden` removes the frames from the a11y tree, focus order, and + prevents `contenteditable` caret placement while collapsed. `visibility` has + built-in transition semantics that keep the element visible during the + collapse animation and only flip to hidden at the end. */ +.codeBlock :global(.frame:not([data-frame-type])), +.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']), +.codeBlock :global(.frame[data-frame-type='focus-unfocused']) { + max-height: 0; + overflow: hidden; + overflow-anchor: none; + opacity: 0; + visibility: hidden; + transition: + max-height 0.3s cubic-bezier(0.5, 0, 0, 1), + opacity 0.2s ease 0.1s, + visibility 0.3s; +} + +@supports (interpolate-size: allow-keywords) { + .codeBlock :global(.frame:not([data-frame-type])), + .codeBlock :global(.frame[data-frame-type='highlighted-unfocused']), + .codeBlock :global(.frame[data-frame-type='focus-unfocused']) { + interpolate-size: allow-keywords; + max-height: unset; + height: 0; + overflow: clip; + transition: + height 0.3s ease, + opacity 0.3s ease, + visibility 0.3s; + } +} + +/* Highlighted-unfocused keeps its background visible — only animate height */ +.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { + opacity: 1; +} + +/* Padding frames appear dimmed when collapsed */ +.codeBlock :global(.frame[data-frame-type='padding-top']), +.codeBlock :global(.frame[data-frame-type='padding-bottom']) { + opacity: 0.5; + transition: opacity 0.3s ease; +} + +/* When checkbox is checked (expanded), show all frames */ +.codeSection:has(.checkbox:checked) .code .codeBlock :global(.frame:not([data-frame-type])), +.codeSection:has(.checkbox:checked) + .code + .codeBlock + :global(.frame[data-frame-type='highlighted-unfocused']), +.codeSection:has(.checkbox:checked) + .code + .codeBlock + :global(.frame[data-frame-type='focus-unfocused']) { + max-height: 2220px; + opacity: 1; + visibility: visible; + transition: + max-height 1.5s cubic-bezier(0.25, 0.46, 0.45, 0.94), + opacity 0.15s ease, + visibility 0s; +} + +@supports (interpolate-size: allow-keywords) { + .codeSection:has(.checkbox:checked) .code .codeBlock :global(.frame:not([data-frame-type])), + .codeSection:has(.checkbox:checked) + .code + .codeBlock + :global(.frame[data-frame-type='highlighted-unfocused']), + .codeSection:has(.checkbox:checked) + .code + .codeBlock + :global(.frame[data-frame-type='focus-unfocused']) { + max-height: unset; + height: auto; + overflow: clip; + transition: + height 0.3s ease, + opacity 0.3s ease, + visibility 0s; + } +} + +.codeSection:has(.checkbox:checked) .code .codeBlock :global(.frame[data-frame-type='padding-top']), +.codeSection:has(.checkbox:checked) + .code + .codeBlock + :global(.frame[data-frame-type='padding-bottom']) { + opacity: 1; +} + +.fileRefs { + display: none; +} diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleDemoContent.tsx b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleDemoContent.tsx new file mode 100644 index 000000000..f01399117 --- /dev/null +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/CollapsibleDemoContent.tsx @@ -0,0 +1,111 @@ +'use client'; + +import * as React from 'react'; +import type { ContentProps } from '@mui/internal-docs-infra/CodeHighlighter/types'; +import { useDemo } from '@mui/internal-docs-infra/useDemo'; +import { LabeledSwitch } from '@/components/LabeledSwitch'; +import { Tabs } from '@/components/Tabs'; +import { CopyButton } from '@/components/CopyButton'; +import { Select } from '@/components/Select'; +import styles from './CollapsibleDemoContent.module.css'; +import { useScrollAnchor } from './useScrollAnchor'; + +import '@wooorm/starry-night/style/light'; + +const variantNames: Record = { + CssModules: 'CSS Modules', +}; + +export function CollapsibleDemoContent(props: ContentProps) { + // @focus-start @padding 1 + const demo = useDemo(props, { preClassName: styles.codeBlock }); + + const hasJsTransform = demo.availableTransforms.includes('js'); + const isJsSelected = demo.selectedTransform === 'js'; + + const labels = { false: 'TS', true: 'JS' }; + const toggleJs = React.useCallback( + (checked: boolean) => { + demo.selectTransform(checked ? 'js' : null); + }, + [demo], + ); + + const tabs = React.useMemo( + () => demo.files.map(({ name, slug }) => ({ id: name, name, slug })), + [demo.files], + ); + const variants = React.useMemo( + () => + demo.variants.map((variant) => ({ value: variant, label: variantNames[variant] || variant })), + [demo.variants], + ); + + const id = React.useId(); + const checkboxId = `${id}-expand`; + const { containerRef, toggleRef, anchorScroll } = useScrollAnchor(); + const blurPointerFocus = React.useCallback((event: React.FocusEvent) => { + if (!event.currentTarget.matches(':focus-visible')) { + event.currentTarget.blur(); + } + }, []); + + return ( +
+ {demo.allFilesSlugs.map(({ slug }) => ( + + ))} +
+
{demo.component}
+
+
+
+
+ +
+
+ + {demo.variants.length > 1 && ( + { + anchorScroll(event.target.checked ? 'expand' : 'collapse'); + }} + /> + +
+
+
+ ); + // @focus-end +} diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/IndentContent.module.css b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/IndentContent.module.css index 7d9b0efde..9995c7e52 100644 --- a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/IndentContent.module.css +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/IndentContent.module.css @@ -5,15 +5,16 @@ } .code { - padding: 10px 6px; + padding: 10px 0 0; } .toggle { - display: block; + display: none; width: 100%; padding: 6px; border: none; border-top: 1px solid #d0cdd7; + border-radius: 0 0 7px 7px; background: #f5f3f8; color: #65636d; cursor: pointer; @@ -21,20 +22,65 @@ font-family: var(--font-geist-mono); } +/* Only show the toggle when the code block has collapsible frames */ +.code:has([data-collapsible]) + .toggle { + display: block; +} + .toggle:hover { background: #eae7ef; } -/* Pre element */ -.codeBlock { +/* Pre element — `pre` qualifier needed to override layout's `.content pre { overflow-x: auto }` */ +pre.codeBlock { margin: 0; - padding: 6px; + padding: 6px 0; + overflow-x: hidden; +} + +/* Fade overlay at the bottom of truncated code blocks. + Uses ::after + transform so the animation is GPU-accelerated. + The pre's computed overflow clips the translated pseudo-element. */ +pre.codeBlock:has(:global(.frame[data-frame-truncated='visible'])) { + position: relative; + padding-bottom: 0; + overflow-y: clip; +} + +pre.codeBlock:has(:global(.frame[data-frame-truncated='visible']))::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 40px; + background: linear-gradient(to bottom, transparent, rgb(255 255 255 / 0.8)); + transition: transform 0.3s ease; + pointer-events: none; +} + +.expanded pre.codeBlock:has(:global(.frame[data-frame-truncated='visible'])) { + padding-bottom: 6px; +} + +.expanded pre.codeBlock:has(:global(.frame[data-frame-truncated='visible']))::after { + transform: translateY(100%); +} + +.expanded pre.codeBlock { overflow-x: auto; } +/* Code element inside pre — block so frames stretch to the widest line */ +.codeBlock :global(code) { + display: block; + min-width: fit-content; +} + /* Frame can contain multiple lines */ .codeBlock :global(.frame) { display: block; + padding: 0 12px; } .codeBlock :global(.frame[data-lined]) { @@ -47,13 +93,33 @@ white-space: pre; } -/* Highlighted line styling */ +/* Highlighted frames get rounded corners and background */ +.codeBlock :global(.frame[data-frame-type='highlighted']), +.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { + background: #ebe4ff; + border-radius: 8px; + margin: 0 6px; + padding: 0 6px; +} + +/* Line-level highlight inside a frame (nested emphasis) */ .codeBlock :global(.line[data-hl]) { background: #ebe4ff; margin: 0 -6px; padding: 0 6px; } +.codeBlock :global(:not(.line)[data-hl]) { + background: #e1d9ff; + border-radius: 4px; +} + +.codeBlock :global(.line[data-hl='strong']) { + background: #e1d9ff; + margin: 0 -6px; + padding: 0 6px; +} + .codeBlock :global(.line[data-hl-position='single']) { border-radius: 8px; } @@ -68,66 +134,154 @@ border-bottom-right-radius: 8px; } +.codeBlock :global(.line[data-hl-description])::after { + content: attr(data-hl-description); + float: right; + background: #8145b5; + border-radius: 8px; + color: white; + padding: 2px 4px; + margin-right: -6px; +} + +.codeBlock :global(.frame[data-frame-description])::before { + content: attr(data-frame-description); + float: right; + background: #8145b5; + border-radius: 8px; + color: white; + padding: 2px 4px; +} + +/* When truncated visible (collapsed): only round top — bottom fades out */ +/* When truncated visible (collapsed): only round top */ +.codeBlock :global(.frame[data-frame-truncated='visible']) { + border-radius: 8px 8px 0 0; +} + +/* Truncated hidden frame is the bottom of the region */ +.codeBlock :global(.frame[data-frame-truncated='hidden']) { + border-radius: 0 0 8px 8px; +} + /* ---- Collapsible frame behavior (no padding, just highlighted vs normal) ---- */ -/* Normal frames and unfocused highlighted frames are hidden by default */ +/* Normal frames and unfocused highlighted/focus frames are hidden by default. + `visibility: hidden` removes the frames from the a11y tree, focus order, and + prevents `contenteditable` caret placement while collapsed. `visibility` has + built-in transition semantics that keep the element visible during the + collapse animation and only flip to hidden at the end. */ .codeBlock :global(.frame:not([data-frame-type])), -.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { +.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']), +.codeBlock :global(.frame[data-frame-type='focus-unfocused']) { max-height: 0; overflow: hidden; + overflow-anchor: none; opacity: 0; + visibility: hidden; transition: - max-height 0.3s ease, - opacity 0.3s ease; + max-height 0.3s cubic-bezier(0.5, 0, 0, 1), + opacity 0.2s ease 0.1s, + visibility 0.3s; +} + +@supports (interpolate-size: allow-keywords) { + .codeBlock :global(.frame:not([data-frame-type])), + .codeBlock :global(.frame[data-frame-type='highlighted-unfocused']), + .codeBlock :global(.frame[data-frame-type='focus-unfocused']) { + interpolate-size: allow-keywords; + max-height: unset; + height: 0; + overflow: clip; + transition: + height 0.3s ease, + opacity 0.3s ease, + visibility 0.3s; + } +} + +/* Highlighted-unfocused keeps its background visible — only animate height */ +.codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { + opacity: 1; } /* When expanded, show all frames */ .expanded .codeBlock :global(.frame:not([data-frame-type])), -.expanded .codeBlock :global(.frame[data-frame-type='highlighted-unfocused']) { - max-height: 500px; - overflow: visible; +.expanded .codeBlock :global(.frame[data-frame-type='highlighted-unfocused']), +.expanded .codeBlock :global(.frame[data-frame-type='focus-unfocused']) { + max-height: 2220px; opacity: 1; + visibility: visible; + transition: + max-height 1.5s cubic-bezier(0.25, 0.46, 0.45, 0.94), + opacity 0.15s ease, + visibility 0s; +} + +@supports (interpolate-size: allow-keywords) { + .expanded .codeBlock :global(.frame:not([data-frame-type])), + .expanded .codeBlock :global(.frame[data-frame-type='highlighted-unfocused']), + .expanded .codeBlock :global(.frame[data-frame-type='focus-unfocused']) { + max-height: unset; + height: auto; + overflow: clip; + transition: + height 0.3s ease, + opacity 0.3s ease, + visibility 0s; + } } -/* ---- Indent shifting: move highlighted frame left by its indent level ---- */ +/* ---- Indent shifting: move highlighted/focus frame left by its indent level ---- */ +/* Uses transform instead of margin-left to avoid layout reflow during height transitions */ -.codeBlock :global(.frame[data-frame-type='highlighted']) { - transition: margin-left 0.3s ease; +.codeBlock :global(.frame[data-frame-type='highlighted']), +.codeBlock :global(.frame[data-frame-type='focus']) { + transition: transform 0.3s ease; } -.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='1']) { - margin-left: -2ch; +.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='1']), +.codeBlock :global(.frame[data-frame-type='focus'][data-frame-indent='1']) { + transform: translateX(-2ch); } -.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='2']) { - margin-left: -4ch; +.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='2']), +.codeBlock :global(.frame[data-frame-type='focus'][data-frame-indent='2']) { + transform: translateX(-4ch); } -.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='3']) { - margin-left: -6ch; +.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='3']), +.codeBlock :global(.frame[data-frame-type='focus'][data-frame-indent='3']) { + transform: translateX(-6ch); } -.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='4']) { - margin-left: -8ch; +.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='4']), +.codeBlock :global(.frame[data-frame-type='focus'][data-frame-indent='4']) { + transform: translateX(-8ch); } -.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='5']) { - margin-left: -10ch; +.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='5']), +.codeBlock :global(.frame[data-frame-type='focus'][data-frame-indent='5']) { + transform: translateX(-10ch); } -.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='6']) { - margin-left: -12ch; +.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='6']), +.codeBlock :global(.frame[data-frame-type='focus'][data-frame-indent='6']) { + transform: translateX(-12ch); } -.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='7']) { - margin-left: -14ch; +.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='7']), +.codeBlock :global(.frame[data-frame-type='focus'][data-frame-indent='7']) { + transform: translateX(-14ch); } -.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='8']) { - margin-left: -16ch; +.codeBlock :global(.frame[data-frame-type='highlighted'][data-frame-indent='8']), +.codeBlock :global(.frame[data-frame-type='focus'][data-frame-indent='8']) { + transform: translateX(-16ch); } /* Reset indent shift when expanded */ -.expanded .codeBlock :global(.frame[data-frame-type='highlighted']) { - margin-left: 0; +.expanded .codeBlock :global(.frame[data-frame-type='highlighted']), +.expanded .codeBlock :global(.frame[data-frame-type='focus']) { + transform: translateX(0); } diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/IndentContent.tsx b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/IndentContent.tsx index c352f96e8..dad52af7a 100644 --- a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/IndentContent.tsx +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/IndentContent.tsx @@ -4,19 +4,30 @@ import * as React from 'react'; import type { ContentProps } from '@mui/internal-docs-infra/CodeHighlighter/types'; import { useCode } from '@mui/internal-docs-infra/useCode'; import styles from './IndentContent.module.css'; +import { useScrollAnchor } from './useScrollAnchor'; import '../../../components/code-highlighter/demos/syntax.css'; export function IndentContent(props: ContentProps) { + // @focus-start @padding 1 const code = useCode(props, { preClassName: styles.codeBlock }); const [expanded, setExpanded] = React.useState(false); + const { containerRef, anchorScroll } = useScrollAnchor(); return ( -
+
{code.selectedFile}
-
); + // @focus-end } diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-code/CollapsibleCode.tsx b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-code/CollapsibleCode.tsx index 7d39817e7..fa4eea7c6 100644 --- a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-code/CollapsibleCode.tsx +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-code/CollapsibleCode.tsx @@ -42,6 +42,7 @@ export function UserProfile({ id }: { id: string }) { }`; export async function CollapsibleCode() { + // @focus-start @padding 1 const { code: strippedSource, comments } = await parseImportsAndComments(source, '/demo.tsx', { removeCommentsWithPrefix: [EMPHASIS_COMMENT_PREFIX], notableCommentsPrefix: [EMPHASIS_COMMENT_PREFIX], @@ -56,4 +57,5 @@ export async function CollapsibleCode() { }; return ; + // @focus-end } diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-demo/Counter.tsx b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-demo/Counter.tsx new file mode 100644 index 000000000..fe24f520b --- /dev/null +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-demo/Counter.tsx @@ -0,0 +1,15 @@ +'use client'; + +import * as React from 'react'; + +export function Counter() { + // @focus-start @padding 1 + const [count, setCount] = React.useState(0); + + return ( + + ); + // @focus-end +} diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-demo/createDemo.ts b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-demo/createDemo.ts new file mode 100644 index 000000000..14772c480 --- /dev/null +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-demo/createDemo.ts @@ -0,0 +1,19 @@ +import 'server-only'; + +import { + createDemoFactory, + createDemoWithVariantsFactory, +} from '@mui/internal-docs-infra/abstractCreateDemo'; + +import { CollapsibleDemoContent as DemoContent } from '../CollapsibleDemoContent'; +import { DemoTitle } from '../../../../components/code-highlighter/demos/DemoTitle'; + +export const createDemo = createDemoFactory({ + DemoContent, + DemoTitle, +}); + +export const createDemoWithVariants = createDemoWithVariantsFactory({ + DemoContent, + DemoTitle, +}); diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-demo/index.ts b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-demo/index.ts new file mode 100644 index 000000000..f27ff4182 --- /dev/null +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/collapsible-demo/index.ts @@ -0,0 +1,7 @@ +import { createDemo } from './createDemo'; +import { Counter } from './Counter'; + +export const DemoCollapsibleDemo = createDemo(import.meta.url, Counter, { + name: 'Collapsible Demo', + slug: 'collapsible-demo', +}); diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/focus-code/FocusCode.tsx b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/focus-code/FocusCode.tsx index 0852a0eef..425af2646 100644 --- a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/focus-code/FocusCode.tsx +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/focus-code/FocusCode.tsx @@ -1,7 +1,10 @@ import * as React from 'react'; import type { Code as CodeType } from '@mui/internal-docs-infra/CodeHighlighter/types'; import { parseImportsAndComments } from '@mui/internal-docs-infra/pipeline/loaderUtils'; -import { EMPHASIS_COMMENT_PREFIX } from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis'; +import { + EMPHASIS_COMMENT_PREFIX, + FOCUS_COMMENT_PREFIX, +} from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis'; import { Code } from '../Code'; const source = `import * as React from 'react'; @@ -13,11 +16,11 @@ const today = formatDate(new Date()); // @highlight export function Calendar() { const [events, setEvents] = React.useState([]); - // @highlight-start @focus + // @focus-start React.useEffect(() => { fetchEvents(today).then(setEvents); }, []); - // @highlight-end + // @focus-end return (
@@ -32,9 +35,10 @@ export function Calendar() { }`; export async function FocusCode() { + // @focus-start @padding 1 const { code: strippedSource, comments } = await parseImportsAndComments(source, '/demo.tsx', { - removeCommentsWithPrefix: [EMPHASIS_COMMENT_PREFIX], - notableCommentsPrefix: [EMPHASIS_COMMENT_PREFIX], + removeCommentsWithPrefix: [EMPHASIS_COMMENT_PREFIX, FOCUS_COMMENT_PREFIX], + notableCommentsPrefix: [EMPHASIS_COMMENT_PREFIX, FOCUS_COMMENT_PREFIX], }); const code: CodeType = { @@ -46,4 +50,5 @@ export async function FocusCode() { }; return ; + // @focus-end } diff --git a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/indent-code/IndentCode.tsx b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/indent-code/IndentCode.tsx index e6b27cfed..69a4b40f3 100644 --- a/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/indent-code/IndentCode.tsx +++ b/docs/app/docs-infra/pipeline/enhance-code-emphasis/demos/indent-code/IndentCode.tsx @@ -1,7 +1,10 @@ import * as React from 'react'; import type { Code as CodeType } from '@mui/internal-docs-infra/CodeHighlighter/types'; import { parseImportsAndComments } from '@mui/internal-docs-infra/pipeline/loaderUtils'; -import { EMPHASIS_COMMENT_PREFIX } from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis'; +import { + EMPHASIS_COMMENT_PREFIX, + FOCUS_COMMENT_PREFIX, +} from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis'; import { CodeIndent } from '../CodeIndent'; const source = `import * as React from 'react'; @@ -18,7 +21,7 @@ export function ScheduleView() {
- {/* @highlight-start @focus */} + {/* @focus-start */} - {/* @highlight-end */} + {/* @focus-end */}