From a8504f89975c0904bf68d89814422c63a63f798d Mon Sep 17 00:00:00 2001 From: Aleksei Date: Fri, 2 Feb 2024 22:32:04 +0300 Subject: [PATCH] Revert "Revert "feat: Added ArticleNavigation component, which provides next/prev navigation between articles (#159)" (#168)" This reverts commit 1c118e5ef33a0e0e4ef6f50cf40b2d3d7b33dbd0. --- .../ArticleNavigation/ArticleNavigation.scss | 66 +++++++++++++++ .../ArticleNavigation/ArticleNavigation.tsx | 82 +++++++++++++++++++ src/components/ArticleNavigation/index.ts | 1 + src/components/Component/Component.scss | 4 + src/components/Component/Component.tsx | 59 ++++++++++++- .../ComponentsLayout/ComponentsLayout.tsx | 20 +---- .../DesignArticle/DesignArticle.scss | 3 + .../DesignArticle/DesignArticle.tsx | 55 ++++++++++++- src/components/DesignLayout/DesignLayout.tsx | 26 ++---- .../components/[libId]/[componentId].tsx | 28 ++++++- src/pages/design/[sectionId]/[articleId].tsx | 27 ++++-- 11 files changed, 324 insertions(+), 47 deletions(-) create mode 100644 src/components/ArticleNavigation/ArticleNavigation.scss create mode 100644 src/components/ArticleNavigation/ArticleNavigation.tsx create mode 100644 src/components/ArticleNavigation/index.ts diff --git a/src/components/ArticleNavigation/ArticleNavigation.scss b/src/components/ArticleNavigation/ArticleNavigation.scss new file mode 100644 index 000000000000..3b885e3b9178 --- /dev/null +++ b/src/components/ArticleNavigation/ArticleNavigation.scss @@ -0,0 +1,66 @@ +@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; + } + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm') - 1) { + display: none; + } + &-title { + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'md') - 1) { + display: none; + } + } + } + &__button { + cursor: pointer; + text-decoration: none; + 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; + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm') - 1) { + width: unset; + } + &_reverse { + flex-direction: row-reverse; + margin-left: auto; + padding-left: var(--g-spacing-5); + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm') - 1) { + padding-left: var(--g-spacing-1); + } + } + &-icon { + flex: none; + border-radius: 12px; + background: var(--g-color-base-generic); + display: flex; + align-items: center; + justify-content: center; + color: var(--g-color-text-primary); + padding: 0 var(--g-spacing-5); + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'md') - 1) { + padding: 0 var(--g-spacing-3); + } + @media (max-width: map-get(pcVariables.$gridBreakpoints, 'sm') - 1) { + padding: var(--g-spacing-3); + } + } + } +} diff --git a/src/components/ArticleNavigation/ArticleNavigation.tsx b/src/components/ArticleNavigation/ArticleNavigation.tsx new file mode 100644 index 000000000000..cf05cf033c78 --- /dev/null +++ b/src/components/ArticleNavigation/ArticleNavigation.tsx @@ -0,0 +1,82 @@ +import {ArrowLeft, ArrowRight} from '@gravity-ui/icons'; +import {Flex, Icon, Text} from '@gravity-ui/uikit'; +import Link from 'next/link'; +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 linkClickHandler = () => { + 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/Component/Component.scss b/src/components/Component/Component.scss index 4f2ff9ff39fb..ac97c8dad0a6 100644 --- a/src/components/Component/Component.scss +++ b/src/components/Component/Component.scss @@ -19,6 +19,10 @@ $block: '.#{variables.$ns}component'; border-radius: 10px; } + &__navigation { + margin-top: var(--g-spacing-10); + } + &__header { display: flex; align-items: center; diff --git a/src/components/Component/Component.tsx b/src/components/Component/Component.tsx index a64b04a9cbc3..fb226ccddc5f 100644 --- a/src/components/Component/Component.tsx +++ b/src/components/Component/Component.tsx @@ -8,6 +8,8 @@ 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 {ArticleNavigation} from '../ArticleNavigation/ArticleNavigation'; +import {Section} from '../NavigationLayout/types'; import {SandboxBlock} from '../SandboxBlock'; import './Component.scss'; @@ -34,15 +36,64 @@ export type ComponentProps = { libId: string; component: ComponentType; readmeContent: string; + sections: Section[]; }; -export const Component: React.FC = ({libId, component, readmeContent}) => { +export const Component: React.FC = ({ + libId, + component, + readmeContent, + sections, +}) => { const router = useRouter(); const {tabId} = router.query; const [activeTab, setActiveTab] = React.useState( tabId === Tab.Design ? Tab.Design : Tab.Overview, ); + + const currentSection = React.useMemo( + () => sections.find((item) => item.id === libId), + [libId, sections], + ); + + const currentIndex = React.useMemo(() => { + if (!currentSection || !currentSection.subSections) { + return null; + } + return currentSection.subSections.findIndex((item) => item.id === component.id); + }, [currentSection, component.id]); + + const nextSection = React.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 = React.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]); + React.useEffect(() => { setActiveTab(tabId === Tab.Design ? Tab.Design : Tab.Overview); }, [tabId]); @@ -140,6 +191,12 @@ export const Component: React.FC = ({libId, component, readmeCon rewriteLinks={rewriteLinks} withComponents /> +
+ +
)} diff --git a/src/components/ComponentsLayout/ComponentsLayout.tsx b/src/components/ComponentsLayout/ComponentsLayout.tsx index 28581de46fce..61803288dfae 100644 --- a/src/components/ComponentsLayout/ComponentsLayout.tsx +++ b/src/components/ComponentsLayout/ComponentsLayout.tsx @@ -1,34 +1,20 @@ -import React, {useMemo} from 'react'; +import React from 'react'; -import {libs} from '../../content/components'; import {NavigationLayout, Section} from '../NavigationLayout/NavigationLayout'; export type ComponentsLayoutProps = { libId: string; componentId?: string; children?: React.ReactNode; + sections: Section[]; }; export const ComponentsLayout: React.FC = ({ libId, componentId, children, + sections, }) => { - const sections = useMemo(() => { - return libs.map((lib) => ({ - id: lib.id, - title: lib.title, - // url: `/components/${lib.id}`, // "Overview" link - subSections: lib.components.map((component) => ({ - id: component.id, - title: component.title, - url: - component.isComingSoon === true ? '#' : `/components/${lib.id}/${component.id}`, - isComingSoon: component.isComingSoon, - })), - })); - }, []); - return ( = ({article}) => { +export const DesignArticle: React.FC = ({article, 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 === article.id); + }, [currentSection, article.id]); + + 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/components/DesignLayout/DesignLayout.tsx b/src/components/DesignLayout/DesignLayout.tsx index 84b2344eab73..37f0e214ece6 100644 --- a/src/components/DesignLayout/DesignLayout.tsx +++ b/src/components/DesignLayout/DesignLayout.tsx @@ -1,30 +1,20 @@ -import React, {useMemo} from 'react'; +import React from 'react'; -import {sections as designSections} from '../../content/design'; import {NavigationLayout, Section} from '../NavigationLayout/NavigationLayout'; export type DesignLayoutProps = { sectionId: string; articleId?: string; children?: React.ReactNode; + sections: Section[]; }; -export const DesignLayout: React.FC = ({sectionId, articleId, children}) => { - const sections = useMemo(() => { - const result: Section[] = designSections.map((section) => ({ - id: section.id, - title: section.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}`, - })), - })); - return result; - }, []); - +export const DesignLayout: React.FC = ({ + sectionId, + articleId, + children, + sections, +}) => { return ( (() => { + return libs.map(({id, title, components}) => ({ + id: id, + title: title, + // url: `/components/${lib.id}`, // "Overview" link + subSections: components.map((componentItem) => ({ + id: componentItem.id, + title: componentItem.title, + url: + componentItem.isComingSoon === true + ? '#' + : `/components/${lib.id}/${componentItem.id}`, + isComingSoon: componentItem.isComingSoon, + })), + })); + }, []); + return ( - - + + ); diff --git a/src/pages/design/[sectionId]/[articleId].tsx b/src/pages/design/[sectionId]/[articleId].tsx index 2eaf2443d660..d1843377f84a 100644 --- a/src/pages/design/[sectionId]/[articleId].tsx +++ b/src/pages/design/[sectionId]/[articleId].tsx @@ -1,13 +1,15 @@ import {GetStaticPaths, GetStaticProps} from 'next'; +import {useMemo} from 'react'; +import {Section} from 'src/components/NavigationLayout/types'; import {DesignArticle} from '../../../components/DesignArticle/DesignArticle'; import {DesignLayout} from '../../../components/DesignLayout/DesignLayout'; import {Layout} from '../../../components/Layout/Layout'; -import {sections} from '../../../content/design'; +import {sections as designSections} from '../../../content/design'; export const getStaticPaths: GetStaticPaths = async () => { return { - paths: sections.reduce<{params: {sectionId: string; articleId: string}}[]>( + paths: designSections.reduce<{params: {sectionId: string; articleId: string}}[]>( (acc, section) => { section.articles.forEach((article) => { acc.push({params: {sectionId: section.id, articleId: article.id}}); @@ -27,17 +29,32 @@ export const getStaticProps: GetStaticProps = async (context) => { }; export const ArticlePage = ({sectionId, articleId}: {sectionId: string; articleId: string}) => { - const section = sections.find((item) => item.id === sectionId); + const section = designSections.find((item) => item.id === sectionId); const article = section?.articles.find((item) => item.id === articleId); if (!section || !article) { return null; } + const sections = useMemo(() => { + const result: Section[] = designSections.map(({id, title, articles}) => ({ + id: id, + title: title, + // Uncomment it to show overview tab + // url: `/design/${section.id}`, + subSections: articles.map((articleItem) => ({ + id: articleItem.id, + title: articleItem.title, + url: `/design/${id}/${articleItem.id}`, + })), + })); + return result; + }, []); + return ( - - + + );