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
5 changes: 4 additions & 1 deletion apps/documentation/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
"executor": "@analogjs/platform:vite",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"syncGenerators": ["@mgremy/nx-source-plugin:component-metadata-generator"],
"syncGenerators": [
"@mgremy/nx-source-plugin:component-metadata-generator",
"@mgremy/nx-source-plugin:component-css-generator"
],
"options": {
"configFile": "apps/documentation/vite.config.ts",
"main": "apps/documentation/src/main.ts",
Expand Down
150 changes: 71 additions & 79 deletions apps/documentation/src/app/components/app-css-content.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,29 @@
import { NgClass } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
effect,
inject,
input,
signal,
untracked,
} from '@angular/core';
import { ChangeDetectionStrategy, Component, effect, inject, input, signal } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { codeToHtml } from 'shiki';

type ComponentGroup = {
name: string;
content: string;
};

@Component({
selector: 'app-css-content',
imports: [NgClass],
template: `
<div class="flex flex-col grow">
@if (availableTabs().size > 1) {
@if (selectedMetadata() && selectedMetadata()!.length > 1) {
<div class="flex flex-row justify-around border-b border-b-ui overflow-x-auto">
@for (name of availableTabs(); track name) {
@for (componentGroup of selectedMetadata(); track componentGroup.name) {
<button
class="w-full min-w-32 items-center py-2 bg-ui hover:cursor-pointer hover:bg-[color-mix(in_srgb,var(--background-color-ui),var(--mg-state-hover-mix))] transition-colors"
[ngClass]="{
'border-b border-(--text-color-accent) text-accent': selectedTab() === name,
'border-b border-(--text-color-accent) text-accent':
selectedStyle() === componentGroup.name,
}"
(click)="selectedTab.set(name)">
{{ name }}
(click)="selectedStyle.set(componentGroup.name)">
{{ componentGroup.name }}
</button>
}
</div>
Expand All @@ -48,92 +45,87 @@ import { codeToHtml } from 'shiki';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppCssContent {
private readonly _sanitizer = inject(DomSanitizer);
private readonly sanitizer = inject(DomSanitizer);

private readonly styles = import.meta.glob<string>(
'../../../../../packages/*/_theme/components/**/*.css',
private readonly metadatas = import.meta.glob<string>(
'../../../../../tmp/packages/**/css/*.json',
{
import: 'default',
query: '?raw',
eager: true,
query: '?source',
eager: false,
}
);

readonly name = input.required<string>();
readonly path = input<'ng-primitives' | 'extended'>('ng-primitives');

readonly isLoading = signal(false);
readonly isOpen = signal(false);
readonly selectedTab = signal<string>('');
readonly selectedMetadata = signal<ComponentGroup[] | undefined>(undefined);
readonly selectedStyle = signal<string>('');
readonly style = signal<SafeHtml | string>('');

readonly availableTabs = computed(() => {
const currentName = this.name();
if (!currentName) return new Set<string>();

const detectedTabs = new Set<string>();
const styleKeys = Object.keys(this.styles);
const nameRegexPattern = new RegExp(`/${currentName}/([a-zA-Z0-9-]+)\\.css$`);

for (const key of styleKeys) {
// Add css entrypoint for the current component
if (key.endsWith(`/${currentName}.css`)) {
detectedTabs.add(currentName);
continue;
}

const match = key.match(nameRegexPattern);

if (match && match[1]) {
detectedTabs.add(match[1]);
}
}

return detectedTabs;
});

constructor() {
effect(() => {
const currentName = this.name();
if (!currentName) return;
effect(async () => {
const name = this.name();
if (!name) return;

this.selectedTab.set(currentName);
await this.loadMetadata(name);
});

effect(async () => {
const currentName = untracked(this.name);
const selectedTab = this.selectedTab();
if (!selectedTab) return;
const selectedMetadata = this.selectedMetadata();
const selectedStyle = this.selectedStyle();
if (!selectedMetadata || !selectedStyle) return;

await this.loadStyle(currentName, selectedTab);
await this.generateStyle();
});
}

private async loadStyle(name: string, tab: string): Promise<void> {
const styleKeys = Object.keys(this.styles);
let nameRegexPattern = new RegExp(`/${name}/${tab}\\.css$`);

if (name === tab) {
nameRegexPattern = new RegExp(`/${name}\\.css$`);
private async loadMetadata(name: string): Promise<void> {
this.isLoading.set(true);

const metadatas = Object.entries(this.metadatas);

for (const metadata of metadatas) {
if (metadata[0].endsWith(`${name}.json`)) {
await metadata[1]()
.then((x) => JSON.parse(x) as ComponentGroup[])
.then((x) =>
x.sort((y, z) =>
// If y.name === name, first
// Else If z.name === name, first
// Else compare string
y.name === name ? -1 : z.name === name ? 1 : y.name.localeCompare(z.name)
)
)
.then((x) => this.selectedMetadata.set(x))
.then(() => this.isLoading.set(false));

// Default set the selected style to the metadata file name (without extension)
this.selectedStyle.set(name);

return;
}
}

for (const key of styleKeys) {
const match = key.match(nameRegexPattern);

if (match) {
const selectedStyle = this.styles[key];

await codeToHtml(selectedStyle.trim(), {
lang: 'postcss',
themes: {
light: 'material-theme-lighter',
dark: 'material-theme-darker',
},
})
.then((x) => this._sanitizer.bypassSecurityTrustHtml(x))
.then((x) => this.style.set(x));
this.isLoading.set(false);
}

break;
}
}
private async generateStyle(): Promise<void> {
const selectedStyle = this.selectedStyle();
const selectedMetadata = this.selectedMetadata();
const componentGroup = selectedMetadata?.find((x) => x.name === selectedStyle);

if (!componentGroup) return;

this.style.set(
await codeToHtml(componentGroup.content.trim(), {
lang: 'postcss',
themes: {
light: 'material-theme-lighter',
dark: 'material-theme-darker',
},
}).then(this.sanitizer.bypassSecurityTrustHtml)
);
}
}
23 changes: 23 additions & 0 deletions apps/documentation/src/app/components/app-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ type ComponentGroup = {
type: 'component' | 'directive';
selector: string;
exportAs: string;
host: {
name: string;
value: string;
}[];
inputs: {
name: string;
type: string;
Expand Down Expand Up @@ -160,6 +164,25 @@ export class AppMetadata {
}
}
}

markdown.push('#### CSS classes');
if (!directive.host || directive.host.length === 0) {
markdown.push('-----');
} else {
markdown.push('| class | custom class |');
markdown.push('| --- | --- |');

for (const hostDefinition of directive.host) {
if (hostDefinition.name !== 'class') continue;

const value = hostDefinition.value.replaceAll("'", '');

const clazz = value.split(' ').find((x) => !x.includes('-c-'));
const customClazz = value.split(' ').find((x) => x.startsWith('mgnp-c-'));

markdown.push(`| ${clazz} | ${customClazz} |`);
}
}
}

this.markdown.set(await marked(markdown.join('\n')));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,3 @@ Display a series of panels that can be expanded or collapsed.
## Metadata

<app-metadata name="accordion"></app-metadata>

## CSS

| directive | class | custom class |
| -------------------- | ---------------------- | ------------------------ |
| MgnpAccordion | mgnp-accordion | mgnp-c-accordion |
| MgnpAccordionContent | mgnp-accordion-content | mgnp-c-accordion-content |
| MgnpAccordionItem | mgnp-accordion-item | mgnp-c-accordion-item |
| MgnpAccordionTrigger | mgnp-accordion-trigger | mgnp-c-accordion-trigger |
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,3 @@ Help users understand their location within a hierarchy with a fully accessible
## Metadata

<app-metadata name="breadcrumb"></app-metadata>

## CSS

| directive | class | custom class |
| ----------------------- | ------------------------- | --------------------------- |
| MgnpBreadcrumb | mgnp-breadcrumb | mgnp-c-breadcrumb |
| MgnpBreadcrumbEllipsis | mgnp-breadcrumb-ellipsis | mgnp-c-breadcrumb-ellipsis |
| MgnpBreadcrumbItem | mgnp-breadcrumb-item | mgnp-c-breadcrumb-item |
| MgnpBreadcrumbLink | mgnp-breadcrumb-link | mgnp-c-breadcrumb-link |
| MgnpBreadcrumbList | mgnp-breadcrumb-list | mgnp-c-breadcrumb-list |
| MgnpBreadcrumbPage | mgnp-breadcrumb-page | mgnp-c-breadcrumb-page |
| MgnpBreadcrumbSeparator | mgnp-breadcrumb-separator | mgnp-c-breadcrumb-separator |
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,3 @@ focus.
## Metadata

<app-metadata name="button"></app-metadata>

## CSS

| directive | class | custom class |
| ---------- | ----------- | ------------- |
| MgnpButton | mgnp-button | mgnp-c-button |
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,3 @@ Perform state toggling.
## Metadata

<app-metadata name="checkbox"></app-metadata>

## CSS

| directive | class | custom class |
| ------------ | ------------- | --------------- |
| MgnpCheckbox | mgnp-checkbox | mgnp-c-checkbox |
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,3 @@ from a list of options while filtering the list based on their input.
## Metadata

<app-metadata name="combobox"></app-metadata>

## CSS

| directive | class | custom class |
| -------------------- | ---------------------- | ------------------------ |
| MgnpCombobox | mgnp-combobox | mgnp-c-combobox |
| MgnpComboboxButton | mgnp-combobox-button | mgnp-c-combobox-button |
| MgnpComboboxDropdown | mgnp-combobox-dropdown | mgnp-c-combobox-dropdown |
| MgnpComboboxInput | mgnp-combobox-input | mgnp-c-combobox-input |
| MgnpComboboxOption | mgnp-combobox-option | mgnp-c-combobox-option |
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,3 @@ A dialog is a floating window that can be used to display information or prompt
## Metadata

<app-metadata name="dialog"></app-metadata>

## CSS

| directive | class | custom class |
| --------------------- | ----------------------- | ------------------------- |
| MgnpDialog | mgnp-dialog | mgnp-c-dialog |
| MgnpDialogTitle | mgnp-dialog-title | mgnp-c-dialog-title |
| MgnpDialogDescription | mgnp-dialog-description | mgnp-c-dialog-description |
| MgnpDialogOverlay | mgnp-dialog-overlay | mgnp-c-dialog-overlay |
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,3 @@ Display a loader indicator
## Metadata

<app-metadata name="loader"></app-metadata>

## CSS

| directive | class | custom class |
| ---------- | ----------- | ------------- |
| MgnpLoader | mgnp-loader | mgnp-c-loader |
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,3 @@ Display a navbar
## Metadata

<app-metadata name="navbar"></app-metadata>

## CSS

| directive | class | custom class |
| ---------- | ----------- | ------------- |
| MgnpNavbar | mgnp-navbar | mgnp-c-navbar |
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,3 @@ Display a pre-styled table
## Metadata

<app-metadata name="table"></app-metadata>

## CSS

| directive | class | custom class |
| --------- | ---------- | ------------ |
| MgnpTable | mgnp-table | mgnp-c-table |
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,3 @@ The Form Field primitive is used to create interactive forms, with state and err
## Metadata

<app-metadata name="form-field"></app-metadata>

## CSS

| directive | class | custom class |
| ------------------- | ---------------------- | ------------------------ |
| MgnpDescription | mgnp-description | mgnp-c-description |
| MgnpError | mgnp-error | mgnp-c-error |
| MgnpFormControl | mgnp-form-control | mgnp-c-form-control |
| MgnpFormField | mgnp-form-field | mgnp-c-form-field |
| MgnpInputGroup | mgnp-input-group | mgnp-c-input-group |
| MgnpInputGroupAddon | mgnp-input-group-addon | mgnp-c-input-group-addon |
| MgnpLabel | mgnp-label | mgnp-c-label |
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,3 @@ consistent interaction handling for hover, focus, press and autofill states.
## Metadata

<app-metadata name="input"></app-metadata>

## CSS

| directive | class | custom class |
| --------- | ---------- | ------------ |
| MgnpInput | mgnp-input | mgnp-c-input |
13 changes: 0 additions & 13 deletions apps/documentation/src/content/documentation/ng-primitives/menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,3 @@ A menu is a list of options or commands presented to the user in a dropdown list
## Metadata

<app-metadata name="menu"></app-metadata>

## CSS

| directive | class | custom class |
| ---------------------- | -------------------------- | ---------------------------- |
| MgnpMenu | mgnp-menu | mgnp-c-menu |
| MgnpMenuItem | mgnp-menu-item | mgnp-c-menu-item |
| MgnpMenuItemCheckbox | mgnp-menu-item-checkbox | mgnp-c-menu-item-checkbox |
| MgnpMenuItemIndicator | mgnp-menu-item-indicator | mgnp-c-menu-item-indicator |
| MgnpMenuItemRadio | mgnp-menu-item-radio | mgnp-c-menu-item-radio |
| MgnpMenuItemRadioGroup | mgnp-menu-item-radio-group | mgnp-c-menu-item-radio-group |
| MgnpMenuTrigger | mgnp-menu-trigger | mgnp-c-menu-trigger |
| MgnpMenuSubmenuTrigger | mgnp-menu-submenu-trigger | mgnp-c-menu-submenu-trigger |
Loading
Loading