diff --git a/packages/landingpage/components/getting-started/layout.tsx b/packages/landingpage/components/getting-started/layout.tsx index 6fb99a2d16..7bdf18179f 100644 --- a/packages/landingpage/components/getting-started/layout.tsx +++ b/packages/landingpage/components/getting-started/layout.tsx @@ -7,6 +7,8 @@ import { Framework, NameByFramework } from 'utils/frameworks'; import { MainRoutes } from 'utils/routes'; import styles from './layout.module.scss'; import NavigationMenu from './navigationMenu'; +import { useEffect, useState } from 'react'; +import { scrollToElement } from 'utils/scrollToElement'; interface Props { children: React.ReactNode; @@ -21,9 +23,21 @@ const SANDBOX_MAP: { [key in Framework]?: string } = { }; const Layout = ({ children, framework, sandboxUrl }: Props) => { - const { push } = useRouter(); + const router = useRouter(); const frameworkName = NameByFramework[framework]; + useEffect(() => { + const handlePageLoad = () => { + if (router.asPath.includes('#')) { + const hash = window.location.hash.substring(1); + scrollToElement(hash); + } + }; + + window.addEventListener('load', handlePageLoad); + return () => window.removeEventListener('load', handlePageLoad); + }, [router.asPath]); + return (
@@ -31,7 +45,7 @@ const Layout = ({ children, framework, sandboxUrl }: Props) => { id="segment-grp" value={framework} onValueChange={(value) => - push( + router.push( `/${Supported_Locales.EN}${MainRoutes.GETTING_STARTED}/${value.detail}`, ) } diff --git a/packages/landingpage/components/getting-started/menuSection.tsx b/packages/landingpage/components/getting-started/menuSection.tsx index c44d98c016..3aff8d17cf 100644 --- a/packages/landingpage/components/getting-started/menuSection.tsx +++ b/packages/landingpage/components/getting-started/menuSection.tsx @@ -1,9 +1,12 @@ import { ReactNode } from 'react'; import 'utils/stringExtensions'; +import { InoTooltip } from '@elements'; +import { scrollToElement } from 'utils/scrollToElement'; type MenuSectionProps = { title: string; children: ReactNode; + level?: 'main' | 'sub'; // 'main' for main sections, 'sub' for subsections (subsections will not be listed in the navigationMenu.tsx) }; /** @@ -12,10 +15,36 @@ type MenuSectionProps = { * @param title The title of the section (that also functions as the id). * @param children The content to render within the section. */ -function MenuSection({ title, children }: MenuSectionProps) { +function MenuSection({ title, children, level = 'main' }: MenuSectionProps) { + // Sanitize the ID by replacing all non-word characters (this prevents querySelector errors when the id could contain special characters like "Install @inovex.de/elements" in the vue-guide.mdx) + const id = title.toCamelCase().replace(/[^\w-]/g, '-'); + const isMainSection = level === 'main'; + + const handleMenuSectionClick = ( + event: React.MouseEvent, + ) => { + event.preventDefault(); + scrollToElement(id); + }; + + const HeadingTag = isMainSection ? 'h2' : 'h3'; + return ( -
-

{title}

+
+ + {title} + + + # + {children}
); diff --git a/packages/landingpage/components/getting-started/navigationMenu.tsx b/packages/landingpage/components/getting-started/navigationMenu.tsx index b4a0137ed3..3f001ab75e 100644 --- a/packages/landingpage/components/getting-started/navigationMenu.tsx +++ b/packages/landingpage/components/getting-started/navigationMenu.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import styles from './navigationMenu.module.scss'; +import { scrollToElement } from 'utils/scrollToElement'; // We use Record instead of enum because we can iterate over it AND use it as a type export type Sections = Record; @@ -27,46 +28,30 @@ export default function NavigationMenu({ title }: NavigationMenuProps) { 'section[data-menu-section]', ); - const sectionsTemp: Sections = {}; + const sectionMap: Sections = {}; - domSectionElements.forEach((section) => { - const headingElement = section.querySelector('h2'); - if (headingElement) { - const headingText = headingElement.innerHTML.trim(); - const key = headingText; - const value = headingText.toCamelCase(); - sectionsTemp[key] = value; + domSectionElements.forEach((sectionElement) => { + if (sectionElement.id) { + const sectionTitle = + sectionElement.querySelector('h2')?.textContent?.trim() || + sectionElement.id; + sectionMap[sectionTitle] = sectionElement.id; } - observer.observe(section); + observer.observe(sectionElement); }); - setActiveSection(Object.values(sectionsTemp)[0]); // set the first section as active - setSections(sectionsTemp); + setActiveSection(Object.values(sectionMap)[0]); // set the first section as active + setSections(sectionMap); return () => observer.disconnect(); }, []); function handleAnchorClick( event: React.MouseEvent, - section: string, + sectionId: string, ) { event.preventDefault(); - const targetElement = document.querySelector(`#${section}`); - if (!targetElement) return; - - const headerOffset = 80; - const elementPosition = targetElement.getBoundingClientRect().top; - const offsetPosition = elementPosition + window.pageYOffset - headerOffset; - - // Change the URL (because we're preventing the default anchor click behavior) - const newUrl = `${window.location.origin}${window.location.pathname}#${section}`; - window.history.pushState(null, '', newUrl); - - // Using window.scrollTo() instead of element.scrollIntoView() because the latter doesn't support offsets - window.scrollTo({ - top: offsetPosition, - behavior: 'smooth', - }); + scrollToElement(sectionId); } // Return null if there are no sections @@ -77,20 +62,19 @@ export default function NavigationMenu({ title }: NavigationMenuProps) { diff --git a/packages/landingpage/components/layout/header/desktop/header.desktop.module.scss b/packages/landingpage/components/layout/header/desktop/header.desktop.module.scss index c0db6e5530..3538e0a2dc 100644 --- a/packages/landingpage/components/layout/header/desktop/header.desktop.module.scss +++ b/packages/landingpage/components/layout/header/desktop/header.desktop.module.scss @@ -6,7 +6,7 @@ $header-height--shrunk: 80px; top: $header-height--shrunk - $header-height; position: sticky; background-color: var(--inovex-elements-white); - z-index: 999; + z-index: 99999; height: $header-height; display: flex; align-items: center; diff --git a/packages/landingpage/components/layout/header/desktop/header.desktop.tsx b/packages/landingpage/components/layout/header/desktop/header.desktop.tsx index 731d58323e..bd4a834ab5 100644 --- a/packages/landingpage/components/layout/header/desktop/header.desktop.tsx +++ b/packages/landingpage/components/layout/header/desktop/header.desktop.tsx @@ -15,7 +15,7 @@ export default function HeaderDesktop() { return (
-
+
-### Install via package manager + Add the package `@inovex.de/elements` to your project using **npm** or **yarn**: diff --git a/packages/landingpage/mdx/getting-started/react-guide.mdx b/packages/landingpage/mdx/getting-started/react-guide.mdx index 4f2a385194..f576a60fe5 100644 --- a/packages/landingpage/mdx/getting-started/react-guide.mdx +++ b/packages/landingpage/mdx/getting-started/react-guide.mdx @@ -109,7 +109,7 @@ import MenuSection from "components/getting-started/menuSection"; ``` - ### Playground + Every component has a variety of powerful and unique properties. They are listed and explained in each component **Playground**: @@ -130,7 +130,7 @@ import MenuSection from "components/getting-started/menuSection"; Hint: These are some of the properties of the `` component. The Playground allows you to modify these properties and see the result in real time. Additionally, we provide examples about the usage of each component with its given properties. - + Following up on our login-form, we add a type and value property, setting it to a state object named _email_. Do the same for the password ``. ```tsx @@ -155,7 +155,7 @@ import MenuSection from "components/getting-started/menuSection"; ``` - ## Events + With the exact same approach, we use **Events**. They emit a certain value when the user does something that triggers our Event to fire. In other terms: Our event gives us a **reaction** to an **action** made by our User. In case of our `` the action is the user typing and our events reaction contains the typed input. @@ -191,8 +191,9 @@ import MenuSection from "components/getting-started/menuSection"; ``` Hint: while in our Playground the Event is called `valueChange`, we name it `onValueChange` inside of our ``. The reason for this is that we want to accomodate our naming similar to the react-way of naming events (e.g. onClick) + - ## Slots + Many components of the Elements Library have **slots**, that can be filled by other components or your own content to further customize its functionality or appearance. @@ -220,8 +221,8 @@ import MenuSection from "components/getting-started/menuSection"; ``` Note that we indicated the position of the slot inside our `` with `slot="icon-trailing"`. - - ## CSS-Variables + + In order to provide a CSS way of styling the inovex-elements, we sometimes provide custom properties, a.k.a. CSS-Variables. In the case of the ``, there are two variables we can use to change the appearance of our component: `--ino-input-line-color` to change the color of the underline and the `--ino-input-label-color` which changes the color of the floating label. They can be used like this: @@ -232,7 +233,7 @@ import MenuSection from "components/getting-started/menuSection"; --ino-input-label-color: blue; } ``` - + diff --git a/packages/landingpage/utils/scrollToElement.ts b/packages/landingpage/utils/scrollToElement.ts new file mode 100644 index 0000000000..25d2ec4ee4 --- /dev/null +++ b/packages/landingpage/utils/scrollToElement.ts @@ -0,0 +1,20 @@ +export const scrollToElement = (elementId: string, offset = 80) => { + const targetElement = document.getElementById(elementId); + if (!targetElement) return; + + const headerInnerElement = document.getElementById('desktopHeaderInner'); + const headerOffset = headerInnerElement + ? headerInnerElement.offsetHeight + : offset; // uses inputted offset if desktop header is not present (e.g. on mobile it defaults to 80px due to 26px high burger menu icon) + + const elementPosition = targetElement.getBoundingClientRect().top; + const offsetPosition = elementPosition + window.pageYOffset - headerOffset; + + // Using window.scrollTo() instead of element.scrollIntoView() because the latter doesn't support offsets + window.scrollTo({ + top: offsetPosition, + behavior: 'smooth', + }); + const newUrl = `${window.location.origin}${window.location.pathname}#${elementId}`; + window.history.pushState(null, '', newUrl); +};