Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
651 changes: 583 additions & 68 deletions demo/package-lock.json

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
"homepage": "https://ionicframework.com/",
"scripts": {
"ng": "ng",
"docs:generate": "node scripts/generate-docs.mjs",
"prestart": "npm run docs:generate",
"start": "ng serve",
"prebuild": "npm run docs:generate",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"pretest": "npm run docs:generate",
"test": "ng test",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
Expand Down Expand Up @@ -56,6 +60,8 @@
"eslint-plugin-jsdoc": "^48.2.1",
"eslint-plugin-prefer-arrow": "1.2.2",
"jsdom": "^26.0.0",
"marked": "^16.4.2",
"shiki": "^3.23.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.61.1",
"vitest": "^4.0.0"
Expand Down
45 changes: 45 additions & 0 deletions demo/scripts/generate-docs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { readFile, writeFile } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

import { marked } from 'marked';
import { format } from 'prettier';
import { createHighlighter } from 'shiki';

const demoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const sourcePath = resolve(demoRoot, '../docs/special-markup.md');
const outputPath = resolve(demoRoot, 'src/app/docs/docs-content.generated.ts');

const markdown = await readFile(sourcePath, 'utf8');
const markdownBody = markdown.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '');
const markdownWithPreviews = markdownBody.replace(
/```html preview\r?\n([\s\S]*?)```/g,
(_match, code) =>
`\`\`\`html\n${code}\`\`\`\n\n<figure class="docs-example">\n<figcaption>Preview</figcaption>\n${code.trim()}\n</figure>`,
);
const highlighter = await createHighlighter({
themes: ['github-dark'],
langs: ['html', 'css'],
});
const highlightedBlocks = [];
const markdownWithCodePlaceholders = markdownWithPreviews.replace(/```([\w-]+)?\r?\n([\s\S]*?)```/g, (_match, language = 'text', code) => {
const highlighted = highlighter.codeToHtml(code.replace(/\r?\n$/, ''), {
lang: highlighter.getLoadedLanguages().includes(language) ? language : 'text',
theme: 'github-dark',
colorReplacements: {
'#24292e': '#151e2c',
'#6a737d': '#a0aab5',
},
});
const index = highlightedBlocks.push(`<div class="code-block-container">${highlighted}</div>`) - 1;
return `<div data-docs-code-block="${index}"></div>`;
});
let html = await marked.parse(markdownWithCodePlaceholders, { gfm: true });
html = html.replace(/<div data-docs-code-block="(\d+)"><\/div>/g, (_match, index) => highlightedBlocks[Number(index)]);
highlighter.dispose();
const generatedSource = await format(
`// Generated from docs/special-markup.md. Do not edit directly.\nexport const docsContentHtml = ${JSON.stringify(html)};\n`,
{ parser: 'typescript', printWidth: 140 },
);

