diff --git a/preview/docs/Breadcrumbs.md b/preview/docs/Breadcrumbs.md index 06c5ce92..7cf92fd4 100644 --- a/preview/docs/Breadcrumbs.md +++ b/preview/docs/Breadcrumbs.md @@ -10,7 +10,6 @@ Breadcrumb navigation provides a list of links to all previous (parent) pages th **Attention!** -- `Home • index` is the root page by default. - This component should be on a nested page. - Navigation will only work on the published site. @@ -18,17 +17,17 @@ Breadcrumb navigation provides a list of links to all previous (parent) pages th ### In the interface -| Prop name | Default | Example | -| :-------- | :-----: | :-----: | -| Root page | `-` | `index` | -| Separator | `/` | `-` | +| Prop name | Default | Example | +| :-------- | :-----: | :--------: | +| Root page | `-` | `/foo/bar` | +| Separator | `/` | `-` | ### In the code (for developers) -| Prop name | Name in the code | Type | Default | Example | -| :-------- | :--------------: | :------: | :-----: | :-----: | -| Root page | `rootId` | `string` | `-` | `index` | -| Separator | `separator` | `string` | `/` | `-` | +| Prop name | Name in the code | Type | Default | Example | +| :-------- | :--------------: | :------: | :-----: | :--------: | +| Root page | `rootPath` | `string` | `-` | `/foo/bar` | +| Separator | `separator` | `string` | `/` | `-` | ## 🗄 GitHub diff --git a/preview/docs/Menu.md b/preview/docs/Menu.md index 8fd7b2a2..04dd785b 100644 --- a/preview/docs/Menu.md +++ b/preview/docs/Menu.md @@ -10,19 +10,19 @@ Add the component to the page and change the value of the `Maximum nesting` prop ### In the interface -| Prop name | Default | Example | -| :----------------------- | :-------: | :--------------: | -| Maximum nesting | `1` | `5` | -| Root page | `root` | `Your page name` | -| Mark parent active items | `Checked` | `Unchecked` | +| Prop name | Default | Example | +| :----------------------- | :--------: | :---------: | +| Maximum nesting | `1` | `5` | +| Root page | `rootPath` | `/foo` | +| Mark parent active items | `Checked` | `Unchecked` | ### In the code (for developers) -| Prop name | Name in the code | Type | Default | Example | -| :----------------------- | :------------------: | :-------: | :-----: | :--------------: | -| Maximum nesting | `depth` | `string` | `1` | `5` | -| Root page | `rootId` | `string` | `root` | `Your page name` | -| Mark parent active items | `exact-active-match` | `boolean` | `true` | `false` | +| Prop name | Name in the code | Type | Default | Example | +| :----------------------- | :------------------: | :-------: | :-----: | :-----: | +| Maximum nesting | `depth` | `string` | `1` | `5` | +| Root page | `rootPath` | `string` | `root` | `/foo` | +| Mark parent active items | `exact-active-match` | `boolean` | `true` | `false` | ## 🗄 GitHub diff --git a/preview/docs/MenuWithGroups.md b/preview/docs/MenuWithGroups.md index 0a9d774a..f378cc41 100644 --- a/preview/docs/MenuWithGroups.md +++ b/preview/docs/MenuWithGroups.md @@ -17,7 +17,7 @@ The `Root page ID` prop allows you to specify the page for which the menu will b | Prop name | Default | Example | | :-------------------------- | :----------------------------------: | :-----------------: | | Maximum nesting | `10` | `5` | -| Root page | `-` | `index` | +| Root page | `-` | `/foo` | | Groups condition by default | `Expand before the active menu item` | `Expand all groups` | ### In the code (for developers) @@ -25,7 +25,7 @@ The `Root page ID` prop allows you to specify the page for which the menu will b | Prop name | Name in the code | Type | Default | Example | | :-------------------------- | :--------------: | :------: | :------------: | :---------: | | Maximum nesting | `depth` | `string` | `10` | `5` | -| Root page | `root` | `string` | `-` | `index` | +| Root page | `root` | `string` | `-` | `/foo` | | Groups condition by default | `tabState` | `enum` | `expandActive` | `expandAll` | #### The 'Groups condition by default' property values diff --git a/preview/docs/ru/Breadcrumbs.md b/preview/docs/ru/Breadcrumbs.md index a55ea6f6..7eb71978 100644 --- a/preview/docs/ru/Breadcrumbs.md +++ b/preview/docs/ru/Breadcrumbs.md @@ -11,7 +11,6 @@ **Внимание**: -- `Home • index` является корневой страницей по умолчанию. - Этот компонент должен быть на вложенной странице. - Переход по ссылкам будет работать только на опубикованном сайте. @@ -19,17 +18,17 @@ ### В интерфейсе -| Названия свойств | По умолчанию | Пример | -| :---------------- | :----------: | :-----: | -| Корневая страница | `-` | `index` | -| Разделитель | `/` | `-` | +| Названия свойств | По умолчанию | Пример | +| :---------------- | :----------: | :--------: | +| Корневая страница | `-` | `/foo/bar` | +| Разделитель | `/` | `-` | ### В коде (для разработчиков) -| Названия свойств | Название в коде | Тип | По умолчанию | Пример | -| :---------------- | :-------------: | :------: | :----------: | :-----: | -| Корневая страница | `rootId` | `string` | `-` | `index` | -| Разделитель | `separator` | `string` | `/` | `-` | +| Названия свойств | Название в коде | Тип | По умолчанию | Пример | +| :---------------- | :-------------: | :------: | :----------: | :--------: | +| Корневая страница | `rootPath` | `string` | `-` | `/foo/bar` | +| Разделитель | `separator` | `string` | `/` | `-` | ## 🗄 GitHub diff --git a/preview/docs/ru/Menu.md b/preview/docs/ru/Menu.md index 6040fb0e..bc560058 100644 --- a/preview/docs/ru/Menu.md +++ b/preview/docs/ru/Menu.md @@ -10,19 +10,19 @@ ### В интерфейсе -| Названия свойств | По умолчанию | Пример | -| :------------------------------------ | :----------: | :-----------------------: | -| Максимальная вложенность | `1` | `5` | -| Корневая страница | `root` | `Название вашей страницы` | -| Выделять родительские активные пункты | `Отмечен` | `Не отмечен` | +| Названия свойств | По умолчанию | Пример | +| :------------------------------------ | :----------: | :----------: | +| Максимальная вложенность | `1` | `5` | +| Корневая страница | `-` | `/foo` | +| Выделять родительские активные пункты | `Отмечен` | `Не отмечен` | ### В коде (для разработчиков) -| Названия свойств | Название в коде | Тип | По умолчанию | Пример | -| :------------------------------------ | :------------------: | :-------: | :----------: | :-----------------------: | -| Максимальная вложенность | `depth` | `string` | `1` | `5` | -| Корневая страница | `rootId` | `string` | `root` | `Название вашей страницы` | -| Выделять родительские активные пункты | `exact-active-match` | `boolean` | `true` | `false` | +| Названия свойств | Название в коде | Тип | По умолчанию | Пример | +| :------------------------------------ | :------------------: | :-------: | :----------: | :-----: | +| Максимальная вложенность | `depth` | `string` | `1` | `5` | +| Корневая страница | `rootPath` | `string` | `-` | `/foo` | +| Выделять родительские активные пункты | `exact-active-match` | `boolean` | `true` | `false` | ## 🗄 GitHub diff --git a/preview/docs/ru/MenuWithGroups.md b/preview/docs/ru/MenuWithGroups.md index b1ec63d9..8d56a307 100644 --- a/preview/docs/ru/MenuWithGroups.md +++ b/preview/docs/ru/MenuWithGroups.md @@ -19,7 +19,7 @@ ID текущей страницы можно скопировать из адр | Названия свойств | По умолчанию | Пример | | :--------------------------- | :-------------------------------: | :-------------------: | | Максимальная вложенность | `10` | `5` | -| Корневая страница | `-` | `index` | +| Корневая страница | `-` | `/foo` | | Состояние групп по умолчанию | `Раскрыть перед активным пунктом` | `Раскрыть все группы` | ### В коде (для разработчиков) @@ -27,7 +27,7 @@ ID текущей страницы можно скопировать из адр | Названия свойств | Название в коде | Тип | По умолчанию | Пример | | :--------------------------- | :-------------: | :------: | :------------: | :---------: | | Максимальная вложенность | `depth` | `string` | `10` | `5` | -| Корневая страница | `root` | `string` | `-` | `index` | +| Корневая страница | `rootPath` | `string` | `-` | `/foo` | | Состояние групп по умолчанию | `tabState` | `enum` | `expandActive` | `expandAll` | #### Значения свойства 'Состояние групп по умолчанию' diff --git a/src/Breadcrumbs/Breadcrumbs.js b/src/Breadcrumbs/Breadcrumbs.js index 582ce7d2..361e6c7d 100644 --- a/src/Breadcrumbs/Breadcrumbs.js +++ b/src/Breadcrumbs/Breadcrumbs.js @@ -6,17 +6,17 @@ import { getItems } from './utils'; const isBrowser = typeof window !== 'undefined'; -const Breadcrumbs = ({ separator, rootId, ...props }) => { +const Breadcrumbs = ({ separator, rootPath, ...props }) => { const { override, rest } = useOverrides(props, overrides); const items = useMemo( () => getItems({ - rootId, + rootPath, separator, override, }), - [rootId, separator, override] + [rootPath, separator, override] ); return ( diff --git a/src/Breadcrumbs/props/propsDefault.js b/src/Breadcrumbs/props/propsDefault.js index 94e3ae6e..a42864d9 100644 --- a/src/Breadcrumbs/props/propsDefault.js +++ b/src/Breadcrumbs/props/propsDefault.js @@ -1,4 +1,3 @@ export default { - rootId: 'root', separator: '/', }; diff --git a/src/Breadcrumbs/props/propsInfo.js b/src/Breadcrumbs/props/propsInfo.js index 91e5a57d..b3445723 100644 --- a/src/Breadcrumbs/props/propsInfo.js +++ b/src/Breadcrumbs/props/propsInfo.js @@ -1,5 +1,5 @@ export default { - rootId: { + rootPath: { title: { en: 'Root page', ru: 'Корневая страница', diff --git a/src/Breadcrumbs/utils/getItems.js b/src/Breadcrumbs/utils/getItems.js index c057902a..f896bb69 100644 --- a/src/Breadcrumbs/utils/getItems.js +++ b/src/Breadcrumbs/utils/getItems.js @@ -2,8 +2,8 @@ import React from 'react'; import { Link, Text } from '@quarkly/widgets'; import getLinks from './getLinks'; -const getItems = ({ rootId, separator, override }) => { - const { links, url } = getLinks({ rootId }); +const getItems = ({ rootPath, separator, override }) => { + const { links, url } = getLinks(rootPath); return links.map((link, i) => { if (i !== links.length - 1) { diff --git a/src/Breadcrumbs/utils/getLinks.js b/src/Breadcrumbs/utils/getLinks.js index 6fec84d6..31136a94 100644 --- a/src/Breadcrumbs/utils/getLinks.js +++ b/src/Breadcrumbs/utils/getLinks.js @@ -1,8 +1,8 @@ -import { getAPI } from '../../utils'; +import { getAPI, getPagesIdsByPath } from '../../utils'; const isBrowser = typeof window !== 'undefined'; -const getLinks = ({ rootId }) => { +const getLinks = (path) => { const { pages, mode } = getAPI(); if (!isBrowser || !pages) @@ -11,7 +11,12 @@ const getLinks = ({ rootId }) => { url: [], }; - const rootPage = pages[rootId]; + const ids = getPagesIdsByPath(path); + const lastId = ids.pop(); + + if (!lastId) return { links: [], url: [] }; + + const rootPage = pages[lastId]; const names = Object.values(pages).reduce( (prev, curr) => ({ diff --git a/src/Menu.js b/src/Menu.js index 48c7b37a..b0421542 100644 --- a/src/Menu.js +++ b/src/Menu.js @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import atomize from '@quarkly/atomize'; import { useRouteMatch } from 'react-router-dom'; import { useMatch } from '@reach/router'; import { Link } from '@quarkly/widgets'; import { useOverrides } from '@quarkly/components'; +import { getPagesIdsByPath, getAPI } from './utils'; const overrides = { item: { @@ -41,15 +42,6 @@ const overrides = { const Ul = atomize.ul(); const Li = atomize.li(); -const getAPI = () => { - if (typeof window !== 'undefined') { - return window.QAPI || {}; - } - if (typeof global !== 'undefined') { - return global.QAPI || {}; - } - return {}; -}; const Item = ({ id, @@ -128,27 +120,15 @@ const Wrapper = ({ ); }; -const getParent = (pages, pageId) => { - if (!pageId || !pages[pageId]) return null; - - return Object.values(pages).find(({ children = [] }) => { - return children && Array.isArray(children) && children.includes(pageId); - }); -}; - -const Menu = ({ rootId, depth, 'exact-active-match': exact, ...props }) => { +const Menu = ({ rootPath, depth, 'exact-active-match': exact, ...props }) => { const { override, rest } = useOverrides(props, overrides, defaultProps); const pages = getAPI().pages || {}; - let path = []; - if (rootId !== 'root') { - let parent = pages[rootId]; + const [rootId, path] = useMemo(() => { + const p = rootPath ? getPagesIdsByPath(rootPath) : []; - while (parent && parent.id !== 'root') { - path = [parent.pageUrl, ...path]; - parent = getParent(pages, parent?.id); - } - } + return [p[p.length - 1] ?? 'root', p.map((id) => pages[id]?.pageUrl)]; + }, [rootPath, pages]); return ( { - if (typeof window !== 'undefined') { - return window.QAPI || {}; - } - if (typeof global !== 'undefined') { - return global.QAPI || {}; - } - return {}; -}; - -const getParent = (pages, pageId) => { - if (!pageId || !pages[pageId]) return null; - - return Object.values(pages).find( - ({ children = [] }) => - children && Array.isArray(children) && children.includes(pageId) - ); -}; - const Ul = atomize.ul(); const Li = atomize.li(); @@ -288,20 +270,15 @@ const List = ({ ); }; -const MenuWithGroups = ({ depth, rootId, expand, tabState, ...props }) => { +const MenuWithGroups = ({ depth, rootPath, expand, tabState, ...props }) => { const { override, rest } = useOverrides(props, overrides, defaultProps); const pages = getAPI().pages || {}; - let path = []; + const [rootId, path] = useMemo(() => { + const p = rootPath ? getPagesIdsByPath(rootPath) : []; - if (rootId !== 'root') { - let parent = pages[rootId]; - - while (parent && parent.id !== 'root') { - path = [parent.pageUrl, ...path]; - parent = getParent(pages, parent?.id); - } - } + return [p[p.length - 1] ?? 'root', p.map((id) => pages[id]?.pageUrl)]; + }, [rootPath, pages]); return ( { describe('parseTime', () => { @@ -12,4 +12,69 @@ describe('utils', () => { expect(parseTime(timeString)).toBe(timeInMs); }); }); + + describe('getPagesIdsByPath', () => { + window.QAPI = { + pages: { + root: { + id: 'root', + pageUrl: 'root', + name: 'root', + children: [ + '624314bfddbaf70020af1b89', + '62457b06ddbaf70020af2334', + '624314bfddbaf70020af1b8c', + ], + }, + '624314bfddbaf70020af1b89': { + id: '624314bfddbaf70020af1b89', + name: '404', + pageUrl: '404', + }, + '62457b06ddbaf70020af2334': { + id: '62457b06ddbaf70020af2334', + pageUrl: 'foo', + name: 'foo', + children: [ + '62457b255ac830002350df05', + '62457b15ddbaf70020af233e', + ], + }, + '62457b255ac830002350df05': { + id: '62457b255ac830002350df05', + pageUrl: 'bar', + name: 'bar', + }, + '62457b15ddbaf70020af233e': { + id: '62457b15ddbaf70020af233e', + pageUrl: 'baz', + name: 'baz', + }, + '624314bfddbaf70020af1b8c': { + id: '624314bfddbaf70020af1b8c', + pageUrl: 'qux', + name: 'qux', + }, + }, + }; + + test('correct', () => { + const res = getPagesIdsByPath('/foo/bar'); + + expect(res).toStrictEqual([ + '62457b06ddbaf70020af2334', + '62457b255ac830002350df05', + ]); + }); + + test('incorrect', () => { + const res = getPagesIdsByPath('/foo/bar/baz'); + + expect(res).toStrictEqual([ + '62457b06ddbaf70020af2334', + '62457b255ac830002350df05', + undefined, + ]); + }); + }); }); diff --git a/src/utils/getPagesIdsByPath.js b/src/utils/getPagesIdsByPath.js new file mode 100644 index 00000000..1f9d74c4 --- /dev/null +++ b/src/utils/getPagesIdsByPath.js @@ -0,0 +1,23 @@ +import getAPI from './getAPI'; + +const getPagesIdsByPath = (path) => { + const { pages } = getAPI(); + + if (!pages) return [undefined]; + + const pathArray = path.split('/'); + + const f = (page, i = 1) => { + if (!page || i >= pathArray.length) return []; + + const nextId = page?.children?.find( + (id) => pathArray[i] === pages[id]?.pageUrl + ); + + return [nextId, ...f(pages[nextId], i + 1)]; + }; + + return f(pages.root); +}; + +export default getPagesIdsByPath; diff --git a/src/utils/index.js b/src/utils/index.js index 55606324..2caae3c1 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -6,6 +6,7 @@ export { default as waitDOMLoaded } from './waitDOMLoaded'; export { default as pick } from './pick'; export { default as getAPI } from './getAPI'; +export { default as getPagesIdsByPath } from './getPagesIdsByPath'; export { default as useColor } from './useColor'; export { default as useDebounce } from './useDebounce';