From 4affaa2cc3fe174e1acbd61676bab38fc6f96916 Mon Sep 17 00:00:00 2001 From: Nayden Naydenov Date: Tue, 10 Feb 2026 14:16:55 +0200 Subject: [PATCH 1/3] chore: add illustration explorer --- packages/website/.gitignore | 2 + .../illustrations-generation/index.mjs | 184 +++++++++++ packages/website/docusaurus.config.ts | 5 + packages/website/package.json | 3 +- packages/website/src/pages/illustrations.css | 281 +++++++++++++++++ packages/website/src/pages/illustrations.tsx | 286 ++++++++++++++++++ 6 files changed, 760 insertions(+), 1 deletion(-) create mode 100644 packages/website/build-scripts/illustrations-generation/index.mjs create mode 100644 packages/website/src/pages/illustrations.css create mode 100644 packages/website/src/pages/illustrations.tsx diff --git a/packages/website/.gitignore b/packages/website/.gitignore index 206f62ddd8a7..b06fbd4400a0 100644 --- a/packages/website/.gitignore +++ b/packages/website/.gitignore @@ -16,6 +16,8 @@ src/components/Editor/ui5-autocomplete.json /icons /icons-tnt /icons-business-suite +/illustrations +/illustrations-tnt static/packages static/assets diff --git a/packages/website/build-scripts/illustrations-generation/index.mjs b/packages/website/build-scripts/illustrations-generation/index.mjs new file mode 100644 index 000000000000..04fe6937097c --- /dev/null +++ b/packages/website/build-scripts/illustrations-generation/index.mjs @@ -0,0 +1,184 @@ +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "node:url"; + +const SAPIllustrationsConfig = { + title: "SAP Illustrations", + npmLink: "https://www.npmjs.com/package/@ui5/webcomponents-fiori", + npmPackage: "@ui5/webcomponents-fiori", + dir: "illustrations", + componentName: "SAPIllustrations", + subfolder: "", // No subfolder for SAP illustrations +}; + +const SAPTNTIllustrationsConfig = { + title: "SAP TNT Illustrations", + npmLink: "https://www.npmjs.com/package/@ui5/webcomponents-fiori", + npmPackage: "@ui5/webcomponents-fiori", + dir: "illustrations-tnt", + componentName: "SAPTNTIllustrations", + subfolder: "tnt/", // TNT illustrations are in tnt/ subfolder +}; + +const capitalize = (str) => { + const firstLetter = str.charAt(0); + const firstLetterCap = firstLetter.toUpperCase(); + const remainingLetters = str.slice(1); + const capitalizedWord = firstLetterCap + remainingLetters; + return capitalizedWord; +}; + +const writeFile = (targetDir, content) => { + const targetPath = path.resolve(`./${targetDir}`); + const targetFile = path.resolve(`${targetPath}/index.js`); + + if (!fs.existsSync(targetPath)) { + fs.mkdirSync(targetPath, { recursive: true }); + } + fs.writeFileSync(targetFile, content, { encoding: 'utf8', flag: 'w' }); +}; + +const commonImports = ` +import React, { useState } from 'react'; +import clsx from "clsx"; +import Heading from '@theme/Heading'; +import Link from '@docusaurus/Link'; +`; + +const additionalImports = ``; + +/** + * Parse the IllustrationMessageType enum and extract non-deprecated illustrations + */ +const parseIllustrationsFromEnum = () => { + const enumPath = path.join( + findRoot("@ui5/webcomponents-fiori"), + "src/types/IllustrationMessageType.ts" + ); + + const enumContent = fs.readFileSync(enumPath, 'utf8'); + + const illustrations = []; + + // Extract enum entries with their JSDoc comments + const enumEntryRegex = /\/\*\*([\s\S]*?)\*\/\s*(\w+)\s*=\s*"(\w+)"/g; + let match; + + while ((match = enumEntryRegex.exec(enumContent)) !== null) { + const [, jsdoc, enumKey, enumValue] = match; + const isDeprecated = jsdoc.includes('@deprecated'); + + if (!isDeprecated) { + illustrations.push({ + name: enumValue.startsWith('Tnt') ? enumValue.replace(/^Tnt/, '') : enumValue, + displayName: enumValue, // Full enum name for display + isTnt: enumValue.startsWith('Tnt') + }); + } + } + + return illustrations; +}; + +const _generateIllustrationsPage = (illustrations, config) => { + let imports = ``; + let illustrationCards = ``; + let illustrationDataArray = ``; + + illustrations.forEach(illustration => { + const illustrationName = illustration.name; + const illustrationNameImportName = `${illustrationName}`; + + imports += ` +import ${illustrationNameImportName} from "${config.npmPackage}/dist/illustrations/${config.subfolder}${illustrationName}.js"; +import { spotSvg as ${illustrationName}SpotSvg } from "${config.npmPackage}/dist/illustrations/${config.subfolder}${illustrationName}.js"; +`; + + illustrationCards += ` + {isVisible("${illustrationName}") && ( +
onIllustrationSelect(illustrations.find(item => item.name === "${illustrationName}"))}> + +
+ + {illustrations.find(item => item.name === "${illustrationName}")?.displayName || "${illustrationName}"} +
+ )}`; + + illustrationDataArray += ` + { name: "${illustrationName}", displayName: "${illustration.displayName}", isTnt: ${illustration.isTnt} },`; + }); + + const classDef = `export default function ${config.componentName}({ onIllustrationSelect, selectedIllustration, searchQuery = "" }) { + const illustrations = [${illustrationDataArray} + ]; + + // Filter based on search query + const filteredIllustrations = searchQuery + ? illustrations.filter(item => item.displayName.toLowerCase().includes(searchQuery.toLowerCase())) + : illustrations; + + // Check if each illustration should be visible + const isVisible = (illustrationName) => { + return filteredIllustrations.some(item => item.name === illustrationName); + }; + + return ( +
+
+ ${illustrationCards} +
+ {filteredIllustrations.length === 0 && ( +
+

No matching illustrations found

+
+ )} +
+ ); + } + +export const illustrationsData = [${illustrationDataArray} +];`; + + return { imports, classDef }; +}; + +const generateIllustrationsPage = (illustrations, config) => { + const { imports, classDef } = _generateIllustrationsPage(illustrations, config); + + const content = ` +${commonImports} +${additionalImports} +${imports} +${classDef}`; + + writeFile(config.dir, content); +}; + +function findRoot(pkgName) { + return path.dirname(fileURLToPath(import.meta.resolve(`${pkgName}/package.json`))); +} + +// Main execution +const allIllustrations = parseIllustrationsFromEnum(); + +// Split into SAP and TNT collections +const sapIllustrations = allIllustrations.filter(ill => !ill.isTnt); +const tntIllustrations = allIllustrations.filter(ill => ill.isTnt); + +console.log(`Found ${sapIllustrations.length} SAP illustrations (non-deprecated)`); +console.log(`Found ${tntIllustrations.length} TNT illustrations (non-deprecated)`); + +generateIllustrationsPage(sapIllustrations, SAPIllustrationsConfig); +generateIllustrationsPage(tntIllustrations, SAPTNTIllustrationsConfig); + +console.log("Illustrations pages generated successfully!"); diff --git a/packages/website/docusaurus.config.ts b/packages/website/docusaurus.config.ts index 2a41300248cf..df26e0607c04 100644 --- a/packages/website/docusaurus.config.ts +++ b/packages/website/docusaurus.config.ts @@ -163,6 +163,11 @@ const config: Config = { label: 'Icons', activeBasePath: 'icons', }, + { + to: 'illustrations/', + label: 'Illustrations', + activeBasePath: 'illustrations', + }, { to: 'play/', label: 'Playground', diff --git a/packages/website/package.json b/packages/website/package.json index 16c2e84844fe..5cf7cd192d5e 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -7,7 +7,8 @@ "generate-api-reference": "rimraf ./docs/components/fiori && rimraf ./docs/components/main && rimraf ./docs/components/compat && rimraf ./docs/components/ai && node ./build-scripts/api-reference-generation/index.mjs", "generate-documentation": "rimraf ./docs/docs && node ./build-scripts/documentation-generation/index.mjs", "generate-icons": "rimraf ./icons && rimraf ./icons-tnt && rimraf ./icons-business-suite && node ./build-scripts/icons-generation/index.mjs", - "generate-local-env": "yarn generate-api-reference && yarn generate-documentation && yarn generate-icons", + "generate-illustrations": "rimraf ./illustrations && rimraf ./illustrations-tnt && node ./build-scripts/illustrations-generation/index.mjs", + "generate-local-env": "yarn generate-api-reference && yarn generate-documentation && yarn generate-icons && yarn generate-illustrations", "generate-production-env": "yarn generate-local-env && rimraf ./static/pages && rimraf ./static/assets && yarn copy:pages:compat && yarn copy:pages:ai && yarn copy:pages:fiori && yarn copy:pages:main", "docusaurus": "docusaurus", "start": "yarn generate-local-cdn && yarn generate-local-env && docusaurus start", diff --git a/packages/website/src/pages/illustrations.css b/packages/website/src/pages/illustrations.css new file mode 100644 index 000000000000..b9f89e94ea42 --- /dev/null +++ b/packages/website/src/pages/illustrations.css @@ -0,0 +1,281 @@ +:root { + --sapIllus_BrandColorPrimary: var(--sapContent_Illustrative_Color1); + --sapIllus_BrandColorSecondary: var(--sapContent_Illustrative_Color2); + --sapIllus_StrokeDetailColor: var(--sapContent_Illustrative_Color4); + --sapIllus_Layering1: var(--sapContent_Illustrative_Color5); + --sapIllus_Layering2: var(--sapContent_Illustrative_Color6); + --sapIllus_BackgroundColor: var(--sapContent_Illustrative_Color7); + --sapIllus_ObjectFillColor: var(--sapContent_Illustrative_Color8); + --sapIllus_AccentColor: var(--sapContent_Illustrative_Color3); + --sapIllus_NoColor: none; + --sapIllus_PatternShadow: url(#sapIllus_PatternShadow); + --sapIllus_PatternHighlight: url(#sapIllus_PatternHighlight); +} + +/* Main container */ +.illustrations__container { + width: 100%; + height: calc(100vh - var(--ifm-navbar-height)); + display: flex; + flex-direction: column; +} + +/* Header with collection switcher and search */ +.illustrations__header { + padding: 1rem 2rem; + border-bottom: 1px solid var(--ifm-color-emphasis-300); + background-color: var(--ifm-background-surface-color); + display: flex; + align-items: center; + justify-content: space-between; + gap: 2rem; + flex-wrap: wrap; +} + +/* Split-pane layout */ +.illustrations__split-pane { + display: flex; + flex: 1; + overflow: hidden; +} + +.illustrations__left-pane { + width: 40%; + overflow-y: auto; + border-right: 1px solid var(--ifm-color-emphasis-300); + background-color: var(--ifm-background-color); +} + +.illustrations__right-pane { + width: 60%; + overflow-y: auto; + background-color: var(--ifm-background-surface-color); + padding: 2rem; +} + +/* Grid for left pane thumbnails */ +.illustration__grid { + display: grid; + padding: 1rem; + gap: 1rem; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); +} + +/* Simplified thumbnail card */ +.illustration__wrapper { + position: relative; + display: flex; + align-items: center; + flex-direction: column; + padding: 0.75rem 0.5rem; + background-color: var(--ifm-background-surface-color); + box-shadow: rgba(0, 0, 0, 0.15) 0px 1.5px 3px 0px; + border: 2px solid transparent; + border-radius: 0.5rem; + cursor: pointer; + gap: 0.5rem; + transition: all 0.2s; +} + +.illustration__wrapper:hover { + background-color: var(--ifm-menu-color-background-hover); + border-color: var(--ifm-color-primary-light); +} + +.illustration__wrapper--selected { + border-color: var(--ifm-color-primary); + background-color: var(--ifm-menu-color-background-active); +} + +/* Small thumbnail preview */ +.illustration__preview { + width: 100%; + max-width: 80px; + height: auto; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + min-height: 60px; +} + +.illustration__preview svg { + width: 100%; + height: auto; + max-height: 80px; +} + +.illustration__wrapper__title { + color: var(--ifm-font-color-base); + text-align: center; + word-break: break-word; + font-size: 0.75rem; + width: 100%; + line-height: 1.2; +} + +/* Right pane preview panel */ +.illustration__preview-panel { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.illustration__preview-header { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 1rem; + border-bottom: 1px solid var(--ifm-color-emphasis-300); +} + +.illustration__preview-header h2 { + margin: 0; + font-size: 1.5rem; + color: var(--ifm-font-color-base); +} + +.illustration__preview-sizes { + display: flex; + flex-direction: column; + gap: 2rem; +} + +.illustration__preview-size { + padding: 1.5rem; + background-color: var(--ifm-background-color); + border-radius: 0.5rem; + border: 1px solid var(--ifm-color-emphasis-200); +} + +.illustration__preview-size h3 { + margin: 0 0 1rem 0; + font-size: 1rem; + color: var(--ifm-color-primary); + font-weight: 600; +} + +/* ui5-illustrated-message styling */ +.illustration__preview-size ui5-illustrated-message { + display: block; + width: 100%; +} + +/* Welcome message styling */ +.illustration__welcome { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + padding: 2rem; +} + +.illustration__welcome-content { + max-width: 600px; + text-align: center; +} + +.illustration__welcome-content h2 { + margin-bottom: 1rem; + color: var(--ifm-font-color-base); +} + +.illustration__welcome-content p { + margin-bottom: 1.5rem; + color: var(--ifm-color-content-secondary); + font-size: 1.1rem; +} + +.illustration__welcome-content ul { + text-align: left; + list-style: none; + padding: 0; +} + +.illustration__welcome-content ul li { + padding: 0.5rem 0; + color: var(--ifm-font-color-base); + position: relative; + padding-left: 1.5rem; +} + +.illustration__welcome-content ul li:before { + content: "→"; + position: absolute; + left: 0; + color: var(--ifm-color-primary); +} + +/* Search input */ +.illustrations__search { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat 0.75rem center / 1rem 1rem; + border: none; + border-radius: 2rem; + color: var(--ifm-navbar-search-input-color); + cursor: text; + display: inline-block; + font-size: 0.9rem; + height: 2.5rem; + padding: 0 0.5rem 0 2.25rem; + width: 12.5rem; +} + +/* Segmented button */ +.segmented__button { + display: inline-flex; + align-items: center; + justify-content: center; + background-color: var(--ifm-background-color); + padding: 0.5rem; + border-radius: 0.5rem; +} + +.segmented__button__item { + padding: 0.5rem 1rem; + margin: 0 0.125rem; + border-radius: 0.5rem; + color: var(--ifm-navbar-link-color); + font-weight: 500; +} + +.segmented__button__item:hover { + cursor: pointer; + transition: all 0.2s; + background-color: var(--ifm-menu-color-background-hover); +} + +.segmented__button__item--active { + color: var(--ifm-menu-color-active); + background-color: var(--ifm-menu-color-background-active); +} + +/* Responsive: Stack panes on mobile */ +@media (max-width: 996px) { + .illustrations__split-pane { + flex-direction: column; + } + + .illustrations__left-pane, + .illustrations__right-pane { + width: 100%; + border: none; + } + + .illustrations__right-pane { + border-top: 1px solid var(--ifm-color-emphasis-300); + } + + .illustrations__header { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } +} + +/* Utility classes */ +.hidden { + display: none !important; +} diff --git a/packages/website/src/pages/illustrations.tsx b/packages/website/src/pages/illustrations.tsx new file mode 100644 index 000000000000..5914267ae894 --- /dev/null +++ b/packages/website/src/pages/illustrations.tsx @@ -0,0 +1,286 @@ +import React, { useState, useEffect } from 'react'; +import clsx from "clsx"; +import Layout from '@theme/Layout'; +import BrowserOnly from '@docusaurus/BrowserOnly'; +import "./illustrations.css"; +import "@sap-theming/theming-base-content/content/Base/baseLib/sap_horizon/css_variables.css"; + +// Import generated components and data +import SAPIllustrations, { illustrationsData as sapData } from "../../illustrations/index.js"; +import SAPTNTIllustrations, { illustrationsData as tntData } from "../../illustrations-tnt/index.js"; + +// IllustratedMessage preview component +function IllustrationPreview({ name, displayName, collection }) { + const [copied, setCopied] = useState(false); + + useEffect(() => { + // Dynamically import UI5 components (SSR-safe) + if (typeof window !== 'undefined') { + import("@ui5/webcomponents-fiori/dist/IllustratedMessage.js"); + import("@ui5/webcomponents-fiori/dist/illustrations/AllIllustrations.js"); + } + }, []); + + const illustrationPath = collection === "SAP" + ? `@ui5/webcomponents-fiori/dist/illustrations/${name}.js` + : `@ui5/webcomponents-fiori/dist/illustrations/tnt/${name}.js`; + + const handleCopyImport = () => { + navigator.clipboard.writeText(`import "${illustrationPath}";`); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( +
+
+

{displayName}

+ +
+ +
+
+

Extra Small (Dot)

+ +
+ +
+

Small (Spot)

+ +
+ +
+

Medium (Dialog)

+ +
+ +
+

Large (Scene)

+ +
+
+
+ ); +} + +// Welcome message shown when no illustration is selected +function WelcomeMessage() { + return ( +
+
+

Welcome to UI5 Illustrations

+

+ Browse and explore SAP Fiori and TNT illustrations for your UI5 applications. +

+
    +
  • Select an illustration from the grid to preview all size variants
  • +
  • Use the search box to filter illustrations by name
  • +
  • Toggle between SAP and TNT illustration collections
  • +
  • Copy the import statement to use in your project
  • +
+
+
+ ); +} + +// Main illustrations page component +function IllustrationsContent() { + const [collection, setCollection] = useState("SAP"); + const [selectedIllustration, setSelectedIllustration] = useState(null); + const [searchQuery, setSearchQuery] = useState(""); + + // Load UI5 components and handle URL hash on INITIAL MOUNT only (auto-switch collection) + useEffect(() => { + // Load UI5 components + if (typeof window !== 'undefined') { + import("@ui5/webcomponents-fiori/dist/IllustratedMessage.js"); + import("@ui5/webcomponents-fiori/dist/illustrations/AllIllustrations.js"); + } + + // Check URL hash for pre-selected illustration (auto-switch collection) + if (typeof window !== 'undefined') { + const hash = window.location.hash.slice(1); // Remove '#' prefix + if (hash) { + // Search BOTH collections to find the illustration + let illustration = sapData.find(item => item.displayName === hash); + let targetCollection = "SAP"; + + if (!illustration) { + illustration = tntData.find(item => item.displayName === hash); + targetCollection = "TNT"; + } + + if (illustration) { + // Auto-switch collection if needed + if (targetCollection !== "SAP") { + setCollection(targetCollection); + } + setSelectedIllustration(illustration); + } else { + // Hash doesn't exist in any collection, clear selection + setSelectedIllustration(null); + } + } else { + // No hash, clear selection + setSelectedIllustration(null); + } + } + }, []); // Empty dependency array - run only on mount + + // Handle manual tab switching - check if current hash belongs to new collection + useEffect(() => { + if (typeof window !== 'undefined') { + const hash = window.location.hash.slice(1); + if (hash) { + const currentData = collection === "SAP" ? sapData : tntData; + const illustration = currentData.find(item => item.displayName === hash); + if (illustration) { + // Hash exists in the new collection, select it + setSelectedIllustration(illustration); + } else { + // Hash doesn't exist in the new collection, clear selection + setSelectedIllustration(null); + } + } else { + setSelectedIllustration(null); + } + } + }, [collection]); // Run when collection changes (manual tab switch) + + // Handle browser back/forward navigation + useEffect(() => { + const handleHashChange = () => { + if (typeof window === 'undefined') return; + + const hash = window.location.hash.slice(1); + if (hash) { + // Search BOTH collections to find the illustration + let illustration = sapData.find(item => item.displayName === hash); + let targetCollection = "SAP"; + + if (!illustration) { + illustration = tntData.find(item => item.displayName === hash); + targetCollection = "TNT"; + } + + if (illustration) { + // Auto-switch collection if needed + if (collection !== targetCollection) { + setCollection(targetCollection); + } + setSelectedIllustration(illustration); + } + } else { + setSelectedIllustration(null); + } + }; + + if (typeof window !== 'undefined') { + window.addEventListener('hashchange', handleHashChange); + return () => window.removeEventListener('hashchange', handleHashChange); + } + }, [collection]); + + // Wrapper function that updates URL hash when illustration is selected + const handleIllustrationSelect = (illustration) => { + setSelectedIllustration(illustration); + if (typeof window !== 'undefined') { + window.history.pushState(null, '', `#${illustration.displayName}`); + } + }; + + const currentData = collection === "SAP" ? sapData : tntData; + + return ( +
+ {/* Header with collection switcher and search */} +
+
+
setCollection("SAP")} + className={clsx("segmented__button__item", { + 'segmented__button__item--active': collection === "SAP" + })} + >SAP Illustrations
+
setCollection("TNT")} + className={clsx("segmented__button__item", { + 'segmented__button__item--active': collection === "TNT" + })} + >SAP TNT Illustrations
+
+ + setSearchQuery(e.target.value)} + /> +
+ + {/* Split pane layout */} +
+ {/* Left pane: Grid of thumbnails */} +
+ {collection === "SAP" ? ( + + ) : ( + + )} +
+ + {/* Right pane: IllustratedMessage preview or welcome message */} +
+ {selectedIllustration ? ( + + {() => ( + + )} + + ) : ( + + )} +
+
+
+ ); +} + +export default function Illustrations() { + return ( + + + + ); +} From 5e90907e1ef56561ed503a60ca51903997ced58b Mon Sep 17 00:00:00 2001 From: Nayden Naydenov Date: Tue, 10 Feb 2026 14:34:24 +0200 Subject: [PATCH 2/3] chore: fix build --- packages/website/src/pages/illustrations.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/website/src/pages/illustrations.tsx b/packages/website/src/pages/illustrations.tsx index 5914267ae894..b22e2121f8f7 100644 --- a/packages/website/src/pages/illustrations.tsx +++ b/packages/website/src/pages/illustrations.tsx @@ -3,7 +3,6 @@ import clsx from "clsx"; import Layout from '@theme/Layout'; import BrowserOnly from '@docusaurus/BrowserOnly'; import "./illustrations.css"; -import "@sap-theming/theming-base-content/content/Base/baseLib/sap_horizon/css_variables.css"; // Import generated components and data import SAPIllustrations, { illustrationsData as sapData } from "../../illustrations/index.js"; From 96d180f56fb2c3dfe825e852dc641422f5ca0827 Mon Sep 17 00:00:00 2001 From: Nayden Naydenov Date: Tue, 10 Feb 2026 15:55:22 +0200 Subject: [PATCH 3/3] chore: align icon and illustration pages --- .../build-scripts/icons-generation/index.mjs | 24 +--- packages/website/src/pages/icons.css | 114 +++++++++------ packages/website/src/pages/icons.tsx | 132 ++++++++++++++---- packages/website/src/pages/illustrations.css | 49 +------ packages/website/src/pages/illustrations.tsx | 3 +- packages/website/src/pages/shared.css | 47 +++++++ 6 files changed, 230 insertions(+), 139 deletions(-) create mode 100644 packages/website/src/pages/shared.css diff --git a/packages/website/build-scripts/icons-generation/index.mjs b/packages/website/build-scripts/icons-generation/index.mjs index a818c9252b10..ad202eae8f05 100644 --- a/packages/website/build-scripts/icons-generation/index.mjs +++ b/packages/website/build-scripts/icons-generation/index.mjs @@ -131,29 +131,7 @@ const _generateIconsPage = (sourceDir, config) => { const classDef = `export default function ${config.componentName}() { return ( -
-
-
- ${config.title} - ${config.npmPackage} -
-
- - { - const iconName = iconWrapper.getAttribute("data-icon-name").toLowerCase(); - iconWrapper.classList.toggle("hidden", !iconName.includes(e.target.value)) - }) - - document - .querySelector(".icon__not__found") - .classList - .toggle("hidden", ![...document.querySelectorAll("[data-icon-name]")].every(iconWrapper => iconWrapper.classList.contains("hidden"))) - }} /> -
-
+
${icons}
diff --git a/packages/website/src/pages/icons.css b/packages/website/src/pages/icons.css index b47041a7a832..47c9a6bf9fa5 100644 --- a/packages/website/src/pages/icons.css +++ b/packages/website/src/pages/icons.css @@ -1,6 +1,72 @@ +@import "./shared.css"; + +/* Main container */ +.icons__container { + width: 100%; + min-height: calc(100vh - var(--ifm-navbar-height)); + display: flex; + flex-direction: column; +} + +/* Header with collection switcher and search */ +.icons__header { + padding: 1rem 2rem; + border-bottom: 1px solid var(--ifm-color-emphasis-300); + background-color: var(--ifm-background-surface-color); + display: flex; + align-items: center; + gap: 1.5rem; + flex-wrap: wrap; +} + +/* Collection metadata (title + package link) */ +.icons__header__metadata { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.125rem; + min-width: 200px; +} + +/* Collection metadata (title + package link) */ +.icons__header__separator { + flex: 1; +} + +.icons__header__title { + margin: 0; + font-size: 1.25rem; + font-weight: 600; + color: var(--ifm-font-color-base); +} + +.icons__header__package { + font-size: 0.875rem; + color: var(--ifm-color-primary); + text-decoration: none; +} + +.icons__header__package:hover { + text-decoration: underline; +} + +/* Responsive: Stack header elements on mobile */ +@media (max-width: 996px) { + .icons__header { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .icons__header__metadata { + align-items: flex-start; + width: 100%; + } +} + .icon__grid { display: grid; - padding: 2rem 0; + padding: 2rem; gap: 2rem; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); } @@ -15,21 +81,7 @@ justify-content: center; } -.icons__search { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background: var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat 0.75rem center / 1rem 1rem; - border: none; - border-radius: 2rem; - color: var(--ifm-navbar-search-input-color); - cursor: text; - display: inline-block; - font-size: 0.9rem; - height: 2.5rem; - padding: 0 0.5rem 0 2.25rem; - width: 12.5rem; -} +/* Search input styles moved to shared.css */ .icon__wrapper { position: relative; @@ -81,7 +133,7 @@ [data-theme='dark'] .icon__svg--picture { fill: var(--ifm-color-primary); } - + .icon__wrapper__title { color: var(--ifm-font-color-base); @@ -119,30 +171,4 @@ background-color: var(--ifm-background-surface-color); } - - -.segmented__button { - display: inline-flex; - align-items: center; - justify-content: center; - background-color: var(--ifm-background-surface-color); - padding: 0.5rem; -} - -.segmented__button__item { - padding: 0.25rem 0.5rem; - margin: 0 0.125rem; - border-radius: 0.5rem; - color: var(--ifm-navbar-link-color); -} - -.segmented__button__item:hover { - cursor: pointer; - transition: all 0.5s; - background-color: var(--ifm-menu-color-background-active); -} - -.segmented__button__item--active { - color: var(--ifm-menu-color-active); - background-color: var(--ifm-menu-color-background-active); -} \ No newline at end of file +/* Segmented button styles moved to shared.css */ \ No newline at end of file diff --git a/packages/website/src/pages/icons.tsx b/packages/website/src/pages/icons.tsx index 57668854fccb..a09fb6f088c5 100644 --- a/packages/website/src/pages/icons.tsx +++ b/packages/website/src/pages/icons.tsx @@ -1,59 +1,139 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import clsx from "clsx"; import SAPIcons from "../../icons/index.js"; import SAPTNTIcons from "../../icons-tnt/index.js"; import SAPBSIcons from "../../icons-business-suite/index.js"; import Layout from '@theme/Layout'; +import "./shared.css"; import "./icons.css"; +// Collection metadata +const COLLECTION_METADATA = { + "SAP Icons": { + title: "SAP Icons", + package: "@ui5/webcomponents-icons", + packageUrl: "https://www.npmjs.com/package/@ui5/webcomponents-icons" + }, + "SAP TNT Icons": { + title: "SAP TNT Icons", + package: "@ui5/webcomponents-icons-tnt", + packageUrl: "https://www.npmjs.com/package/@ui5/webcomponents-icons-tnt" + }, + "SAP BSuite Icons": { + title: "SAP BSuite Icons", + package: "@ui5/webcomponents-icons-business-suite", + packageUrl: "https://www.npmjs.com/package/@ui5/webcomponents-icons-business-suite" + } +}; const Select = ({ updateState }) => { - const [ collection, setCollection ] = useState("SAP Icons"); + const [collection, setCollection] = useState("SAP Icons"); return
{ - setCollection("SAP Icons") - updateState("SAP Icons"); - }} - className={clsx("segmented__button__item", { 'segmented__button__item--active': collection === "SAP Icons" })} + onClick={() => { + setCollection("SAP Icons") + updateState("SAP Icons"); + }} + className={clsx("segmented__button__item", { 'segmented__button__item--active': collection === "SAP Icons" })} >SAP Icons
{ - setCollection("SAP TNT Icons"); - updateState("SAP TNT Icons"); - }} - className={clsx("segmented__button__item", { 'segmented__button__item--active': collection === "SAP TNT Icons" })} + onClick={() => { + setCollection("SAP TNT Icons"); + updateState("SAP TNT Icons"); + }} + className={clsx("segmented__button__item", { 'segmented__button__item--active': collection === "SAP TNT Icons" })} >SAP TNT Icons
{ - setCollection("SAP BSuite Icons"); - updateState("SAP BSuite Icons"); - }} - className={clsx("segmented__button__item", { 'segmented__button__item--active': collection === "SAP BSuite Icons" })} + onClick={() => { + setCollection("SAP BSuite Icons"); + updateState("SAP BSuite Icons"); + }} + className={clsx("segmented__button__item", { 'segmented__button__item--active': collection === "SAP BSuite Icons" })} >SAP BSuite Icons
; }; -const Collection = ({ currCollection }) => { - if (currCollection === "SAP TNT Icons") { - return - } else if (currCollection === "SAP BSuite Icons") { - return - } +const Collection = ({ currCollection }) => { + // if (currCollection === "SAP TNT Icons") { + // return + // } else if (currCollection === "SAP BSuite Icons") { + // return + // } return }; export default function Icons() { - const [ collection, setCollection ] = useState("SAP Icons"); + const [collection, setCollection] = useState("SAP Icons"); + const [searchQuery, setSearchQuery] = useState(""); + + // Client-side search filtering using DOM manipulation + useEffect(() => { + if (typeof window === 'undefined') return; + + const iconWrappers = document.querySelectorAll('.icon__wrapper'); + const notFoundElement = document.querySelector('.icon__not__found'); + let visibleCount = 0; + + iconWrappers.forEach(wrapper => { + const iconName = wrapper.getAttribute('data-icon-name') || ''; + const matches = iconName.toLowerCase().includes(searchQuery.toLowerCase()); + + if (matches) { + wrapper.classList.remove('hidden'); + visibleCount++; + } else { + wrapper.classList.add('hidden'); + } + }); + + // Show/hide "not found" message + if (visibleCount === 0 && searchQuery) { + notFoundElement?.classList.remove('hidden'); + } else { + notFoundElement?.classList.add('hidden'); + } + }, [searchQuery, collection]); // Re-run when search or collection changes return ( - + +
+ + setSearchQuery(e.target.value)} + /> +
+ + {/* Icons grid */} + +
); }; diff --git a/packages/website/src/pages/illustrations.css b/packages/website/src/pages/illustrations.css index b9f89e94ea42..7d49b1e9cef1 100644 --- a/packages/website/src/pages/illustrations.css +++ b/packages/website/src/pages/illustrations.css @@ -1,3 +1,5 @@ +@import "./shared.css"; + :root { --sapIllus_BrandColorPrimary: var(--sapContent_Illustrative_Color1); --sapIllus_BrandColorSecondary: var(--sapContent_Illustrative_Color2); @@ -206,51 +208,8 @@ color: var(--ifm-color-primary); } -/* Search input */ -.illustrations__search { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background: var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat 0.75rem center / 1rem 1rem; - border: none; - border-radius: 2rem; - color: var(--ifm-navbar-search-input-color); - cursor: text; - display: inline-block; - font-size: 0.9rem; - height: 2.5rem; - padding: 0 0.5rem 0 2.25rem; - width: 12.5rem; -} - -/* Segmented button */ -.segmented__button { - display: inline-flex; - align-items: center; - justify-content: center; - background-color: var(--ifm-background-color); - padding: 0.5rem; - border-radius: 0.5rem; -} - -.segmented__button__item { - padding: 0.5rem 1rem; - margin: 0 0.125rem; - border-radius: 0.5rem; - color: var(--ifm-navbar-link-color); - font-weight: 500; -} - -.segmented__button__item:hover { - cursor: pointer; - transition: all 0.2s; - background-color: var(--ifm-menu-color-background-hover); -} - -.segmented__button__item--active { - color: var(--ifm-menu-color-active); - background-color: var(--ifm-menu-color-background-active); -} +/* Segmented button styles moved to shared.css */ +/* Search input styles moved to shared.css */ /* Responsive: Stack panes on mobile */ @media (max-width: 996px) { diff --git a/packages/website/src/pages/illustrations.tsx b/packages/website/src/pages/illustrations.tsx index b22e2121f8f7..bfee571663b0 100644 --- a/packages/website/src/pages/illustrations.tsx +++ b/packages/website/src/pages/illustrations.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import clsx from "clsx"; import Layout from '@theme/Layout'; import BrowserOnly from '@docusaurus/BrowserOnly'; +import "./shared.css"; import "./illustrations.css"; // Import generated components and data @@ -227,7 +228,7 @@ function IllustrationsContent() {