await writeFile(outputPath, generatedSource);
3 changes: 3 additions & 0 deletions demo/src/app/docs/docs-content.generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Generated from docs/special-markup.md. Do not edit directly.
export const docsContentHtml =
'<h1>Special markup</h1>\n<p>Most Ionic markup works without changes. The combinations below are explicit opt-ins and are useful when the same template also uses <code>@rdlabo/ionic-theme-ios26</code>.</p>\n<h2>Two-line inset list items</h2>\n<p>Place an unslotted <code>ion-label</code> immediately alongside an unslotted <code>ion-note</code> to render a two-line item. Use <code>slot=&quot;end&quot;</code> on <code>ion-note</code> when you want the standard trailing-note layout instead.</p>\n<div class="code-block-container"><pre class="shiki github-dark" style="background-color:#151e2c;color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">ion-list</span><span style="color:#B392F0"> inset</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"true"</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#85E89D">ion-item-group</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#85E89D">ion-item</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#85E89D">ion-label</span><span style="color:#E1E4E8">>Network </span><span style="color:#79B8FF">&#x26;amp;</span><span style="color:#E1E4E8"> internet&#x3C;/</span><span style="color:#85E89D">ion-label</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#85E89D">ion-note</span><span style="color:#E1E4E8">>Mobile, Wi-Fi, hotspot&#x3C;/</span><span style="color:#85E89D">ion-note</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;/</span><span style="color:#85E89D">ion-item</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;/</span><span style="color:#85E89D">ion-item-group</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8">&#x3C;/</span><span style="color:#85E89D">ion-list</span><span style="color:#E1E4E8">></span></span></code></pre></div>\n\n<figure class="docs-example">\n<figcaption>Preview</figcaption>\n<ion-list inset="true">\n <ion-item-group>\n <ion-item>\n <ion-label>Network &amp; internet</ion-label>\n <ion-note>Mobile, Wi-Fi, hotspot</ion-note>\n </ion-item>\n </ion-item-group>\n</ion-list>\n</figure>\n\n<h2>Square buttons</h2>\n<p>Add <code>.button-square</code> when a button should use more squared corners. It works with text buttons and icon-only buttons.</p>\n<div class="code-block-container"><pre class="shiki github-dark" style="background-color:#151e2c;color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">ion-button</span><span style="color:#B392F0"> class</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"button-square"</span><span style="color:#B392F0"> fill</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"solid"</span><span style="color:#E1E4E8">>Continue&#x3C;/</span><span style="color:#85E89D">ion-button</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">ion-button</span><span style="color:#B392F0"> class</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"button-square"</span><span style="color:#B392F0"> fill</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"solid"</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#85E89D">ion-icon</span><span style="color:#B392F0"> name</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"add"</span><span style="color:#B392F0"> slot</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"icon-only"</span><span style="color:#E1E4E8">>&#x3C;/</span><span style="color:#85E89D">ion-icon</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8">&#x3C;/</span><span style="color:#85E89D">ion-button</span><span style="color:#E1E4E8">></span></span></code></pre></div>\n\n<figure class="docs-example">\n<figcaption>Preview</figcaption>\n<ion-button class="button-square" fill="solid">Continue</ion-button>\n<ion-button class="button-square" fill="solid">\n <ion-icon name="add" slot="icon-only"></ion-icon>\n</ion-button>\n</figure>\n\n<h2>Inset-list section headers</h2>\n<p>Add <code>.item-group-header</code> to an <code>ion-item-group</code> to create the centered icon, title, and description used at the top of the component demo pages.</p>\n<div class="code-block-container"><pre class="shiki github-dark" style="background-color:#151e2c;color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">ion-list</span><span style="color:#B392F0"> inset</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"true"</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#85E89D">ion-item-group</span><span style="color:#B392F0"> class</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"item-group-header"</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#85E89D">ion-item</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#85E89D">ion-label</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#85E89D">ion-icon</span><span style="color:#B392F0"> name</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"list"</span><span style="color:#B392F0"> style</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"background: var(--ion-color-primary)"</span><span style="color:#E1E4E8">>&#x3C;/</span><span style="color:#85E89D">ion-icon</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#85E89D">h2</span><span style="color:#E1E4E8">>Lists&#x3C;/</span><span style="color:#85E89D">h2</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;</span><span style="color:#85E89D">ion-text</span><span style="color:#E1E4E8">>Inset-list examples&#x3C;/</span><span style="color:#85E89D">ion-text</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;/</span><span style="color:#85E89D">ion-label</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;/</span><span style="color:#85E89D">ion-item</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8"> &#x3C;/</span><span style="color:#85E89D">ion-item-group</span><span style="color:#E1E4E8">></span></span>\n<span class="line"><span style="color:#E1E4E8">&#x3C;/</span><span style="color:#85E89D">ion-list</span><span style="color:#E1E4E8">></span></span></code></pre></div>\n\n<figure class="docs-example">\n<figcaption>Preview</figcaption>\n<ion-list inset="true">\n <ion-item-group class="item-group-header">\n <ion-item>\n <ion-label>\n <ion-icon name="list" style="background: var(--ion-color-primary)"></ion-icon>\n <h2>Lists</h2>\n <ion-text>Inset-list examples</ion-text>\n </ion-label>\n </ion-item>\n </ion-item-group>\n</ion-list>\n</figure>\n\n<h2>Opting out</h2>\n<p>Add <code>.md3-disabled</code> to an individual Ionic component when it must retain Ionic&#39;s standard Material styling.</p>\n<div class="code-block-container"><pre class="shiki github-dark" style="background-color:#151e2c;color:#e1e4e8" tabindex="0"><code><span class="line"><span style="color:#E1E4E8">&#x3C;</span><span style="color:#85E89D">ion-button</span><span style="color:#B392F0"> fill</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"solid"</span><span style="color:#E1E4E8">>MD3 theme&#x3C;/</span><span style="color:#85E89D">ion-button</span><span style="color:#E1E4E8">> &#x3C;</span><span style="color:#85E89D">ion-button</span><span style="color:#B392F0"> class</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"md3-disabled"</span><span style="color:#B392F0"> fill</span><span style="color:#E1E4E8">=</span><span style="color:#9ECBFF">"solid"</span><span style="color:#E1E4E8">>Standard Ionic&#x3C;/</span><span style="color:#85E89D">ion-button</span><span style="color:#E1E4E8">></span></span></code></pre></div>\n\n<figure class="docs-example">\n<figcaption>Preview</figcaption>\n<ion-button fill="solid">MD3 theme</ion-button> <ion-button class="md3-disabled" fill="solid">Standard Ionic</ion-button>\n</figure>\n';
45 changes: 4 additions & 41 deletions demo/src/app/docs/docs-page.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,12 @@
<ion-toolbar>
<ion-title>Docs</ion-title>
<ion-buttons slot="end">
<ion-button fill="default" href="https://github.com/rdlabo-dev/ionic-theme-md3"
><ion-icon name="logo-github" slot="icon-only"></ion-icon
></ion-button>
<ion-button fill="default" href="https://github.com/rdlabo-dev/ionic-theme-md3">
<ion-icon name="logo-github" slot="icon-only"></ion-icon>
</ion-button>
</ion-buttons>
<ion-button slot="end" fill="clear" href="https://github.com/rdlabo-dev/ionic-theme-md3"
><ion-icon name="logo-github" slot="icon-only"></ion-icon
></ion-button>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<section class="ion-padding-start ion-padding-end ion-padding-bottom">
<p>This is a demo of the Material Design 3 theme for Ionic Framework. Just import the CSS and start using it immediately.</p>
<code class="code-block">% npm install &#64;rdlabo/ionic-theme-md3 --save</code>
<p>For detailed usage instructions, please refer to the README in the GitHub repository.</p>
</section>

