diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 4615bd94..0a0b99ea 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -28,10 +28,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
with:
- fetch-depth: 0
+ fetch-depth: 0
- uses: pnpm/action-setup@v3
with:
- version: 9
+ version: 9
- name: Setup Node
uses: actions/setup-node@v4
with:
@@ -41,8 +41,8 @@ jobs:
uses: actions/configure-pages@v4
- name: Install dependencies
run: pnpm install
- - name: Analyze
- run: pnpm analyze
+ - name: Building Packages
+ run: pnpm packages:build
- name: Build Docs
run: pnpm docs:build
- name: Upload artifact
@@ -60,4 +60,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
- uses: actions/deploy-pages@v4
\ No newline at end of file
+ uses: actions/deploy-pages@v4
diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css
index 7bcf453b..e23b8348 100644
--- a/docs/.vitepress/theme/custom.css
+++ b/docs/.vitepress/theme/custom.css
@@ -13,6 +13,37 @@
flex-wrap: wrap;
}
+#usage {
+ background: var(--vp-c-bg-soft);
+ padding: 18px;
+ border-radius: 8px;
+ overflow: auto;
+ font-size: 14px;
+}
+
+#usage li {
+ display: flex;
+ align-items: center;
+}
+
+:not(.dark) #usage pre span {
+ color: var(--shiki-light, inherit);
+}
+
+.dark #usage pre span {
+ color: var(--shiki-dark, inherit);
+}
+
+#usage pre {
+ margin: 0;
+ flex-shrink: 0;
+}
+
+#usage strong {
+ width: 90px;
+ flex-shrink: 0;
+}
+
.table-wrapper {
width: 100%;
overflow: auto;
@@ -22,3 +53,51 @@
min-width: 688px;
display: table;
}
+
+.modal-overlay {
+ position: fixed;
+ background: var(--vp-backdrop-bg-color);
+ backdrop-filter: blur(4px);
+
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 100;
+
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 240ms;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.modal-overlay.open {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+.modal {
+ overflow: auto;
+ background: var(--vp-c-bg);
+ padding: 0 30px 30px;
+ max-width: 90dvw;
+ width: 500px;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ z-index: 101;
+ transform: translate(-50%, -50%);
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 240ms;
+ border-radius: 16px;
+}
+
+.modal.open {
+ transform: translate(-50%, -50%);
+ pointer-events: auto;
+ opacity: 1;
+}
+
diff --git a/docs/components/[component].md b/docs/components/[component].md
index 9d01ef08..13fd489a 100644
--- a/docs/components/[component].md
+++ b/docs/components/[component].md
@@ -4,33 +4,4 @@ next: false
outline: "deep"
---
-
-
diff --git a/docs/components/[component].paths.ts b/docs/components/[component].paths.ts
index 701fbc1b..cc4f83b8 100644
--- a/docs/components/[component].paths.ts
+++ b/docs/components/[component].paths.ts
@@ -5,7 +5,13 @@ import {
type Metadata,
} from "../../internals/doc-helpers/types.ts";
import { getFileMeta } from "../../scripts/utils.ts";
-import { codify, tabulateData } from "../utils/md.ts";
+import {
+ codify,
+ getFormattedImportUsageString,
+ getFormattedTagUsageString,
+ getUsageSectionMarkdown,
+ tabulateData,
+} from "../utils/markdown.ts";
export default {
paths() {
@@ -43,7 +49,13 @@ const getComponentMarkdown = (component: Component) => {
res += `${component.summary}\n`;
- res += getUsageMarkdown(component);
+ res += getUsageSectionMarkdown([
+ [
+ "Import",
+ getFormattedImportUsageString(component.importPaths.webComponents),
+ ],
+ ["Tag", getFormattedTagUsageString(component.tagName)],
+ ]);
res += getMembersMarkdown(component);
@@ -95,19 +107,6 @@ ${slots
return res;
};
-const getUsageMarkdown = (component: Component) => {
- let res = "";
-
- res += "\n
\n";
-
- return res;
-};
-
const getMembersMarkdown = (component: Component) => {
const members = component.members || [];
let res = "";
diff --git a/docs/icons.md b/docs/icons.md
new file mode 100644
index 00000000..397e0436
--- /dev/null
+++ b/docs/icons.md
@@ -0,0 +1,113 @@
+---
+prev: false
+next: false
+outline: false
+---
+
+
+
+
+
+# Icons
+
+## Properties
+
+
+
+
+| Name | Description | Default Value |
+|------------|--------------------------------------------------------------------------------------------------------------------------------------------|-----------------|
+| `viewbox` | The viewBox of the SVG. Allows you to redefine what the coordinates without units mean inside an SVG element. | `0 0 24 24` |
+| `title` | Provides a human-readable title for the element that contains it. [More Info](https://www.w3.org/TR/SVG-access/#Equivalent) | - |
+| `size` | The size of the icon. If set to `"auto"`, the icon will get the parent's width and height. | `"auto"` |
+
+
+
+## Explore Icons
+
+
diff --git a/docs/internals/components/DocIconGrid.ts b/docs/internals/components/DocIconGrid.ts
new file mode 100644
index 00000000..6926d1ad
--- /dev/null
+++ b/docs/internals/components/DocIconGrid.ts
@@ -0,0 +1,286 @@
+import icons from "@tapsioss/icons";
+import * as scrollLock from "body-scroll-lock";
+import { createFocusTrap, type FocusTrap } from "focus-trap";
+import { html, LitElement, nothing, svg, type PropertyValues } from "lit";
+import { customElement, query, state } from "lit/decorators.js";
+import { classMap } from "lit/directives/class-map.js";
+import { unsafeHTML } from "lit/directives/unsafe-html.js";
+
+import "vitepress/theme";
+import {
+ getFormattedImportUsageString,
+ getFormattedTagUsageString,
+ getUsageSectionMarkdown,
+} from "../../utils/markdown.ts";
+
+type SVGPathInfo = {
+ clipRule?: string;
+ fillRule?: string;
+ xlinkHref?: string;
+ d: string;
+};
+
+type SVGIconInfo = {
+ kebabName: string;
+ pascalName: string;
+ paths: SVGPathInfo[];
+};
+
+@customElement("doc-icon-grid")
+export class DocIconGrid extends LitElement {
+ protected override createRenderRoot() {
+ return this;
+ }
+
+ @state()
+ _searchString: string = "";
+
+ @state()
+ _showModal: boolean = false;
+
+ @state()
+ _selectedIcon: null | SVGIconInfo = null;
+
+ @query("#icon-modal")
+ private _iconModal!: HTMLElement;
+
+ private _focusTrapper: null | FocusTrap = null;
+
+ private _handleInputChange(e: Event) {
+ this._searchString = (e.target as HTMLInputElement).value;
+ }
+
+ private _normalizeStringFiltering(inputString: string): string {
+ return inputString.toLowerCase().replace(/-/g, "").replace(/_/g, "");
+ }
+
+ public override disconnectedCallback() {
+ super.disconnectedCallback();
+
+ scrollLock.enableBodyScroll(this._iconModal);
+
+ this._focusTrapper?.deactivate();
+ this._focusTrapper = null;
+
+ window.removeEventListener("keydown", this._handleEscapeKey);
+ }
+
+ protected override firstUpdated(_changedProperties: PropertyValues) {
+ super.firstUpdated(_changedProperties);
+
+ this._focusTrapper = createFocusTrap(this._iconModal, {
+ clickOutsideDeactivates: true,
+ });
+
+ window.addEventListener("keydown", this._handleEscapeKey);
+ }
+
+ private _handleEscapeKey = (e: KeyboardEvent) => {
+ if (e.key === "Escape") {
+ e.preventDefault();
+ this._closeModal();
+ }
+ };
+
+ private _closeModal = () => {
+ this._showModal = false;
+ this._selectedIcon = null;
+ scrollLock.enableBodyScroll(this._iconModal);
+ this._focusTrapper?.deactivate();
+ };
+
+ private _openModal = (icon: SVGIconInfo) => {
+ this._showModal = true;
+ this._selectedIcon = icon;
+ scrollLock.disableBodyScroll(this._iconModal);
+ this._focusTrapper?.activate();
+ };
+
+ private _getSvg(iconPaths?: SVGPathInfo[]) {
+ if (!iconPaths) return;
+ return html`
+
+ ${iconPaths.map(({ d, clipRule, fillRule }) => {
+ return svg` `;
+ })}
+
+ `;
+ }
+
+ private _renderIconUsageSection() {
+ if (!this._selectedIcon) return null;
+
+ return unsafeHTML(
+ getUsageSectionMarkdown([
+ [
+ "Import",
+ getFormattedImportUsageString(
+ `@tapsioss/web-icons/${this._selectedIcon.kebabName}`,
+ ),
+ ],
+ [
+ "Tag",
+ getFormattedTagUsageString(
+ `tapsi-icon-${this._selectedIcon.kebabName}`,
+ ),
+ ],
+ ]),
+ );
+ }
+
+ private _renderModal() {
+ return html`
+
+ e.stopPropagation()}
+ >
+
+
+
+ Usage
+
+ ${this._renderIconUsageSection()}
+
+ Preview
+
+
+ ${this._getSvg(this._selectedIcon?.paths)}
+
+
+
`;
+ }
+
+ private _renderSearchIcon() {
+ return html`
+
+ `;
+ }
+
+ private _renderSearchbar() {
+ return html``;
+ }
+
+ private _shouldShowIconInGrid(icon: SVGIconInfo) {
+ // Do not apply the filter if the user typed a single-character string in search input.
+ if (this._searchString.length < 2) return true;
+
+ return this._normalizeStringFiltering(icon.kebabName).includes(
+ this._normalizeStringFiltering(this._searchString),
+ );
+ }
+
+ private _renderIcons() {
+ const filteredIcons = Object.values(
+ icons as Record,
+ ).filter(icon => this._shouldShowIconInGrid(icon));
+
+ if (filteredIcons.length > 0) {
+ return filteredIcons.map(icon => {
+ return html` this._openModal(icon)}
+ >
+ ${this._getSvg(icon.paths)}
+ `;
+ });
+ }
+
+ return html`No icon was found for "${this._searchString}"!
`;
+ }
+
+ protected override render() {
+ return html`
+ ${this._renderModal()}${this._renderSearchbar()}
+ ${this._renderIcons()}
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "doc-icon-grid": DocIconGrid;
+ }
+}
diff --git a/docs/package.json b/docs/package.json
index a0775454..841c4629 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -11,6 +11,11 @@
"dependencies": {
"@tapsioss/theme": "workspace:*",
"@tapsioss/web-components": "workspace:*",
- "@tapsioss/web-icons": "workspace:*"
+ "@tapsioss/icons": "workspace:*",
+ "body-scroll-lock": "4.0.0-beta.0",
+ "focus-trap": "^7.6.4"
+ },
+ "devDependencies": {
+ "@types/body-scroll-lock": "^3.1.2"
}
}
diff --git a/docs/sidebar.ts b/docs/sidebar.ts
index 4af42288..e9b7deb5 100644
--- a/docs/sidebar.ts
+++ b/docs/sidebar.ts
@@ -10,18 +10,10 @@ const workspaceDir = path.join(dirname, "..");
const distDir = path.join(workspaceDir, "dist");
const metadataFile = path.join(distDir, "components-metadata.json");
-const getComponentsSidebarItems = (): DefaultTheme.SidebarItem[] =>
- (JSON.parse(fs.readFileSync(metadataFile).toString("utf-8")) as Metadata)
- .sidebarItems;
-
-const getComponentsSidebar = (): DefaultTheme.Sidebar => {
- return [
- {
- text: "Components",
- collapsed: false,
- items: getComponentsSidebarItems(),
- },
- ];
+const getSidebarItems = (): DefaultTheme.Sidebar => {
+ return (
+ JSON.parse(fs.readFileSync(metadataFile).toString("utf-8")) as Metadata
+ ).sidebarItems;
};
-export default getComponentsSidebar();
+export default getSidebarItems();
diff --git a/docs/utils/markdown.ts b/docs/utils/markdown.ts
new file mode 100644
index 00000000..d7ced87d
--- /dev/null
+++ b/docs/utils/markdown.ts
@@ -0,0 +1,39 @@
+export const tabulateData = (headers: string[], data: string[][]): string => {
+ if (headers.length === 0) return "";
+ if (data.length === 0 || data[0]!.length === 0) return "";
+
+ const headersMarkdown = `| ${headers.join(" | ")} |`;
+ const separator = `| ${headers.map(() => "---").join(" | ")} |`;
+
+ const rowsMarkdown = data.map(row => `| ${row.join(" | ")} |`).join("\n");
+
+ return `\n\n\n${headersMarkdown}\n${separator}\n${rowsMarkdown}\n\n
\n`;
+};
+
+export const codify = (inputString: string) => `\`${inputString}\``;
+
+export const getFormattedTagUsageString = (tagName?: string) => {
+ if (!tagName) return "";
+ return `<${tagName} ></${tagName} > `;
+};
+
+export const getFormattedImportUsageString = (path?: string) => {
+ if (!path) return "";
+ return `import "${path}" ; `;
+};
+
+export const getUsageSectionMarkdown = (sections: [string, string?][]) => {
+ let res = "";
+
+ if (sections.length === 0) return res;
+
+ res += "\n\n";
+
+ sections.forEach(([sectionName, sectionValue]) => {
+ res += `${sectionName} ${sectionValue || "-"} `;
+ });
+
+ res += "\n \n";
+
+ return res;
+};
diff --git a/docs/utils/md.ts b/docs/utils/md.ts
deleted file mode 100644
index a05c0d95..00000000
--- a/docs/utils/md.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-export const tabulateData = (headers: string[], data: string[][]): string => {
- if (headers.length === 0) return "";
- if (data.length === 0 || data[0]!.length === 0) return "";
-
- const headersMarkdown = `| ${headers.join(" | ")} |`;
- const separator = `| ${headers.map(() => "---").join(" | ")} |`;
-
- const rowsMarkdown = data.map(row => `| ${row.join(" | ")} |`).join("\n");
-
- return `\n\n\n${headersMarkdown}\n${separator}\n${rowsMarkdown}\n\n
\n`;
-};
-
-export const codify = (inputString: string) => `\`${inputString}\``;
diff --git a/internals/doc-helpers/types.ts b/internals/doc-helpers/types.ts
index ccb6b80e..9be7ba7a 100644
--- a/internals/doc-helpers/types.ts
+++ b/internals/doc-helpers/types.ts
@@ -31,34 +31,6 @@ export type InteractiveDemo = {
slots: DemoSlot[];
};
-export type ComponentSlot = {
- name: string;
- description: string;
-};
-
-export type ComponentMember = {
- type: {
- text: string;
- };
- description?: string;
- name: string;
- default?: string;
- kind: string;
-};
-
-export type ComponentEvent = {
- type?: {
- text: string;
- };
- description?: string;
- name: string;
-};
-
-export type componentSuperclass = {
- name: string;
- module: string;
-};
-
export type ImportPaths = {
webComponents?: string;
};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5b836c5d..e6eb972a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -170,15 +170,25 @@ importers:
docs:
dependencies:
+ '@tapsioss/icons':
+ specifier: workspace:*
+ version: link:../packages/icons
'@tapsioss/theme':
specifier: workspace:*
version: link:../packages/theme
'@tapsioss/web-components':
specifier: workspace:*
version: link:../packages/web-components
- '@tapsioss/web-icons':
- specifier: workspace:*
- version: link:../packages/web-icons
+ body-scroll-lock:
+ specifier: 4.0.0-beta.0
+ version: 4.0.0-beta.0
+ focus-trap:
+ specifier: ^7.6.4
+ version: 7.6.4
+ devDependencies:
+ '@types/body-scroll-lock':
+ specifier: ^3.1.2
+ version: 3.1.2
packages/icons: {}
@@ -1222,6 +1232,9 @@ packages:
'@types/body-parser@1.19.5':
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
+ '@types/body-scroll-lock@3.1.2':
+ resolution: {integrity: sha512-ELhtuphE/YbhEcpBf/rIV9Tl3/O0A0gpCVD+oYFSS8bWstHFJUgA4nNw1ZakVlRC38XaQEIsBogUZKWIPBvpfQ==}
+
'@types/chai-dom@1.11.3':
resolution: {integrity: sha512-EUEZI7uID4ewzxnU7DJXtyvykhQuwe+etJ1wwOiJyQRTH/ifMWKX+ghiXkxCUvNJ6IQDodf0JXhuP6zZcy2qXQ==}
@@ -1756,6 +1769,9 @@ packages:
birpc@0.2.19:
resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==}
+ body-scroll-lock@4.0.0-beta.0:
+ resolution: {integrity: sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==}
+
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
@@ -2371,6 +2387,9 @@ packages:
focus-trap@7.6.4:
resolution: {integrity: sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==}
+ focus-trap@7.6.4:
+ resolution: {integrity: sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==}
+
for-each@0.3.4:
resolution: {integrity: sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw==}
engines: {node: '>= 0.4'}
@@ -4948,6 +4967,8 @@ snapshots:
'@types/connect': 3.4.38
'@types/node': 20.17.16
+ '@types/body-scroll-lock@3.1.2': {}
+
'@types/chai-dom@1.11.3':
dependencies:
'@types/chai': 4.3.20
@@ -5692,6 +5713,8 @@ snapshots:
birpc@0.2.19: {}
+ body-scroll-lock@4.0.0-beta.0: {}
+
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
@@ -6471,6 +6494,10 @@ snapshots:
flatted@3.3.2: {}
+ focus-trap@7.6.4:
+ dependencies:
+ tabbable: 6.2.0
+
focus-trap@7.6.4:
dependencies:
tabbable: 6.2.0
diff --git a/scripts/generate-docs-metadata.ts b/scripts/generate-docs-metadata.ts
index efc302c1..4dc2dc52 100644
--- a/scripts/generate-docs-metadata.ts
+++ b/scripts/generate-docs-metadata.ts
@@ -20,9 +20,9 @@ const webComponentsSrcDir = path.join(
"packages/web-components/src",
);
-const distDir = path.join(workspaceDir, "dist");
-const metadataFile = path.join(distDir, "components-metadata.json");
-const cemFile = path.join(distDir, "custom-elements.json");
+const workspaceDistDir = path.join(workspaceDir, "dist");
+const metadataFile = path.join(workspaceDistDir, "components-metadata.json");
+const cemFile = path.join(workspaceDistDir, "custom-elements.json");
const cem = JSON.parse(fs.readFileSync(cemFile, "utf8")) as Package;
@@ -35,7 +35,7 @@ const getKebabCaseComponentName = (component: Declaration) => {
};
void (() => {
- console.log("🧩 generating components metadata...");
+ console.log("🧩 generating docs metadata...");
const sidebarItemsMap: Record = {};
const components: Component[] = [];
@@ -109,7 +109,7 @@ void (() => {
sidebarItem.text = childPath ?? relativePath;
if (declarations.length === 1) {
- sidebarItem.link = `components/${kebabCaseName}`;
+ sidebarItem.link = `/components/${kebabCaseName}`;
} else {
if (!Array.isArray(sidebarItem.items)) {
sidebarItem.items = [];
@@ -120,7 +120,7 @@ void (() => {
return {
text: name,
- link: `components/${name}`,
+ link: `/components/${name}`,
};
});
}
@@ -148,20 +148,32 @@ void (() => {
});
});
+ const componentSidebarItems: DefaultTheme.SidebarItem = {
+ text: "Components",
+ items: Object.values(sidebarItemsMap).sort((a, b) =>
+ a.text!.localeCompare(b.text!),
+ ),
+ };
+
+ const iconsSidebarItem: DefaultTheme.SidebarItem = {
+ text: "Icons",
+ link: "/icons",
+ };
+
+ const sidebarItems = [iconsSidebarItem, componentSidebarItems];
+
fs.writeFileSync(
metadataFile,
JSON.stringify(
{
components,
- sidebarItems: Object.values(sidebarItemsMap).sort((a, b) =>
- a.text!.localeCompare(b.text!),
- ),
+ sidebarItems,
},
null,
2,
),
);
- console.log("✅ components metadata generated.");
+ console.log("✅ docs metadata generated.");
})();
/* eslint-enable no-console */