From 182116582e035e55e6c2f52256bf84c3cdadd075 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Fri, 5 Dec 2025 13:46:51 +0100 Subject: [PATCH] docs(docs-infra): prefer/avoid code block styling fixes #65866 (cherry picked from commit 3411638f65e5b2e08753630b1960df272b619c25) --- .../docs-viewer/docs-viewer.component.ts | 2 + .../example-viewer.component.spec.ts | 1 + adev/shared-docs/interfaces/code-example.ts | 2 + .../extensions/docs-code/docs-code-block.mts | 3 + .../marked/extensions/docs-code/docs-code.mts | 3 + .../extensions/docs-code/format/index.mts | 15 ++++- adev/shared-docs/styles/docs/_code.scss | 65 ++++++++++++++++--- .../src/content/best-practices/style-guide.md | 19 +++--- adev/src/content/kitchen-sink.md | 23 +++++++ 9 files changed, 111 insertions(+), 22 deletions(-) diff --git a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts index 3b7306cdf1c..f6ae2d647c5 100644 --- a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts +++ b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts @@ -205,6 +205,7 @@ export class DocViewer { const preview = Boolean(placeholder.getAttribute('preview')); const hideCode = Boolean(placeholder.getAttribute('hideCode')); const title = placeholder.getAttribute('header') ?? undefined; + const style = placeholder.getAttribute('style') ?? undefined; const firstCodeSnippetTitle = snippets.length > 0 ? (snippets[0].title ?? snippets[0].name) : undefined; const exampleRef = this.viewContainer.createComponent(ExampleViewer); @@ -217,6 +218,7 @@ export class DocViewer { preview, hideCode, id: this.countOfExamples, + style, }); exampleRef.setInput('githubUrl', `${GITHUB_CONTENT_URL}/${snippets[0].name}`); diff --git a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.spec.ts b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.spec.ts index 069c73fa9c4..023314edf98 100644 --- a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.spec.ts +++ b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.spec.ts @@ -347,6 +347,7 @@ const getMetadata = (value: Partial = {}): ExampleMetadata => { preview: false, hideCode: false, ...value, + style: undefined, }; }; diff --git a/adev/shared-docs/interfaces/code-example.ts b/adev/shared-docs/interfaces/code-example.ts index 46fe274109c..d43580f8c20 100644 --- a/adev/shared-docs/interfaces/code-example.ts +++ b/adev/shared-docs/interfaces/code-example.ts @@ -42,4 +42,6 @@ export interface ExampleMetadata { preview: boolean; /** Whether to hide code example by default. */ hideCode: boolean; + /** Visual style for the code example */ + style?: 'prefer' | 'avoid'; } diff --git a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code-block.mts b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code-block.mts index ac6733ae42f..c202beda033 100644 --- a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code-block.mts +++ b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code-block.mts @@ -16,6 +16,7 @@ export interface DocsCodeBlock extends CodeToken { code: string; // Code language language: string | undefined; + style: 'prefer' | 'avoid' | undefined; } /** @@ -40,6 +41,7 @@ export const docsCodeBlockExtension = { const headerRule = /header\s*:\s*(['"`])([^'"`]+)\1/; // The 2nd capture matters here const highlightRule = /highlight\s*:\s*(.*)([^,])/; const hideCopyRule = /hideCopy/; + const preferRule = /(prefer|avoid)/; const token: DocsCodeBlock = { raw: match[0], @@ -49,6 +51,7 @@ export const docsCodeBlockExtension = { header: headerRule.exec(metadataStr)?.[2], highlight: highlightRule.exec(metadataStr)?.[1], hideCopy: hideCopyRule.test(metadataStr), + style: preferRule.exec(metadataStr)?.[1] as 'prefer' | 'avoid' | undefined, }; return token; } diff --git a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code.mts b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code.mts index efd7365ee06..4ee6a852099 100644 --- a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code.mts +++ b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code.mts @@ -32,6 +32,7 @@ const visibleLinesRule = /visibleLines="([^"]*)"/; const regionRule = /region="([^"]*)"/; const previewRule = /preview/; const hideCodeRule = /hideCode/; +const preferRule = /(prefer|avoid)/; export const docsCodeExtension = { name: 'docs-code', @@ -54,6 +55,7 @@ export const docsCodeExtension = { const preview = previewRule.exec(attr) ? true : false; const hideCode = hideCodeRule.exec(attr) ? true : false; const classes = classRule.exec(attr); + const style = preferRule.exec(attr); let code = match[2]?.trim() ?? ''; if (path && path[1]) { @@ -76,6 +78,7 @@ export const docsCodeExtension = { region: region?.[1], preview: preview, hideCode, + style: style?.[1] as 'prefer' | 'avoid' | undefined, classes: classes?.[1]?.split(' '), }; return token; diff --git a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/format/index.mts b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/format/index.mts index baac263c209..84cad10c9d3 100644 --- a/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/format/index.mts +++ b/adev/shared-docs/pipeline/shared/marked/extensions/docs-code/format/index.mts @@ -53,7 +53,7 @@ export function formatCode(token: CodeToken, context: RendererContext): string { highlightCode(context.highlighter, token); const containerEl = JSDOM.fragment(` -
+
${buildHeaderElement(token)} ${token.code}
@@ -95,7 +95,18 @@ export function processForApiLinks(fragment: Element, apiEntries: ApiEntries): v /** Build the header element if a header is provided in the token. */ function buildHeaderElement(token: CodeToken) { - return token.header ? `

${token.header}

` : ''; + let header = ''; + if (token.style) { + header += `${token.style === 'prefer' ? 'Prefer' : 'Avoid'}`; + } + + if (token.header) { + header += `

${token.header}

`; + } + + if (!header) return ''; + + return `
${header}
`; } function applyContainerAttributesAndClasses(el: Element, token: CodeToken) { diff --git a/adev/shared-docs/styles/docs/_code.scss b/adev/shared-docs/styles/docs/_code.scss index d373ca1307c..556318e4fed 100644 --- a/adev/shared-docs/styles/docs/_code.scss +++ b/adev/shared-docs/styles/docs/_code.scss @@ -166,6 +166,18 @@ $code-font-size: 0.875rem; border-width: 0; border-radius: 0; } + + &.docs-code-avoid { + --style-color: var(--hot-red); + } + + &.docs-code-prefer { + --style-color: var(--symbolic-green); + } + &.docs-code-avoid, + &.docs-code-prefer { + border-color: color-mix(in srgb, var(--style-color) 40%, var(--senary-contrast)); + } } .shiki { @@ -278,18 +290,51 @@ $code-font-size: 0.875rem; } .docs-code-header { + display: flex; padding: 0.75rem; - // docs header background - &::before { - content: ''; - position: absolute; - inset: 0; - width: 100%; - height: 100%; - background: var(--subtle-purple); - border-radius: 0.25rem 0.25rem 0 0; - transition: background 0.3s ease; + background: var(--subtle-purple); + border-radius: 0.25rem 0.25rem 0 0; + transition: background 0.3s ease; + + .docs-code-avoid &, + .docs-code-prefer & { + background: color-mix(in srgb, var(--style-color) 17%, var(--page-background)); + color: color-mix(in srgb, var(--style-color) 60%, var(--primary-contrast)); + h3 { + color: color-mix(in srgb, var(--style-color) 60%, var(--primary-contrast)); + } + } + + .docs-code-header-style { + display: flex; + align-items: center; + font-size: 0.8rem; + + &::before { + font-family: var(--icons); + margin-right: 0.5rem; + } + &:not(:last-child)::after { + content: '-'; + margin: 0 0.5rem; + } + } + + .docs-code-header-style { + .docs-code-prefer & { + &::before { + content: 'check'; + } + } + } + + .docs-code-header-style { + .docs-code-avoid & { + &::before { + content: 'dangerous'; + } + } } } diff --git a/adev/src/content/best-practices/style-guide.md b/adev/src/content/best-practices/style-guide.md index a43a95f981f..9277e92d476 100644 --- a/adev/src/content/best-practices/style-guide.md +++ b/adev/src/content/best-practices/style-guide.md @@ -219,16 +219,15 @@ export class UserProfile { Prefer `class` and `style` bindings over using the [`NgClass`](/api/common/NgClass) and [`NgStyle`](/api/common/NgStyle) directives. -```html - +```html {prefer}
+``` - - +```html {avoid}
``` @@ -246,11 +245,11 @@ For more details, refer to the [bindings guide](/guide/templates/binding#css-cla Prefer naming event handlers for the action they perform rather than for the triggering event: -```html - +```html {prefer} +``` - +```html {avoid} ``` @@ -291,14 +290,14 @@ well-named methods to contain that logic and then _call those methods_ in your l Lifecycle hook names describe _when_ they run, meaning that the code inside doesn't have a meaningful name that describes what the code inside is doing. -```typescript -// PREFER +```ts {prefer} ngOnInit() { this.startLogging(); this.runBackgroundTask(); } +``` -// AVOID +```ts {avoid} ngOnInit() { this.logger.setMode('info'); this.logger.monitorErrors(); diff --git a/adev/src/content/kitchen-sink.md b/adev/src/content/kitchen-sink.md index 179c393a39a..059986be5ed 100644 --- a/adev/src/content/kitchen-sink.md +++ b/adev/src/content/kitchen-sink.md @@ -364,3 +364,26 @@ This can be used to separate page sections, like we're about to do below. These
The end! + +## Prefer / Avoid + +```ts {prefer} +const foo= 'bar'; +``` + +```ts {avoid} +const bar = 'foo'; +``` + +```ts {avoid, header: 'with a header'} +const baz = 42; +``` + + +