[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
This commit is contained in:
parent
8a47bb4b02
commit
ca6490783f
|
@ -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 (
|
||||||
|
<nav className="flex items-center space-x-1">
|
||||||
|
{segments.map((segment, index) => {
|
||||||
|
// Builds the path up to the current segment
|
||||||
|
const path = `/${segments.slice(0, index + 1).join('/')}`
|
||||||
|
|
||||||
|
// Determines if the current segment is the last one
|
||||||
|
const isLastSegment = index === segments.length - 1
|
||||||
|
|
||||||
|
// If the first segment should be cut, skip it
|
||||||
|
if (cutFirstSegment && index === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row items-center" key={segment}>
|
||||||
|
{/* Always render the first chevron unless cutFirstSegment is true and we need to avoid render the chevron before */}
|
||||||
|
{!cutFirstSegment || (cutFirstSegment && index > 1) ? (
|
||||||
|
<ChevronRightIcon size={20} color="$neutral-40" />
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{isLastSegment ? (
|
||||||
|
<Text size={15} color="$neutral-50" weight="medium">
|
||||||
|
{formatSegment(segment)}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<Link href={path}>
|
||||||
|
<Text size={15} weight="medium">
|
||||||
|
{formatSegment(segment)}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Breadcrumbs }
|
|
@ -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 }
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { Breadcrumbs } from './breadcrumbs/breadcrumbs'
|
||||||
|
export { SideBar } from './side-bar/side-bar'
|
|
@ -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 (
|
||||||
|
<Link className="pl-5 transition-opacity hover:opacity-50" {...linkProps}>
|
||||||
|
<Text
|
||||||
|
size={19}
|
||||||
|
weight="medium"
|
||||||
|
color={active ? '$neutral-50' : '$neutral-100'}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { NavLink }
|
|
@ -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 (
|
||||||
|
<Accordion.Item value={label} className="accordion-item">
|
||||||
|
<div>
|
||||||
|
<Accordion.Trigger className="accordion-trigger">
|
||||||
|
<div className="accordion-chevron inline-flex h-5 w-5">
|
||||||
|
<ChevronRightIcon size={20} />
|
||||||
|
</div>
|
||||||
|
<Text size={19} weight="medium" color={'$neutral-100'}>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
</Accordion.Trigger>
|
||||||
|
<Accordion.Content className="accordion-content">
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
overflow: 'hidden',
|
||||||
|
paddingLeft: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{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 (
|
||||||
|
<div
|
||||||
|
key={link.label + index}
|
||||||
|
className={`transition-opacity hover:opacity-50 ${paddingClassName} ${paddingLastChild}`}
|
||||||
|
>
|
||||||
|
<Link href={link.href}>
|
||||||
|
<Text
|
||||||
|
size={15}
|
||||||
|
weight="medium"
|
||||||
|
color={active ? '$neutral-50' : '$neutral-100'}
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Text>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Accordion.Content>
|
||||||
|
</div>
|
||||||
|
</Accordion.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { NavNestedLinks }
|
|
@ -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<string>('')
|
||||||
|
|
||||||
|
const { asPath } = useRouter()
|
||||||
|
|
||||||
|
const defaultLabel = data?.find(
|
||||||
|
item =>
|
||||||
|
item.href === asPath || item.links?.find(link => link.href === asPath)
|
||||||
|
)?.label
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLabel(defaultLabel || '')
|
||||||
|
}, [defaultLabel])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border-neutral-10 border-r p-5">
|
||||||
|
<aside className=" sticky top-5 min-w-[320px]">
|
||||||
|
<Accordion.Root
|
||||||
|
type="single"
|
||||||
|
collapsible
|
||||||
|
value={label}
|
||||||
|
onValueChange={value => setLabel(value)}
|
||||||
|
className="accordion-root flex flex-col gap-3"
|
||||||
|
>
|
||||||
|
{data?.map((item, index) => {
|
||||||
|
if (item.links) {
|
||||||
|
return (
|
||||||
|
<NavNestedLinks
|
||||||
|
key={index}
|
||||||
|
label={item.label}
|
||||||
|
links={item.links}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion.Item
|
||||||
|
key={item.label}
|
||||||
|
value={item.label}
|
||||||
|
className="accordion-item"
|
||||||
|
>
|
||||||
|
<Accordion.Trigger
|
||||||
|
className="accordion-trigger"
|
||||||
|
onClick={() => setLabel(item.label)}
|
||||||
|
>
|
||||||
|
<NavLink href={item.href || ''}>{item.label}</NavLink>
|
||||||
|
</Accordion.Trigger>
|
||||||
|
</Accordion.Item>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Accordion.Root>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SideBar }
|
|
@ -1,42 +1,91 @@
|
||||||
import { Text } from '@status-im/components'
|
import { SideBar } from '../components'
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
|
|
||||||
import { Link } from '@/components/link'
|
|
||||||
|
|
||||||
import { AppLayout } from './app-layout'
|
import { AppLayout } from './app-layout'
|
||||||
|
|
||||||
import type { PageLayout } from 'next'
|
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 => {
|
export const InsightsLayout: PageLayout = page => {
|
||||||
return AppLayout(
|
return AppLayout(
|
||||||
<div className="bg-white-100 mx-1 grid min-h-[calc(100vh-56px-4px)] grid-cols-[320px_1fr] items-stretch rounded-3xl">
|
<div className="bg-white-100 relative mx-1 flex min-h-[calc(100vh-56px-4px)] rounded-3xl">
|
||||||
<aside className="border-neutral-10 flex flex-col gap-3 border-r p-5">
|
<SideBar data={MENU_LINKS} />
|
||||||
<NavLink href="/insights">Epics</NavLink>
|
<main className="flex-1">{page}</main>
|
||||||
<NavLink href="/insights/detail">Detail</NavLink>
|
|
||||||
<NavLink href="/insights/orphans">Orphans</NavLink>
|
|
||||||
<NavLink href="/insights/repos">Repos</NavLink>
|
|
||||||
</aside>
|
|
||||||
<main className="p-10">{page}</main>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const NavLink = (props: LinkProps & { children: string }) => {
|
|
||||||
const { children, ...linkProps } = props
|
|
||||||
|
|
||||||
const { asPath } = useRouter()
|
|
||||||
const active = asPath === props.href
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link className="transition-opacity hover:opacity-50" {...linkProps}>
|
|
||||||
<Text
|
|
||||||
size={19}
|
|
||||||
weight="medium"
|
|
||||||
color={active ? '$neutral-50' : '$neutral-100'}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Text>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import '@/styles/global.css'
|
import '@/styles/global.css'
|
||||||
|
import '@/styles/nav-nested-links.css'
|
||||||
|
|
||||||
import { Provider } from '@status-im/components'
|
import { Provider } from '@status-im/components'
|
||||||
import { Inter } from 'next/font/google'
|
import { Inter } from 'next/font/google'
|
||||||
|
|
|
@ -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 (
|
|
||||||
<>
|
|
||||||
<EpicOverview
|
|
||||||
title="Communities protocol"
|
|
||||||
description="Detecting keycard reader removal for the beginning of each flow"
|
|
||||||
fullscreen
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div role="separator" className="bg-neutral-10 -mx-6 my-6 h-px" />
|
|
||||||
|
|
||||||
<TableIssues />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
InsightsDetailPage.getLayout = InsightsLayout
|
|
||||||
|
|
||||||
export default InsightsDetailPage
|
|
|
@ -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 (
|
||||||
|
<div>
|
||||||
|
<div className="border-neutral-10 border-b px-5 py-3">
|
||||||
|
<Breadcrumbs />
|
||||||
|
</div>
|
||||||
|
<div className="px-10 py-6">
|
||||||
|
<EpicOverview
|
||||||
|
title="Communities protocol"
|
||||||
|
description="Detecting keycard reader removal for the beginning of each flow"
|
||||||
|
fullscreen
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div role="separator" className="bg-neutral-10 -mx-6 my-6 h-px" />
|
||||||
|
|
||||||
|
<TableIssues />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
EpicsDetailPage.getLayout = InsightsLayout
|
||||||
|
|
||||||
|
export default EpicsDetailPage
|
|
@ -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 (
|
||||||
|
<div className="space-y-4 p-10">
|
||||||
|
<Text size={27} weight="semibold">
|
||||||
|
Epics
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Tag size={32} label="In Progress" icon={OpenIcon} selected />
|
||||||
|
<Tag size={32} label="Closed" icon={DoneIcon} />
|
||||||
|
<Tag size={32} label="Not Started" icon={NotStartedIcon} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<IconButton variant="outline" icon={<SearchIcon size={20} />} />
|
||||||
|
<IconButton variant="outline" icon={<SortIcon size={20} />} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4">
|
||||||
|
{epics.map(epic => (
|
||||||
|
<Shadow key={epic.id} variant="$2" className="rounded-2xl px-4 py-3">
|
||||||
|
<EpicOverview title={epic.title} description={epic.description} />
|
||||||
|
</Shadow>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
EpicsPage.getLayout = InsightsLayout
|
||||||
|
|
||||||
|
export default EpicsPage
|
|
@ -1,62 +1,16 @@
|
||||||
import { IconButton, Shadow, Tag, Text } from '@status-im/components'
|
import { useEffect } from 'react'
|
||||||
import {
|
|
||||||
DoneIcon,
|
|
||||||
NotStartedIcon,
|
|
||||||
OpenIcon,
|
|
||||||
SearchIcon,
|
|
||||||
SortIcon,
|
|
||||||
} from '@status-im/icons'
|
|
||||||
|
|
||||||
import { EpicOverview } from '@/components/epic-overview'
|
import { useRouter } from 'next/router'
|
||||||
import { InsightsLayout } from '@/layouts/insights-layout'
|
|
||||||
|
|
||||||
import type { Page } from 'next'
|
export default function InsightsPage() {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const epics = [
|
useEffect(() => {
|
||||||
{
|
// Redirect to the epics page if the user lands on the insights page
|
||||||
id: 1,
|
if (router.pathname === '/insights') {
|
||||||
title: 'Communities protocol',
|
router.replace('/insights/epics')
|
||||||
description: 'Support Encrypted Communities',
|
}
|
||||||
},
|
}, [router])
|
||||||
{
|
|
||||||
id: 5155,
|
|
||||||
title: 'Keycard',
|
|
||||||
description:
|
|
||||||
'Detecting keycard reader removal for the beginning of each flow',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const InsightsPage: Page = () => {
|
return null
|
||||||
return (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<Text size={27} weight="semibold">
|
|
||||||
Epics
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Tag size={32} label="In Progress" icon={OpenIcon} selected />
|
|
||||||
<Tag size={32} label="Closed" icon={DoneIcon} />
|
|
||||||
<Tag size={32} label="Not Started" icon={NotStartedIcon} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<IconButton variant="outline" icon={<SearchIcon size={20} />} />
|
|
||||||
<IconButton variant="outline" icon={<SortIcon size={20} />} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-4">
|
|
||||||
{epics.map(epic => (
|
|
||||||
<Shadow key={epic.id} variant="$2" className="rounded-2xl px-4 py-3">
|
|
||||||
<EpicOverview title={epic.title} description={epic.description} />
|
|
||||||
</Shadow>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InsightsPage.getLayout = InsightsLayout
|
|
||||||
|
|
||||||
export default InsightsPage
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import type { Page } from 'next'
|
||||||
|
|
||||||
const OrphansPage: Page = () => {
|
const OrphansPage: Page = () => {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6 p-10">
|
||||||
<Text size={27} weight="semibold">
|
<Text size={27} weight="semibold">
|
||||||
Orphans
|
Orphans
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -46,7 +46,7 @@ const repos = [
|
||||||
|
|
||||||
const ReposPage: Page = () => {
|
const ReposPage: Page = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="p-10">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<Text size={27} weight="semibold">
|
<Text size={27} weight="semibold">
|
||||||
Repos
|
Repos
|
||||||
|
@ -81,7 +81,7 @@ const ReposPage: Page = () => {
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 (
|
||||||
|
<div>
|
||||||
|
<div className="border-neutral-10 border-b px-5 py-3">
|
||||||
|
<Breadcrumbs />
|
||||||
|
</div>
|
||||||
|
<div className="px-10 py-6">
|
||||||
|
<EpicOverview
|
||||||
|
title="Communities protocol"
|
||||||
|
description="Detecting keycard reader removal for the beginning of each flow"
|
||||||
|
fullscreen
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div role="separator" className="bg-neutral-10 -mx-6 my-6 h-px" />
|
||||||
|
|
||||||
|
<TableIssues />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkstreamDetailPage.getLayout = InsightsLayout
|
||||||
|
|
||||||
|
export default WorkstreamDetailPage
|
|
@ -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 (
|
||||||
|
<div className="space-y-4 p-10">
|
||||||
|
<Text size={27} weight="semibold">
|
||||||
|
Workstreams
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Tag size={32} label="In Progress" icon={OpenIcon} selected />
|
||||||
|
<Tag size={32} label="Closed" icon={DoneIcon} />
|
||||||
|
<Tag size={32} label="Not Started" icon={NotStartedIcon} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<IconButton variant="outline" icon={<SearchIcon size={20} />} />
|
||||||
|
<IconButton variant="outline" icon={<SortIcon size={20} />} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4">
|
||||||
|
{workstreams.map(workstream => (
|
||||||
|
<Shadow
|
||||||
|
key={workstream.id}
|
||||||
|
variant="$2"
|
||||||
|
className="rounded-2xl px-4 py-3"
|
||||||
|
>
|
||||||
|
<EpicOverview
|
||||||
|
title={workstream.title}
|
||||||
|
description={workstream.description}
|
||||||
|
/>
|
||||||
|
</Shadow>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkstreamsPage.getLayout = InsightsLayout
|
||||||
|
|
||||||
|
export default WorkstreamsPage
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue