Skip to content

Commit 8401f89

Browse files
ok7saiatscott
authored andcommitted
docs(docs-infra): allow collapse code example (angular#63559)
PR Close angular#63559
1 parent 0b47781 commit 8401f89

11 files changed

Lines changed: 137 additions & 42 deletions

File tree

adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,10 @@ describe('DocViewer', () => {
109109
expect(exampleViewer).not.toBeNull();
110110
expect(exampleViewer.componentInstance.view()).toBe(CodeExampleViewMode.SNIPPET);
111111

112-
const checkIcon = fixture.debugElement.query(By.directive(IconComponent));
112+
const copySourceCodeButton = fixture.debugElement.query(By.directive(CopySourceCodeButton));
113+
expect(copySourceCodeButton).not.toBeNull();
114+
115+
const checkIcon = copySourceCodeButton.query(By.directive(IconComponent));
113116
expect((checkIcon.nativeElement as HTMLElement).classList).toContain(
114117
`material-symbols-outlined`,
115118
);

adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export class DocViewer {
188188
path: string,
189189
): Promise<void> {
190190
const preview = Boolean(placeholder.getAttribute('preview'));
191+
const hideCode = Boolean(placeholder.getAttribute('hideCode'));
191192
const title = placeholder.getAttribute('header') ?? undefined;
192193
const firstCodeSnippetTitle =
193194
snippets.length > 0 ? (snippets[0].title ?? snippets[0].name) : undefined;
@@ -199,6 +200,7 @@ export class DocViewer {
199200
path,
200201
files: snippets,
201202
preview,
203+
hideCode,
202204
id: this.countOfExamples,
203205
});
204206

adev/shared-docs/components/viewers/example-viewer/example-viewer.component.html

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,35 @@
11
<div class="docs-example-viewer" role="group">
22
<header class="docs-example-viewer-actions">
3-
@if (view() === CodeExampleViewMode.SNIPPET) {
4-
<span>{{ exampleMetadata()?.title }}</span>
5-
}
3+
@if (showCode()) {
4+
@if (view() === CodeExampleViewMode.SNIPPET) {
5+
<span>{{ exampleMetadata()?.title }}</span>
6+
}
67

7-
@if (view() === CodeExampleViewMode.MULTI_FILE) {
8-
<mat-tab-group #codeTabs animationDuration="0ms" mat-stretch-tabs="false">
9-
@for (tab of tabs(); track tab) {
10-
<mat-tab [label]="tab.name" />
11-
}
12-
</mat-tab-group>
8+
@if (view() === CodeExampleViewMode.MULTI_FILE) {
9+
<mat-tab-group #codeTabs animationDuration="0ms" mat-stretch-tabs="false">
10+
@for (tab of tabs(); track tab) {
11+
<mat-tab [label]="tab.name" />
12+
}
13+
</mat-tab-group>
14+
}
15+
} @else {
16+
<!-- Title placeholder -->
17+
<span aria-hidden="true">&nbsp;</span>
1318
}
1419

1520
<div class="docs-example-viewer-icons">
21+
<button
22+
type="button"
23+
role="switch"
24+
class="docs-example-code-toggle"
25+
[attr.aria-checked]="showCode()"
26+
[attr.aria-label]="showCode() ? 'Hide code' : 'Show code'"
27+
(click)="showCode.set(!showCode())"
28+
[matTooltip]="showCode() ? 'Hide code' : 'Show code'"
29+
matTooltipPosition="above"
30+
>
31+
<docs-icon>{{showCode() ? 'code_off' : 'code'}}</docs-icon>
32+
</button>
1633
<button
1734
type="button"
1835
class="docs-example-copy-link"
@@ -80,16 +97,18 @@
8097
</div>
8198
</header>
8299

83-
<div
84-
class="docs-example-viewer-code-wrapper"
85-
[class.docs-example-viewer-snippet]="view() === CodeExampleViewMode.SNIPPET"
86-
[class.docs-example-viewer-multi-file]="view() === CodeExampleViewMode.MULTI_FILE"
87-
>
88-
<button docs-copy-source-code></button>
89-
@if (snippetCode()?.sanitizedContent; as content) {
90-
<div [innerHTML]="content"></div>
91-
}
92-
</div>
100+
@if (showCode()) {
101+
<div
102+
class="docs-example-viewer-code-wrapper"
103+
[class.docs-example-viewer-snippet]="view() === CodeExampleViewMode.SNIPPET"
104+
[class.docs-example-viewer-multi-file]="view() === CodeExampleViewMode.MULTI_FILE"
105+
>
106+
<button docs-copy-source-code></button>
107+
@if (snippetCode()?.sanitizedContent; as content) {
108+
<div [innerHTML]="content"></div>
109+
}
110+
</div>
111+
}
93112

94113
@if (exampleComponent) {
95114
<div class="docs-example-viewer-preview">

adev/shared-docs/components/viewers/example-viewer/example-viewer.component.scss

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,25 @@
5757
display: flex;
5858
gap: 0.75rem;
5959

60-
svg {
61-
fill: var(--gray-400);
62-
}
63-
}
60+
a,
61+
button {
62+
padding: 0;
63+
margin: 0;
64+
cursor: pointer;
65+
height: 24px;
66+
width: 24px;
67+
color: var(--gray-400);
6468

65-
a,
66-
button {
67-
padding: 0;
68-
margin: 0;
69-
cursor: pointer;
70-
height: 24px;
71-
width: 24px;
72-
path {
73-
transition: fill 0.3s ease;
74-
}
69+
path {
70+
transition: fill 0.3s ease;
71+
}
7572

76-
&:hover {
7773
svg {
78-
fill: var(--tertiary-contrast);
74+
fill: currentColor;
75+
}
76+
77+
&:hover {
78+
color: var(--tertiary-contrast);
7979
}
8080
}
8181
}

adev/shared-docs/components/viewers/example-viewer/example-viewer.component.spec.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,50 @@ describe('ExampleViewer', () => {
272272
button.click();
273273
expect(spy.calls.argsFor(0)[0].trim()).toBe(`${window.location.href}#example-1`);
274274
});
275+
276+
it('should hide code content when `hideCode` is true', async () => {
277+
componentRef.setInput(
278+
'metadata',
279+
getMetadata({
280+
hideCode: true,
281+
}),
282+
);
283+
284+
await component.renderExample();
285+
fixture.detectChanges();
286+
287+
// Initially, the code should be hidden.
288+
expect(component.showCode()).toBeFalse();
289+
let codeContainer = fixture.debugElement.query(By.css('.docs-example-viewer-code-wrapper'));
290+
expect(codeContainer).toBeNull();
291+
});
292+
293+
it('should expand/collapse code content with toggle button.', async () => {
294+
componentRef.setInput('metadata', getMetadata());
295+
296+
await component.renderExample();
297+
fixture.detectChanges();
298+
299+
// Initially, the code should be visible.
300+
expect(component.showCode()).toBeTrue();
301+
let codeContainer = fixture.debugElement.query(By.css('.docs-example-viewer-code-wrapper'));
302+
expect(codeContainer).not.toBeNull();
303+
304+
const codeToggleButton = fixture.debugElement.query(By.css('.docs-example-code-toggle'));
305+
codeToggleButton.nativeElement.click();
306+
fixture.detectChanges();
307+
308+
expect(component.showCode()).toBeFalse();
309+
codeContainer = fixture.debugElement.query(By.css('.docs-example-viewer-code-wrapper'));
310+
expect(codeContainer).toBeNull();
311+
312+
codeToggleButton.nativeElement.click();
313+
fixture.detectChanges();
314+
315+
expect(component.showCode()).toBeTrue();
316+
codeContainer = fixture.debugElement.query(By.css('.docs-example-viewer-code-wrapper'));
317+
expect(codeContainer).not.toBeNull();
318+
});
275319
});
276320

277321
const getMetadata = (value: Partial<ExampleMetadata> = {}): ExampleMetadata => {
@@ -282,6 +326,7 @@ const getMetadata = (value: Partial<ExampleMetadata> = {}): ExampleMetadata => {
282326
{name: 'example.css', sanitizedContent: ''},
283327
],
284328
preview: false,
329+
hideCode: false,
285330
...value,
286331
};
287332
};

adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {CommonModule, DOCUMENT} from '@angular/common';
2525
import {MatTabGroup, MatTabsModule} from '@angular/material/tabs';
2626
import {Clipboard} from '@angular/cdk/clipboard';
2727
import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component';
28+
import {IconComponent} from '../../icon/icon.component';
2829
import {ExampleMetadata, Snippet} from '../../../interfaces/index';
2930
import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers/index';
3031
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@@ -42,7 +43,7 @@ export const HIDDEN_CLASS_NAME = 'hidden';
4243

4344
@Component({
4445
selector: 'docs-example-viewer',
45-
imports: [CommonModule, CopySourceCodeButton, MatTabsModule, MatTooltipModule],
46+
imports: [CommonModule, CopySourceCodeButton, MatTabsModule, MatTooltipModule, IconComponent],
4647
templateUrl: './example-viewer.component.html',
4748
styleUrls: ['./example-viewer.component.scss'],
4849
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -75,6 +76,7 @@ export class ExampleViewer {
7576
readonly expandable = signal<boolean>(false);
7677
readonly expanded = signal<boolean>(false);
7778
readonly snippetCode = signal<Snippet | undefined>(undefined);
79+
readonly showCode = signal<boolean>(true);
7880
readonly tabs = computed(() =>
7981
this.exampleMetadata()?.files.map((file) => ({
8082
name:
@@ -98,6 +100,10 @@ export class ExampleViewer {
98100

99101
this.snippetCode.set(this.exampleMetadata()?.files[0]);
100102

103+
if (this.exampleMetadata()?.hideCode) {
104+
this.showCode.set(false);
105+
}
106+
101107
afterNextRender(
102108
() => {
103109
// Several function below query the DOM directly, we need to wait until the DOM is rendered.

adev/shared-docs/interfaces/code-example.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,6 @@ export interface ExampleMetadata {
3838
files: Snippet[];
3939
/** True when ExampleViewer should have preview */
4040
preview: boolean;
41+
/** Whether to hide code example by default. */
42+
hideCode: boolean;
4143
}

adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code-multifile.mts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export interface DocsCodeMultifileToken extends Tokens.Generic {
2020
paneTokens: Token[];
2121
// True if we should display preview
2222
preview: boolean;
23+
/** Whether to hide code example by default. */
24+
hideCode: boolean;
2325
}
2426

2527
// Capture group 1: all attributes on the opening tag
@@ -28,6 +30,7 @@ const multiFileCodeRule = /^\s*<docs-code-multifile(.*?)>(.*?)<\/docs-code-multi
2830

2931
const pathRule = /path="([^"]*)"/;
3032
const previewRule = /preview/;
33+
const hideCodeRule = /hideCode/;
3134

3235
export const docsCodeMultifileExtension = {
3336
name: 'docs-code-multifile',
@@ -42,6 +45,7 @@ export const docsCodeMultifileExtension = {
4245
const attr = match[1].trim();
4346
const path = pathRule.exec(attr);
4447
const preview = previewRule.exec(attr) ? true : false;
48+
const hideCode = hideCodeRule.exec(attr) ? true : false;
4549

4650
const token: DocsCodeMultifileToken = {
4751
type: 'docs-code-multifile',
@@ -50,6 +54,7 @@ export const docsCodeMultifileExtension = {
5054
panes: match[2].trim(),
5155
paneTokens: [],
5256
preview: preview,
57+
hideCode,
5358
};
5459
this.lexer.blockTokens(token.panes, token.paneTokens);
5560
return token;
@@ -69,6 +74,9 @@ export const docsCodeMultifileExtension = {
6974
if (token.preview) {
7075
el.setAttribute('preview', `${token.preview}`);
7176
}
77+
if (token.hideCode) {
78+
el.setAttribute('hideCode', 'true');
79+
}
7280

7381
return el.outerHTML;
7482
},

adev/shared-docs/pipeline/shared/marked/extensions/docs-code/docs-code.mts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const languageRule = /language="([^"]*)"/;
3232
const visibleLinesRule = /visibleLines="([^"]*)"/;
3333
const visibleRegionRule = /visibleRegion="([^"]*)"/;
3434
const previewRule = /preview/;
35+
const hideCodeRule = /hideCode/;
3536

3637
export const docsCodeExtension = {
3738
name: 'docs-code',
@@ -53,6 +54,7 @@ export const docsCodeExtension = {
5354
const visibleLines = visibleLinesRule.exec(attr);
5455
const visibleRegion = visibleRegionRule.exec(attr);
5556
const preview = previewRule.exec(attr) ? true : false;
57+
const hideCode = hideCodeRule.exec(attr) ? true : false;
5658
const classes = classRule.exec(attr);
5759

5860
let code = match[2]?.trim() ?? '';
@@ -76,6 +78,7 @@ export const docsCodeExtension = {
7678
visibleLines: visibleLines?.[1],
7779
visibleRegion: visibleRegion?.[1],
7880
preview: preview,
81+
hideCode,
7982
classes: classes?.[1]?.split(' '),
8083
};
8184
return token;

adev/shared-docs/pipeline/shared/marked/extensions/docs-code/format/index.mts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export interface CodeToken extends Tokens.Generic {
3636
visibleRegion?: string;
3737
/* Whether we should display preview */
3838
preview?: boolean;
39+
/** Whether to hide code example by default. */
40+
hideCode?: boolean;
3941
/* The lines to display highlighting on */
4042
highlight?: string;
4143

@@ -121,6 +123,9 @@ function applyContainerAttributesAndClasses(el: Element, token: CodeToken) {
121123
if (token.preview) {
122124
el.setAttribute('preview', 'true');
123125
}
126+
if (token.hideCode) {
127+
el.setAttribute('hideCode', 'true');
128+
}
124129
if (token.language === 'mermaid') {
125130
el.setAttribute('mermaid', 'true');
126131
}

0 commit comments

Comments
 (0)