<ion-list [inset]="true">
<ion-list-header><ion-label>Special Coding</ion-label><ion-note>Mode more iOS26</ion-note></ion-list-header>

<ion-item-group>
<ion-item>
<div class="ion-padding-bottom">
<h3><code>ion-list[inset] ion-label + ion-note</code></h3>
<p>
When an inset list item contains an <code>ion-label</code> and an <code>ion-note</code> without slots, they are displayed
vertically as a two-line item.
</p>
<code class="code-block">
&lt;ion-list [inset]="true"&gt;<br />
&nbsp;&nbsp;&lt;ion-item&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&lt;ion-label&gt;Network &amp; internet&lt;/ion-label&gt;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&lt;ion-note&gt;Mobile, Wi-Fi, hotspot&lt;/ion-note&gt;<br />
&nbsp;&nbsp;&lt;/ion-item&gt;<br />
&lt;/ion-list&gt;
</code>
<ion-list [inset]="true">
<ion-item color="light">
<ion-label>Network & internet</ion-label>
<ion-note>Mobile, Wi-Fi, hotspot</ion-note>
</ion-item>
</ion-list>
</div>
</ion-item>
</ion-item-group>
</ion-list>
<article class="docs-content" [innerHTML]="docsContentHtml | safeHtml"></article>
</ion-content>
16 changes: 5 additions & 11 deletions demo/src/app/docs/docs-page.component.scss
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
h3 {
font-size: 1.1rem;
font-weight: bold;
}

