Skip to content

Commit 8509dcd

Browse files
author
Sebastian Schürmann
committed
feature(vanillin): starts of a doc page generator.
1 parent cb60799 commit 8509dcd

File tree

5 files changed

+170
-6
lines changed

5 files changed

+170
-6
lines changed

packages/vanillin/src/doc-gen.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { TSDocParser, type ParserContext, type DocComment, TSDocConfiguration, TSDocTagDefinition, TSDocTagSyntaxKind } from '@microsoft/tsdoc';
22
import * as path from 'path';
33
import { readFile } from 'fs/promises';
4-
import { Formatter } from './formatter';
4+
import { FormatterDefault } from './formatter/default';
55

66
/**
77
* A class for parsing and rendering TSDoc documentation comments.
@@ -61,7 +61,7 @@ export class DocGen {
6161
*/
6262
render(context: ParserContext): string {
6363
const docComment: DocComment = context.docComment;
64-
const result = Formatter.renderDocNodes(docComment.getChildNodes());
64+
const result = FormatterDefault.renderDocNodes(docComment.getChildNodes());
6565
return result;
6666
}
6767

@@ -78,7 +78,7 @@ export class DocGen {
7878
throw new Error('Invalid parser context: docComment is undefined');
7979
}
8080
const docComment: DocComment = context.docComment;
81-
const result = Formatter.renderDocNodes(docComment.customBlocks);
81+
const result = FormatterDefault.renderDocNodes(docComment.customBlocks);
8282
return result;
8383
}
8484
}

packages/vanillin/src/formatter.ts packages/vanillin/src/formatter/default.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import { type DocNode, DocExcerpt } from '@microsoft/tsdoc';
66
/**
77
* This is a simplistic solution until we implement proper DocNode rendering APIs.
88
*/
9-
export class Formatter {
9+
export class FormatterDefault {
1010
public static renderDocNode(docNode: DocNode): string {
1111
let result: string = '';
1212
if (docNode) {
1313
if (docNode instanceof DocExcerpt) {
1414
result += docNode.content.toString();
1515
}
1616
for (const childNode of docNode.getChildNodes()) {
17-
result += Formatter.renderDocNode(childNode);
17+
result += FormatterDefault.renderDocNode(childNode);
1818
}
1919
}
2020
return result;
@@ -23,7 +23,7 @@ export class Formatter {
2323
public static renderDocNodes(docNodes: ReadonlyArray<DocNode>): string {
2424
let result: string = '';
2525
for (const docNode of docNodes) {
26-
result += Formatter.renderDocNode(docNode);
26+
result += FormatterDefault.renderDocNode(docNode);
2727
}
2828
return result;
2929
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { DocBlock, DocParamCollection, DocSection, ParserContext } from "@microsoft/tsdoc";
2+
3+
export class FormatterDocPage {
4+
private context: ParserContext;
5+
6+
constructor(context: ParserContext) {
7+
this.context = context;
8+
}
9+
10+
get custom(): readonly DocBlock[] {
11+
return this.context.docComment.customBlocks;
12+
}
13+
14+
get params(): DocParamCollection {
15+
return this.context.docComment.params
16+
}
17+
18+
get summary(): DocSection {
19+
return this.context.docComment.summarySection
20+
}
21+
22+
createDocPage(): string {
23+
const title = "MyCircle Component Demo";
24+
const src = "../dist/my-circle.js"
25+
const tagName = "my-circle"
26+
return `<!DOCTYPE html>
27+
<html lang="en">
28+
<head>
29+
<meta charset="UTF-8">
30+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
31+
<title>${title}</title>
32+
<script type="module" src="${src}"></script>
33+
</head>
34+
<body>
35+
<h1>${title}</h1>
36+
<${tagName}></${tagName}>
37+
</body>
38+
</html>`
39+
}
40+
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { describe, it, before } from "node:test";
2+
import assert from "node:assert";
3+
import { DocGen } from '../src/doc-gen';
4+
import { ParserContext } from "@microsoft/tsdoc";
5+
import { FormatterDocPage } from '../src/formatter/doc-page';
6+
7+
describe('DocGen Formatter', () => {
8+
let docGen: DocGen;
9+
let parsed: ParserContext;
10+
let formatter: FormatterDocPage;
11+
12+
before(async () => {
13+
docGen = new DocGen();
14+
parsed = await docGen.parseDoc('./test/fixtures/my-circle.ts');
15+
formatter = new FormatterDocPage(parsed);
16+
})
17+
it('should parse a typescript file and return a ParserContext', async () => {
18+
assert.ok(parsed, 'ParserContext should be returned');
19+
});
20+
21+
it('should create a doc page', async () => {
22+
const result = formatter.createDocPage();
23+
assert.ok(result, 'Doc page should be created');
24+
});
25+
26+
it('contains the demo tag', async () => {
27+
const result = formatter.createDocPage();
28+
assert.match(result, /<my-circle><\/my-circle>/);
29+
});
30+
});
+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* A custom web component that renders a button with customizable styles.
3+
*
4+
* @example
5+
* ```html
6+
* <custom-button type="primary" size="large">Click me</custom-button>
7+
* ```
8+
*/
9+
/**
10+
* A demo of a custom button component with different styles and sizes.
11+
*
12+
* @demo
13+
* ```html
14+
* <custom-button type="primary" size="large">Primary Large</custom-button>
15+
* <custom-button type="secondary" size="medium">Secondary Medium</custom-button>
16+
* <custom-button type="ghost" size="small">Ghost Small</custom-button>
17+
* ```
18+
*/
19+
class CustomButton extends HTMLElement {
20+
/** The button type: 'primary', 'secondary', or 'ghost'. Default is 'primary'. */
21+
private _type: string = 'primary';
22+
/** The button size: 'small', 'medium', or 'large'. Default is 'medium'. */
23+
private _size: string = 'medium';
24+
25+
constructor() {
26+
super();
27+
this.attachShadow({ mode: 'open' });
28+
this.render();
29+
}
30+
31+
static get observedAttributes() {
32+
return ['type', 'size'];
33+
}
34+
35+
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
36+
if (oldValue !== newValue) {
37+
if (name === 'type') this._type = newValue;
38+
if (name === 'size') this._size = newValue;
39+
this.render();
40+
}
41+
}
42+
43+
private render() {
44+
const style = document.createElement('style');
45+
style.textContent = `
46+
:host {
47+
display: inline-block;
48+
}
49+
button {
50+
font-family: system-ui;
51+
border-radius: 4px;
52+
border: none;
53+
cursor: pointer;
54+
transition: all 0.2s;
55+
}
56+
button.primary {
57+
background: #0066cc;
58+
color: white;
59+
}
60+
button.secondary {
61+
background: #e0e0e0;
62+
color: #333;
63+
}
64+
button.ghost {
65+
background: transparent;
66+
border: 1px solid #0066cc;
67+
color: #0066cc;
68+
}
69+
button.small {
70+
padding: 4px 8px;
71+
font-size: 12px;
72+
}
73+
button.medium {
74+
padding: 8px 16px;
75+
font-size: 14px;
76+
}
77+
button.large {
78+
padding: 12px 24px;
79+
font-size: 16px;
80+
}
81+
`;
82+
83+
const button = document.createElement('button');
84+
button.className = `${this._type} ${this._size}`;
85+
button.innerHTML = '<slot></slot>';
86+
87+
this.shadowRoot!.innerHTML = '';
88+
this.shadowRoot!.appendChild(style);
89+
this.shadowRoot!.appendChild(button);
90+
}
91+
}
92+
93+
customElements.define('custom-button', CustomButton);

0 commit comments

Comments
 (0)