From ca6490783fd6a60df8501401c0cc6cd8b346a772 Mon Sep 17 00:00:00 2001 From: marcelines Date: Wed, 24 May 2023 12:11:52 +0100 Subject: [PATCH] [website] Add sidebar menu and breadcrumbs (#408) * feat: create side-menu component for the website * feat: create index to export necessary components from website * fix: changes from review * feat: add breadcrumbs and refactor routes for insights --- .../components/breadcrumbs/breadcrumbs.tsx | 60 +++++++++ .../components/breadcrumbs/format-segment.ts | 15 +++ apps/website/src/components/index.tsx | 2 + .../src/components/side-bar/nav-link.tsx | 31 +++++ .../components/side-bar/nav-nested-links.tsx | 72 +++++++++++ .../src/components/side-bar/side-bar.tsx | 80 ++++++++++++ apps/website/src/layouts/insights-layout.tsx | 115 +++++++++++++----- apps/website/src/pages/_app.tsx | 1 + apps/website/src/pages/insights/[epic].tsx | 25 ---- .../src/pages/insights/epics/[epic].tsx | 31 +++++ .../src/pages/insights/epics/index.tsx | 62 ++++++++++ apps/website/src/pages/insights/index.tsx | 68 ++--------- apps/website/src/pages/insights/orphans.tsx | 2 +- apps/website/src/pages/insights/repos.tsx | 4 +- .../insights/workstreams/[workstream].tsx | 31 +++++ .../src/pages/insights/workstreams/index.tsx | 69 +++++++++++ apps/website/src/styles/nav-nested-links.css | 41 +++++++ 17 files changed, 591 insertions(+), 118 deletions(-) create mode 100644 apps/website/src/components/breadcrumbs/breadcrumbs.tsx create mode 100644 apps/website/src/components/breadcrumbs/format-segment.ts create mode 100644 apps/website/src/components/index.tsx create mode 100644 apps/website/src/components/side-bar/nav-link.tsx create mode 100644 apps/website/src/components/side-bar/nav-nested-links.tsx create mode 100644 apps/website/src/components/side-bar/side-bar.tsx delete mode 100644 apps/website/src/pages/insights/[epic].tsx create mode 100644 apps/website/src/pages/insights/epics/[epic].tsx create mode 100644 apps/website/src/pages/insights/epics/index.tsx create mode 100644 apps/website/src/pages/insights/workstreams/[workstream].tsx create mode 100644 apps/website/src/pages/insights/workstreams/index.tsx create mode 100644 apps/website/src/styles/nav-nested-links.css diff --git a/apps/website/src/components/breadcrumbs/breadcrumbs.tsx b/apps/website/src/components/breadcrumbs/breadcrumbs.tsx new file mode 100644 index 00000000..b5da3981 --- /dev/null +++ b/apps/website/src/components/breadcrumbs/breadcrumbs.tsx @@ -0,0 +1,60 @@ +import { Text } from '@status-im/components' +import { ChevronRightIcon } from '@status-im/icons' +import { useRouter } from 'next/router' + +import { Link } from '../link' +import { formatSegment } from './format-segment' + +type Props = { + cutFirstSegment?: boolean +} + +const Breadcrumbs = (props: Props) => { + const { cutFirstSegment = true } = props + + const router = useRouter() + const { asPath } = router + + // Splits the current path into segments + const segments = asPath.split('/').filter(segment => segment !== '') + + return ( + + ) +} + +export { Breadcrumbs } diff --git a/apps/website/src/components/breadcrumbs/format-segment.ts b/apps/website/src/components/breadcrumbs/format-segment.ts new file mode 100644 index 00000000..b09cc9a4 --- /dev/null +++ b/apps/website/src/components/breadcrumbs/format-segment.ts @@ -0,0 +1,15 @@ +/** + * Util function that formats a segment for the breadcrumbs + * @param segment - string to be formatted + * formatSegment @returns a formatted string with the first letter of each word capitalized and spaces instead of "-" + **/ +const formatSegment = (segment: string): string => { + // Replaces "-" with a space and capitalize the first letter of each word + const words = segment.split('-') + const formattedSegment = words + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' ') + return formattedSegment +} + +export { formatSegment } diff --git a/apps/website/src/components/index.tsx b/apps/website/src/components/index.tsx new file mode 100644 index 00000000..096e0f20 --- /dev/null +++ b/apps/website/src/components/index.tsx @@ -0,0 +1,2 @@ +export { Breadcrumbs } from './breadcrumbs/breadcrumbs' +export { SideBar } from './side-bar/side-bar' diff --git a/apps/website/src/components/side-bar/nav-link.tsx b/apps/website/src/components/side-bar/nav-link.tsx new file mode 100644 index 00000000..dc06f505 --- /dev/null +++ b/apps/website/src/components/side-bar/nav-link.tsx @@ -0,0 +1,31 @@ +import { Text } from '@status-im/components' +import { useRouter } from 'next/router' + +import { Link } from '../link' + +import type { LinkProps } from 'next/link' + +const NavLink = ( + props: LinkProps & { + children: string + } +) => { + const { children, ...linkProps } = props + + const { asPath } = useRouter() + const active = asPath === props.href + + return ( + + + {children} + + + ) +} + +export { NavLink } diff --git a/apps/website/src/components/side-bar/nav-nested-links.tsx b/apps/website/src/components/side-bar/nav-nested-links.tsx new file mode 100644 index 00000000..01b721a5 --- /dev/null +++ b/apps/website/src/components/side-bar/nav-nested-links.tsx @@ -0,0 +1,72 @@ +import * as Accordion from '@radix-ui/react-accordion' +import {} from '@radix-ui/react-accordion' +import { Text } from '@status-im/components' +import { ChevronRightIcon } from '@status-im/icons' +import { useRouter } from 'next/router' + +import { Link } from '../link' + +import type { Url } from 'next/dist/shared/lib/router/router' + +type NavLinkProps = { + label: string + links: { + label: string + href: Url + }[] +} + +const NavNestedLinks = (props: NavLinkProps) => { + const { label, links } = props + + const { asPath } = useRouter() + + return ( + +
+ +
+ +
+ + {label} + +
+ +
+ {links.map((link, index) => { + const active = asPath === link.href + + const paddingClassName = index === 0 ? 'pt-5' : 'pt-2' + const paddingLastChild = index === links.length - 1 ? 'pb-5' : '' + + return ( +
+ + + {link.label} + + +
+ ) + })} +
+
+
+
+ ) +} + +export { NavNestedLinks } diff --git a/apps/website/src/components/side-bar/side-bar.tsx b/apps/website/src/components/side-bar/side-bar.tsx new file mode 100644 index 00000000..754f2101 --- /dev/null +++ b/apps/website/src/components/side-bar/side-bar.tsx @@ -0,0 +1,80 @@ +import { useEffect, useState } from 'react' + +import * as Accordion from '@radix-ui/react-accordion' +import { useRouter } from 'next/router' + +import { NavLink } from './nav-link' +import { NavNestedLinks } from './nav-nested-links' + +import type { Url } from 'next/dist/shared/lib/router/router' + +type Props = { + data?: { + label: string + href?: Url + links?: { + label: string + href: Url + }[] + }[] +} + +const SideBar = (props: Props) => { + const { data } = props + + const [label, setLabel] = useState('') + + const { asPath } = useRouter() + + const defaultLabel = data?.find( + item => + item.href === asPath || item.links?.find(link => link.href === asPath) + )?.label + + useEffect(() => { + setLabel(defaultLabel || '') + }, [defaultLabel]) + + return ( +
+ +
+ ) +} + +export { SideBar } diff --git a/apps/website/src/layouts/insights-layout.tsx b/apps/website/src/layouts/insights-layout.tsx index e11faf80..0f89e4e2 100644 --- a/apps/website/src/layouts/insights-layout.tsx +++ b/apps/website/src/layouts/insights-layout.tsx @@ -1,42 +1,91 @@ -import { Text } from '@status-im/components' -import { useRouter } from 'next/router' - -import { Link } from '@/components/link' - +import { SideBar } from '../components' import { AppLayout } from './app-layout' import type { PageLayout } from 'next' -import type { LinkProps } from 'next/link' + +// Eventually this will be fetched from the API, at least the nested links +const MENU_LINKS = [ + { + label: 'Epics', + links: [ + { + label: 'Overview', + href: '/insights/epics', + }, + { + label: 'Community Protocol', + href: '/insights/epics/community-protocol', + }, + { + label: 'Keycard', + href: '/insights/epics/keycard', + }, + { + label: 'Notifications Settings', + href: '/insights/epics/notifications-settings', + }, + { + label: 'Wallet', + href: '/insights/epics/wallet', + }, + { + label: 'Communities', + href: '/insights/epics/communities', + }, + { + label: 'Acitivity Center', + href: '/insights/epics/activity-center', + }, + ], + }, + { + label: 'Workstreams', + links: [ + { + label: 'Overview', + href: '/insights/workstreams', + }, + { + label: 'Community Protocol 2', + href: '/insights/workstreams/community-protocol-2', + }, + { + label: 'Keycard 2', + href: '/insights/workstreams/keycard-2', + }, + { + label: 'Notifications Settings 2', + href: '/insights/workstreams/notifications-settings-2', + }, + { + label: 'Wallet 2', + href: '/insights/workstreams/wallet-2', + }, + { + label: 'Communities 2', + href: '/insights/workstreams/communities-2', + }, + { + label: 'Acitivity Center 2', + href: '/insights/workstreams/activity-center-2', + }, + ], + }, + { + label: 'Orphans', + href: '/insights/orphans', + }, + { + label: 'Repos', + href: '/insights/repos', + }, +] export const InsightsLayout: PageLayout = page => { return AppLayout( -
- -
{page}
+
+ +
{page}
) } - -const NavLink = (props: LinkProps & { children: string }) => { - const { children, ...linkProps } = props - - const { asPath } = useRouter() - const active = asPath === props.href - - return ( - - - {children} - - - ) -} diff --git a/apps/website/src/pages/_app.tsx b/apps/website/src/pages/_app.tsx index c8d00ecb..75174466 100644 --- a/apps/website/src/pages/_app.tsx +++ b/apps/website/src/pages/_app.tsx @@ -1,4 +1,5 @@ import '@/styles/global.css' +import '@/styles/nav-nested-links.css' import { Provider } from '@status-im/components' import { Inter } from 'next/font/google' diff --git a/apps/website/src/pages/insights/[epic].tsx b/apps/website/src/pages/insights/[epic].tsx deleted file mode 100644 index 68a822e4..00000000 --- a/apps/website/src/pages/insights/[epic].tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { EpicOverview } from '@/components/epic-overview' -import { TableIssues } from '@/components/table-issues' -import { InsightsLayout } from '@/layouts/insights-layout' - -import type { Page } from 'next' - -const InsightsDetailPage: Page = () => { - return ( - <> - - -
- - - - ) -} - -InsightsDetailPage.getLayout = InsightsLayout - -export default InsightsDetailPage diff --git a/apps/website/src/pages/insights/epics/[epic].tsx b/apps/website/src/pages/insights/epics/[epic].tsx new file mode 100644 index 00000000..9d2cbaa7 --- /dev/null +++ b/apps/website/src/pages/insights/epics/[epic].tsx @@ -0,0 +1,31 @@ +import { Breadcrumbs } from '@/components' +import { EpicOverview } from '@/components/epic-overview' +import { TableIssues } from '@/components/table-issues' +import { InsightsLayout } from '@/layouts/insights-layout' + +import type { Page } from 'next' + +const EpicsDetailPage: Page = () => { + return ( +
+
+ +
+
+ + +
+ + +
+
+ ) +} + +EpicsDetailPage.getLayout = InsightsLayout + +export default EpicsDetailPage diff --git a/apps/website/src/pages/insights/epics/index.tsx b/apps/website/src/pages/insights/epics/index.tsx new file mode 100644 index 00000000..ac9cc533 --- /dev/null +++ b/apps/website/src/pages/insights/epics/index.tsx @@ -0,0 +1,62 @@ +import { IconButton, Shadow, Tag, Text } from '@status-im/components' +import { + DoneIcon, + NotStartedIcon, + OpenIcon, + SearchIcon, + SortIcon, +} from '@status-im/icons' + +import { EpicOverview } from '@/components/epic-overview' +import { InsightsLayout } from '@/layouts/insights-layout' + +import type { Page } from 'next' + +const epics = [ + { + id: 1, + title: 'Communities protocol', + description: 'Support Encrypted Communities', + }, + { + id: 5155, + title: 'Keycard', + description: + 'Detecting keycard reader removal for the beginning of each flow', + }, +] + +const EpicsPage: Page = () => { + return ( +
+ + Epics + + +
+
+ + + +
+ +
+ } /> + } /> +
+
+ +
+ {epics.map(epic => ( + + + + ))} +
+
+ ) +} + +EpicsPage.getLayout = InsightsLayout + +export default EpicsPage diff --git a/apps/website/src/pages/insights/index.tsx b/apps/website/src/pages/insights/index.tsx index 4a47fdea..cd883686 100644 --- a/apps/website/src/pages/insights/index.tsx +++ b/apps/website/src/pages/insights/index.tsx @@ -1,62 +1,16 @@ -import { IconButton, Shadow, Tag, Text } from '@status-im/components' -import { - DoneIcon, - NotStartedIcon, - OpenIcon, - SearchIcon, - SortIcon, -} from '@status-im/icons' +import { useEffect } from 'react' -import { EpicOverview } from '@/components/epic-overview' -import { InsightsLayout } from '@/layouts/insights-layout' +import { useRouter } from 'next/router' -import type { Page } from 'next' +export default function InsightsPage() { + const router = useRouter() -const epics = [ - { - id: 1, - title: 'Communities protocol', - description: 'Support Encrypted Communities', - }, - { - id: 5155, - title: 'Keycard', - description: - 'Detecting keycard reader removal for the beginning of each flow', - }, -] + useEffect(() => { + // Redirect to the epics page if the user lands on the insights page + if (router.pathname === '/insights') { + router.replace('/insights/epics') + } + }, [router]) -const InsightsPage: Page = () => { - return ( -
- - Epics - - -
-
- - - -
- -
- } /> - } /> -
-
- -
- {epics.map(epic => ( - - - - ))} -
-
- ) + return null } - -InsightsPage.getLayout = InsightsLayout - -export default InsightsPage diff --git a/apps/website/src/pages/insights/orphans.tsx b/apps/website/src/pages/insights/orphans.tsx index 57d5b0d7..3a92a530 100644 --- a/apps/website/src/pages/insights/orphans.tsx +++ b/apps/website/src/pages/insights/orphans.tsx @@ -7,7 +7,7 @@ import type { Page } from 'next' const OrphansPage: Page = () => { return ( -
+
Orphans diff --git a/apps/website/src/pages/insights/repos.tsx b/apps/website/src/pages/insights/repos.tsx index 3c34f36a..af185535 100644 --- a/apps/website/src/pages/insights/repos.tsx +++ b/apps/website/src/pages/insights/repos.tsx @@ -46,7 +46,7 @@ const repos = [ const ReposPage: Page = () => { return ( - <> +
Repos @@ -81,7 +81,7 @@ const ReposPage: Page = () => { ))}
- +
) } diff --git a/apps/website/src/pages/insights/workstreams/[workstream].tsx b/apps/website/src/pages/insights/workstreams/[workstream].tsx new file mode 100644 index 00000000..996acfad --- /dev/null +++ b/apps/website/src/pages/insights/workstreams/[workstream].tsx @@ -0,0 +1,31 @@ +import { Breadcrumbs } from '@/components' +import { EpicOverview } from '@/components/epic-overview' +import { TableIssues } from '@/components/table-issues' +import { InsightsLayout } from '@/layouts/insights-layout' + +import type { Page } from 'next' + +const WorkstreamDetailPage: Page = () => { + return ( +
+
+ +
+
+ + +
+ + +
+
+ ) +} + +WorkstreamDetailPage.getLayout = InsightsLayout + +export default WorkstreamDetailPage diff --git a/apps/website/src/pages/insights/workstreams/index.tsx b/apps/website/src/pages/insights/workstreams/index.tsx new file mode 100644 index 00000000..063ad4a4 --- /dev/null +++ b/apps/website/src/pages/insights/workstreams/index.tsx @@ -0,0 +1,69 @@ +import { IconButton, Shadow, Tag, Text } from '@status-im/components' +import { + DoneIcon, + NotStartedIcon, + OpenIcon, + SearchIcon, + SortIcon, +} from '@status-im/icons' + +import { EpicOverview } from '@/components/epic-overview' +import { InsightsLayout } from '@/layouts/insights-layout' + +import type { Page } from 'next' + +const workstreams = [ + { + id: 1, + title: 'Workstream protocol', + description: 'Support Encrypted Communities', + }, + { + id: 5155, + title: 'Work with keys', + description: + 'Detecting keycard reader removal for the beginning of each flow', + }, +] + +const WorkstreamsPage: Page = () => { + return ( +
+ + Workstreams + + +
+
+ + + +
+ +
+ } /> + } /> +
+
+ +
+ {workstreams.map(workstream => ( + + + + ))} +
+
+ ) +} + +WorkstreamsPage.getLayout = InsightsLayout + +export default WorkstreamsPage diff --git a/apps/website/src/styles/nav-nested-links.css b/apps/website/src/styles/nav-nested-links.css new file mode 100644 index 00000000..f9e9cfec --- /dev/null +++ b/apps/website/src/styles/nav-nested-links.css @@ -0,0 +1,41 @@ +.accordion-trigger { + flex: 1; + display: flex; + align-items: center; + cursor: pointer; +} + +.accordion-content { + overflow: hidden; +} +.accordion-content[data-state='open'] { + animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1); +} +.accordion-content[data-state='closed'] { + animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1); +} + +.accordion-chevron { + transition: transform 300ms cubic-bezier(0.87, 0, 0.13, 1); +} +.accordion-trigger[data-state='open'] > .accordion-chevron { + transform: rotate(90deg); +} + +@keyframes slideDown { + from { + height: 0; + } + to { + height: var(--radix-accordion-content-height); + } +} + +@keyframes slideUp { + from { + height: var(--radix-accordion-content-height); + } + to { + height: 0; + } +}