diff --git a/src/components/ArticleNavigation/ArticleNavigation.scss b/src/components/ArticleNavigation/ArticleNavigation.scss new file mode 100644 index 000000000000..c3be46574b28 --- /dev/null +++ b/src/components/ArticleNavigation/ArticleNavigation.scss @@ -0,0 +1,60 @@ +@use '~@gravity-ui/page-constructor/styles/variables.scss' as pcVariables; +@use '../../variables.scss'; + +$block: '.#{variables.$ns}article-navigation'; + +#{$block} { + display: flex; + gap: var(--g-spacing-6); + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'md') - 1) { + gap: var(--g-spacing-5); + } + &__content { + padding: var(--g-spacing-2) 0; + overflow: hidden; + width: 100%; + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'md') - 1) { + padding: var(--g-spacing-3) 0; + } + &-title { + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'md') - 1) { + display: none; + } + } + } + &__button { + cursor: pointer; + display: flex; + gap: var(--g-spacing-3); + width: 50%; + border-radius: 16px; + border: 1px solid var(--g-color-sfx-fade); + padding: var(--g-spacing-1); + justify-content: space-between; + &_reverse { + flex-direction: row-reverse; + } + &_right { + margin-left: auto; + padding-left: var(--g-spacing-5); + } + &_left { + margin-right: auto; + padding-right: var(--g-spacing-5); + } + &-icon { + flex: none; + width: 56px; + height: 100%; + border-radius: 12px; + background: var(--g-color-base-generic); + display: flex; + align-items: center; + justify-content: center; + color: var(--g-color-text-primary); + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'md') - 1) { + width: 40px; + } + } + } +} diff --git a/src/components/ArticleNavigation/ArticleNavigation.tsx b/src/components/ArticleNavigation/ArticleNavigation.tsx new file mode 100644 index 000000000000..45453ae89d03 --- /dev/null +++ b/src/components/ArticleNavigation/ArticleNavigation.tsx @@ -0,0 +1,95 @@ +import {ArrowLeft, ArrowRight} from '@gravity-ui/icons'; +import {Flex, Icon, Link, Text} from '@gravity-ui/uikit'; +import {useCallback} from 'react'; +import {CONTENT_WRAPPER_ID} from 'src/constants'; +import {block} from 'src/utils'; + +import {SubSection} from '../NavigationLayout/types'; + +const b = block('article-navigation'); + +import './ArticleNavigation.scss'; + +interface ArticleNavigationProps { + prevSection: SubSection | null; + nextSection: SubSection | null; +} + +export const ArticleNavigation = ({prevSection, nextSection}: ArticleNavigationProps) => { + const scrollTop = useCallback(() => { + const content = document.getElementById(CONTENT_WRAPPER_ID); + if (content) { + content.scrollTo({ + top: 0, + behavior: 'smooth', + }); + } + }, []); + + const nextHandler = () => { + if (!nextSection) { + return; + } + scrollTop(); + }; + + const prevHandler = () => { + if (!prevSection) { + return; + } + scrollTop(); + }; + + return ( +
+ {prevSection && ( + +
+ +
+ + + Previous + + + {prevSection.title} + + + + )} + {nextSection && ( + +
+ +
+ + + Next + + + {nextSection.title} + + + + )} +
+ ); +}; diff --git a/src/components/ArticleNavigation/index.ts b/src/components/ArticleNavigation/index.ts new file mode 100644 index 000000000000..b6d4f0b45b01 --- /dev/null +++ b/src/components/ArticleNavigation/index.ts @@ -0,0 +1 @@ +export {ArticleNavigation} from './ArticleNavigation'; diff --git a/src/components/ArticleNavigations/ArticleNavigations.scss b/src/components/ArticleNavigations/ArticleNavigations.scss deleted file mode 100644 index 01966bc8b849..000000000000 --- a/src/components/ArticleNavigations/ArticleNavigations.scss +++ /dev/null @@ -1,28 +0,0 @@ -@use '~@gravity-ui/page-constructor/styles/variables.scss' as pcVariables; -@use '../../variables.scss'; - -$block: '.#{variables.$ns}article-navigations'; - -#{$block} { - display: flex; - gap: 24px; - &__item { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - border-radius: 16px; - border: 1px solid rgba(255, 255, 255, 0.2); - padding: 4px; - &_full { - justify-content: space-between; - padding-left: 20px; - } - &-button { - width: 56px; - height: 100%; - border-radius: 11px; - overflow: hidden; - } - } -} diff --git a/src/components/ArticleNavigations/ArticleNavigations.tsx b/src/components/ArticleNavigations/ArticleNavigations.tsx deleted file mode 100644 index 0846113aae1c..000000000000 --- a/src/components/ArticleNavigations/ArticleNavigations.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import {ArrowLeft, ArrowRight} from '@gravity-ui/icons'; -import {Button, Flex, Icon, Text} from '@gravity-ui/uikit'; -import {block} from 'src/utils'; - -const b = block('article-navigations'); - -import './ArticleNavigations.scss'; - -interface ArticleNavigationsProps { - previousTitle: string; - nextTitle: string; - nextHandler: () => void; - prevHandler: () => void; -} - -export const ArticleNavigations = ({ - previousTitle, - nextTitle, - nextHandler, - prevHandler, -}: ArticleNavigationsProps) => { - return ( -
-
- - - - Previous - - - {previousTitle} - - -
-
- - - Next - - - {nextTitle} - - - -
-
- ); -}; diff --git a/src/components/ArticleNavigations/index.ts b/src/components/ArticleNavigations/index.ts deleted file mode 100644 index f456dad0ba45..000000000000 --- a/src/components/ArticleNavigations/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {ArticleNavigations} from './ArticleNavigations'; diff --git a/src/components/Component/Component.tsx b/src/components/Component/Component.tsx index 226650637888..fb226ccddc5f 100644 --- a/src/components/Component/Component.tsx +++ b/src/components/Component/Component.tsx @@ -1,15 +1,14 @@ import {Button, Icon, Tabs} from '@gravity-ui/uikit'; import {URL} from 'next/dist/compiled/@edge-runtime/primitives/url'; import {useRouter} from 'next/router'; -import React, {useMemo} from 'react'; -import {CONTENT_WRAPPER_ID} from 'src/constants'; +import React from 'react'; import figmaIcon from '../../assets/icons/figma.svg'; import githubIcon from '../../assets/icons/github.svg'; import {MDXRenderer} from '../../components/MDXRenderer/MDXRenderer'; import {Component as ComponentType} from '../../content/components/types'; import {block, getRouteFromReadmeUrl} from '../../utils'; -import {ArticleNavigations} from '../ArticleNavigations'; +import {ArticleNavigation} from '../ArticleNavigation/ArticleNavigation'; import {Section} from '../NavigationLayout/types'; import {SandboxBlock} from '../SandboxBlock'; @@ -52,84 +51,71 @@ export const Component: React.FC = ({ const [activeTab, setActiveTab] = React.useState( tabId === Tab.Design ? Tab.Design : Tab.Overview, ); - React.useEffect(() => { - setActiveTab(tabId === Tab.Design ? Tab.Design : Tab.Overview); - }, [tabId]); - - const rewriteLinks = React.useCallback( - (link: string) => { - if (!component.content?.readmeUrl) { - return link; - } - - const readmeUrl = new URL(component.content.readmeUrl); - const url = new URL(link, component.content.readmeUrl); - - if (url.origin !== readmeUrl.origin) { - return link; - } - const newLink = getRouteFromReadmeUrl(url.toString()); - return newLink ?? link; - }, - [component.content?.readmeUrl], - ); - - const currentSection = useMemo( + const currentSection = React.useMemo( () => sections.find((item) => item.id === libId), [libId, sections], ); - const currentIndex = useMemo(() => { - if (!currentSection || !currentSection.subSections || !currentIndex) { + const currentIndex = React.useMemo(() => { + if (!currentSection || !currentSection.subSections) { return null; } return currentSection.subSections.findIndex((item) => item.id === component.id); }, [currentSection, component.id]); - const nextSection = useMemo(() => { - if (!currentSection || !currentSection.subSections || !currentIndex) { + const nextSection = React.useMemo(() => { + if ( + !currentSection || + !currentSection.subSections || + (!currentIndex && currentIndex !== 0) + ) { + return null; + } + const nextIndex = currentIndex + 1; + if (nextIndex >= currentSection.subSections.length) { return null; } - const nextIndex = (currentIndex + 1) % currentSection.subSections.length; return currentSection.subSections[nextIndex]; }, [currentIndex, currentSection]); - const prevSection = useMemo(() => { - if (!currentSection || !currentSection.subSections || !currentIndex) { + const prevSection = React.useMemo(() => { + if ( + !currentSection || + !currentSection.subSections || + (!currentIndex && currentIndex !== 0) + ) { + return null; + } + const prevIndex = currentIndex - 1; + if (prevIndex < 0) { return null; } - const prevIndex = - (currentIndex - 1 + currentSection.subSections.length) % - currentSection.subSections.length; return currentSection.subSections[prevIndex]; }, [currentIndex, currentSection]); - const scrollTop = React.useCallback(() => { - const content = document.getElementById(CONTENT_WRAPPER_ID); - if (content) { - content.scrollTo({ - top: 0, - behavior: 'smooth', - }); - } - }, []); + React.useEffect(() => { + setActiveTab(tabId === Tab.Design ? Tab.Design : Tab.Overview); + }, [tabId]); - const onNextHandler = () => { - if (!nextSection) { - return; - } - router.push(nextSection.url); - scrollTop(); - }; + const rewriteLinks = React.useCallback( + (link: string) => { + if (!component.content?.readmeUrl) { + return link; + } - const onPrevHandler = () => { - if (!prevSection) { - return; - } - router.push(prevSection.url); - scrollTop(); - }; + const readmeUrl = new URL(component.content.readmeUrl); + const url = new URL(link, component.content.readmeUrl); + + if (url.origin !== readmeUrl.origin) { + return link; + } + + const newLink = getRouteFromReadmeUrl(url.toString()); + return newLink ?? link; + }, + [component.content?.readmeUrl], + ); return (
@@ -205,16 +191,12 @@ export const Component: React.FC = ({ rewriteLinks={rewriteLinks} withComponents /> - {typeof window !== 'undefined' && prevSection && nextSection ? ( -
- -
- ) : null} +
+ +
)}
diff --git a/src/components/DesignArticle/DesignArticle.tsx b/src/components/DesignArticle/DesignArticle.tsx index 554ce76b7003..75334670afa5 100644 --- a/src/components/DesignArticle/DesignArticle.tsx +++ b/src/components/DesignArticle/DesignArticle.tsx @@ -1,9 +1,10 @@ -import React from 'react'; +import React, {useMemo} from 'react'; import {Article} from '../../content/design/types'; import {block} from '../../utils'; -import {ArticleNavigations} from '../ArticleNavigations/index'; +import {ArticleNavigation} from '../ArticleNavigation/ArticleNavigation'; import {MDXRenderer} from '../MDXRenderer/MDXRenderer'; +import {Section} from '../NavigationLayout/types'; import './DesignArticle.scss'; @@ -12,31 +13,65 @@ const b = block('design-article'); export type DesignArticleProps = { article: Article; articleId?: string; - previousTitle: string; - nextTitle: string; - nextHandler: () => void; - prevHandler: () => void; + sectionId?: string; + sections: Section[]; }; export const DesignArticle: React.FC = ({ article, - previousTitle, - nextTitle, - nextHandler, - prevHandler, + articleId, + sectionId, + sections, }) => { + const currentSection = useMemo( + () => sections.find((item) => item.id === sectionId), + [sectionId, sections], + ); + + const currentIndex = useMemo(() => { + if (!currentSection || !currentSection.subSections) { + return null; + } + return currentSection.subSections.findIndex((item) => item.id === articleId); + }, [currentSection, articleId]); + + const nextSection = useMemo(() => { + if ( + !currentSection || + !currentSection.subSections || + (!currentIndex && currentIndex !== 0) + ) { + return null; + } + const nextIndex = currentIndex + 1; + if (nextIndex >= currentSection.subSections.length) { + return null; + } + return currentSection.subSections[nextIndex]; + }, [currentIndex, currentSection]); + + const prevSection = useMemo(() => { + if ( + !currentSection || + !currentSection.subSections || + (!currentIndex && currentIndex !== 0) + ) { + return null; + } + const prevIndex = currentIndex - 1; + if (prevIndex < 0) { + return null; + } + return currentSection.subSections[prevIndex]; + }, [currentIndex, currentSection]); + return (

{article.title}

- +
); }; diff --git a/src/pages/components/[libId]/[componentId].tsx b/src/pages/components/[libId]/[componentId].tsx index 012d8fcf5dad..8d04d92d83fa 100644 --- a/src/pages/components/[libId]/[componentId].tsx +++ b/src/pages/components/[libId]/[componentId].tsx @@ -65,16 +65,18 @@ export const ComponentPage = ({ } const sections = useMemo(() => { - return libs.map((lib) => ({ - id: lib.id, - title: lib.title, + return libs.map(({id, title, components}) => ({ + id: id, + title: title, // url: `/components/${lib.id}`, // "Overview" link - subSections: lib.components.map((component) => ({ - id: component.id, - title: component.title, + subSections: components.map((componentItem) => ({ + id: componentItem.id, + title: componentItem.title, url: - component.isComingSoon === true ? '#' : `/components/${lib.id}/${component.id}`, - isComingSoon: component.isComingSoon, + componentItem.isComingSoon === true + ? '#' + : `/components/${lib.id}/${componentItem.id}`, + isComingSoon: componentItem.isComingSoon, })), })); }, []); diff --git a/src/pages/design/[sectionId]/[articleId].tsx b/src/pages/design/[sectionId]/[articleId].tsx index 253815bb49f7..9d61337b5743 100644 --- a/src/pages/design/[sectionId]/[articleId].tsx +++ b/src/pages/design/[sectionId]/[articleId].tsx @@ -1,8 +1,6 @@ import {GetStaticPaths, GetStaticProps} from 'next'; -import {useRouter} from 'next/router'; -import {useCallback, useMemo} from 'react'; +import {useMemo} from 'react'; import {Section} from 'src/components/NavigationLayout/types'; -import {CONTENT_WRAPPER_ID} from 'src/constants'; import {DesignArticle} from '../../../components/DesignArticle/DesignArticle'; import {DesignLayout} from '../../../components/DesignLayout/DesignLayout'; @@ -31,7 +29,6 @@ export const getStaticProps: GetStaticProps = async (context) => { }; export const ArticlePage = ({sectionId, articleId}: {sectionId: string; articleId: string}) => { - const router = useRouter(); const section = designSections.find((item) => item.id === sectionId); const article = section?.articles.find((item) => item.id === articleId); @@ -40,86 +37,28 @@ export const ArticlePage = ({sectionId, articleId}: {sectionId: string; articleI } const sections = useMemo(() => { - const result: Section[] = designSections.map((section) => ({ - id: section.id, - title: section.title, + const result: Section[] = designSections.map(({id, title, articles}) => ({ + id: id, + title: title, // Uncomment it to show overview tab // url: `/design/${section.id}`, - subSections: section.articles.map((article) => ({ - id: article.id, - title: article.title, - url: `/design/${section.id}/${article.id}`, + subSections: articles.map((articleItem) => ({ + id: articleItem.id, + title: articleItem.title, + url: `/design/${id}/${articleItem.id}`, })), })); return result; }, []); - const currentSection = useMemo( - () => sections.find((item) => item.id === sectionId), - [sectionId, sections], - ); - - const currentIndex = useMemo(() => { - if (!currentSection || !currentSection.subSections) { - return null; - } - return currentSection.subSections.findIndex((item) => item.id === articleId); - }, [currentSection, articleId]); - - const nextSection = useMemo(() => { - if (!currentSection || !currentSection.subSections || !currentIndex) { - return null; - } - const nextIndex = (currentIndex + 1) % currentSection.subSections.length; - return currentSection.subSections[nextIndex]; - }, [currentIndex, currentSection]); - - const prevSection = useMemo(() => { - if (!currentSection || !currentSection.subSections || !currentIndex) { - return null; - } - const prevIndex = - (currentIndex - 1 + currentSection.subSections.length) % - currentSection.subSections.length; - return currentSection.subSections[prevIndex]; - }, [currentIndex, currentSection]); - - const scrollTop = useCallback(() => { - const content = document.getElementById(CONTENT_WRAPPER_ID); - if (content) { - content.scrollTo({ - top: 0, - behavior: 'smooth', - }); - } - }, []); - - const onNextHandler = () => { - if (!nextSection) { - return; - } - router.push(nextSection.url); - scrollTop(); - }; - - const onPrevHandler = () => { - if (!prevSection) { - return; - } - router.push(prevSection.url); - scrollTop(); - }; - return (