code.code-block {
background: var(--ion-color-dark);
color: var(--ion-color-dark-contrast);
display: inline-block;
padding: 8px 12px;
border-radius: 8px;
.docs-content {
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 24px 16px 72px;
}
9 changes: 9 additions & 0 deletions demo/src/app/docs/docs-page.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,13 @@ describe('DocsPage', () => {
it('should create', () => {
expect(component).toBeTruthy();
});

it('renders the generated Markdown documentation with live examples', () => {
const element = fixture.nativeElement as HTMLElement;

expect(element.querySelector('article.docs-content h1')?.textContent).toContain('Special markup');
expect(element.querySelectorAll('.code-block-container pre.shiki code').length).toBeGreaterThan(0);
expect(element.querySelector('.code-block-container code span[style*="color"]')).not.toBeNull();
expect(element.querySelectorAll('figure.docs-example').length).toBe(4);
});
});
42 changes: 6 additions & 36 deletions demo/src/app/docs/docs-page.component.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,14 @@
import { Component, inject } from '@angular/core';

import { FormsModule } from '@angular/forms';
import {
IonButton,
IonButtons,
IonContent,
IonHeader,
IonIcon,
IonTitle,
IonToolbar,
Platform,
IonItem,
IonItemGroup,
IonLabel,
IonList,
IonListHeader,
IonNote,
} from '@demo/ionic';
import { Component } from '@angular/core';
import { IonButton, IonButtons, IonContent, IonHeader, IonIcon, IonTitle, IonToolbar } from '@demo/ionic';
import { docsContentHtml } from './docs-content.generated';
import { SafeHtmlPipe } from './safe-html.pipe';

@Component({
selector: 'app-docs-page',
templateUrl: './docs-page.component.html',
styleUrls: ['./docs-page.component.scss'],
imports: [
IonContent,
IonHeader,
IonTitle,
IonToolbar,
FormsModule,
IonIcon,
IonButton,
IonButtons,
IonItemGroup,
IonList,
IonListHeader,
IonLabel,
IonNote,
IonItem,
],
imports: [IonContent, IonHeader, IonTitle, IonToolbar, IonIcon, IonButton, IonButtons, SafeHtmlPipe],
})
export class DocsPage {
readonly platform = inject(Platform);
readonly docsContentHtml = docsContentHtml;
}
13 changes: 13 additions & 0 deletions demo/src/app/docs/safe-html.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Pipe, PipeTransform, inject } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

@Pipe({
name: 'safeHtml',
})
export class SafeHtmlPipe implements PipeTransform {
readonly #sanitizer = inject(DomSanitizer);

transform(html: string): SafeHtml {
return this.#sanitizer.bypassSecurityTrustHtml(html);
}
}
79 changes: 79 additions & 0 deletions demo/src/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,85 @@ code {
font-family: Menlo, Consolas, monospace;
}

.docs-content {
line-height: 1.7;

h1,
h2 {
padding-bottom: 8px;
border-bottom: 1px solid var(--ion-color-step-150, #d7d8da);
}

h1 {
margin: 0 0 24px;
font-size: 1.75rem;
}

h2 {
margin: 40px 0 16px;
font-size: 1.4rem;
}

p {
margin: 16px 0;
}

a {
color: var(--ion-color-primary);
}

:not(pre) > code {
padding: 2px 6px;
border-radius: 4px;
background: var(--ion-color-step-100, #f1f3f5);
font-size: 0.85em;
}

.code-block-container {
margin: 16px 0;
overflow-x: auto;
border-radius: 8px;

pre.shiki {
margin: 0;
padding: 0;
line-height: 1.5;

code {
display: block;
padding: 16px;
overflow-x: auto;
background: transparent;
font-size: 0.85rem;
white-space: pre;
}
}
}
}

.docs-content .docs-example {
margin: 16px 0 32px;
padding: 16px;
overflow: hidden;
border: 1px solid var(--ion-color-step-150, #d7d8da);
border-radius: 16px;
background: var(--ion-background-color, #fff);

figcaption {
margin-bottom: 12px;
color: var(--ion-color-medium, #92949c);
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
}

ion-list,
ion-toolbar {
margin: 0;
}
}

#main-content {
width: 100%;
}
Expand Down
Loading
Loading