Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(next): scaffolds admin layout and dashboard view #4566

Merged
merged 10 commits into from
Dec 20, 2023
8 changes: 8 additions & 0 deletions packages/dev/src/app/(payload)/admin/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
import { AdminLayout } from '@payloadcms/next/layouts/Admin'
import configPromise from 'payload-config'

export default async ({ children }: { children: React.ReactNode }) => (
<AdminLayout config={configPromise}>{children}</AdminLayout>
)
18 changes: 18 additions & 0 deletions packages/next/src/layouts/Admin/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react'
import { Default as DefaultTemplate } from '@payloadcms/ui/templates'
import { SanitizedConfig } from 'payload/types'
import Link from 'next/link'

import '@payloadcms/ui/scss/app.scss'

export const metadata = {
title: 'Next.js',
description: 'Generated by Next.js',
}

export const AdminLayout = async ({
children, // config: configPromise,
}: {
children: React.ReactNode
config: Promise<SanitizedConfig>
}) => <DefaultTemplate Link={Link}>{children}</DefaultTemplate>
7 changes: 5 additions & 2 deletions packages/ui/src/elements/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,13 @@ export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, Props>((

switch (el) {
case 'link':
if (!Link) throw new Error('Link is required when using el="link"')
if (!Link) {
console.error('Link is required when using el="link"')
return null
}

return (
<Link {...buttonProps} to={to || url}>
<Link {...buttonProps} to={to || url} href={to || url}>
<ButtonContents icon={icon} showTooltip={showTooltip} tooltip={tooltip}>
{children}
</ButtonContents>
Expand Down
11 changes: 8 additions & 3 deletions packages/ui/src/elements/Card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,27 @@ import './index.scss'
const baseClass = 'card'

export const Card: React.FC<Props> = (props) => {
const { id, actions, buttonAriaLabel, onClick, title, titleAs } = props
const { id, actions, buttonAriaLabel, onClick, title, titleAs, Link, href } = props

const classes = [baseClass, id, onClick && `${baseClass}--has-onclick`].filter(Boolean).join(' ')
const classes = [baseClass, id, (onClick || href) && `${baseClass}--has-onclick`]
.filter(Boolean)
.join(' ')

const Tag = titleAs ?? 'div'

return (
<div className={classes} id={id}>
<Tag className={`${baseClass}__title`}>{title}</Tag>
{actions && <div className={`${baseClass}__actions`}>{actions}</div>}
{onClick && (
{(onClick || href) && (
<Button
aria-label={buttonAriaLabel}
buttonStyle="none"
className={`${baseClass}__click`}
onClick={onClick}
Link={Link}
el="link"
to={href}
/>
)}
</div>
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/elements/Card/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ export type Props = {
onClick?: () => void
title: string
titleAs?: ElementType
Link?: ElementType
href?: string
}
16 changes: 10 additions & 6 deletions packages/ui/src/elements/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'

import Account from '../../graphics/Account'
import { useConfig } from '../../providers/Config'
Expand All @@ -15,7 +14,9 @@ import { useActions } from '../../providers/ActionsProvider'

const baseClass = 'app-header'

export const AppHeader: React.FC = () => {
export const AppHeader: React.FC<{
Link?: React.ComponentType
}> = ({ Link }) => {
const { t } = useTranslation()

const {
Expand Down Expand Up @@ -47,6 +48,8 @@ export const AppHeader: React.FC = () => {
}
}, [actions])

const LinkElement = Link || 'a'

return (
<header className={[baseClass, navOpen && `${baseClass}--nav-open`].filter(Boolean).join(' ')}>
<div className={`${baseClass}__bg`} />
Expand All @@ -57,7 +60,7 @@ export const AppHeader: React.FC = () => {
</NavToggler>
<div className={`${baseClass}__controls-wrapper`}>
<div className={`${baseClass}__step-nav-wrapper`}>
<StepNav className={`${baseClass}__step-nav`} />
<StepNav className={`${baseClass}__step-nav`} Link={Link} />
</div>
<div className={`${baseClass}__actions-wrapper`}>
<div className={`${baseClass}__actions`} ref={customControlsRef}>
Expand All @@ -78,14 +81,15 @@ export const AppHeader: React.FC = () => {
{localization && (
<LocalizerLabel ariaLabel="invisible" className={`${baseClass}__localizer-spacing`} />
)}
<Link
<LinkElement
aria-label={t('authentication:account')}
className={`${baseClass}__account`}
tabIndex={0}
to={`${adminRoute}/account`}
// to={`${adminRoute}/account`} // for `react-router-dom` Link
href={`${adminRoute}/account`} // for `next/link` Link
>
<Account />
</Link>
</LinkElement>
</div>
</div>
</div>
Expand Down
13 changes: 8 additions & 5 deletions packages/ui/src/elements/Logout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'

import { LogOut } from '../../icons/LogOut'
import { useConfig } from '../../providers/Config'
Expand All @@ -10,7 +9,8 @@ const baseClass = 'nav'

const DefaultLogout: React.FC<{
tabIndex?: number
}> = ({ tabIndex }) => {
Link: React.ComponentType
}> = ({ tabIndex, Link }) => {
const { t } = useTranslation('authentication')
const config = useConfig()

Expand All @@ -19,15 +19,18 @@ const DefaultLogout: React.FC<{
routes: { admin },
} = config

const LinkElement = Link || 'a'

return (
<Link
<LinkElement
aria-label={t('logOut')}
className={`${baseClass}__log-out`}
tabIndex={tabIndex}
to={`${admin}${logoutRoute}`}
// to={`${admin}${logoutRoute}`} // for `react-router-dom`
href={`${admin}${logoutRoute}`} // for `next/link`
>
<LogOut />
</Link>
</LinkElement>
)
}

Expand Down
17 changes: 11 additions & 6 deletions packages/ui/src/elements/Nav/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client'
import React, { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { NavLink } from 'react-router-dom'

import type { EntityToGroup, Group } from '../../utilities/groupNavItems'

Expand All @@ -19,7 +18,10 @@ import './index.scss'

const baseClass = 'nav'

const DefaultNav: React.FC = () => {
const DefaultNav: React.FC<{
Link?: React.ComponentType
}> = (props) => {
const { Link } = props
const { navOpen, navRef, setNavOpen } = useNav()
const { permissions, user } = useAuth()
const [groups, setGroups] = useState<Group[]>([])
Expand Down Expand Up @@ -97,20 +99,23 @@ const DefaultNav: React.FC = () => {
id = `nav-global-${entity.slug}`
}

const LinkElement = Link || 'a'

return (
<NavLink
activeClassName="active"
<LinkElement
// activeClassName="active"
className={`${baseClass}__link`}
id={id}
key={i}
tabIndex={!navOpen ? -1 : undefined}
to={href}
// to={href} // for `react-router-dom` Link
href={href} // for `next/link` Link
>
<span className={`${baseClass}__link-icon`}>
<Chevron direction="right" />
</span>
<span className={`${baseClass}__link-label`}>{entityLabel}</span>
</NavLink>
</LinkElement>
)
})}
</NavGroup>
Expand Down
25 changes: 18 additions & 7 deletions packages/ui/src/elements/StepNav/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client'
import React, { Fragment, createContext, useContext, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'

import type { Context as ContextType } from './types'

Expand Down Expand Up @@ -33,23 +32,32 @@ const useStepNav = (): ContextType => useContext(Context)

const StepNav: React.FC<{
className?: string
}> = (props) => {
const { className } = props
Link?: React.ComponentType
}> = ({ Link, className }) => {
const { i18n } = useTranslation()

const { stepNav } = useStepNav()

const config = useConfig()

const {
routes: { admin },
} = config

const LinkElement = Link || 'a'

return (
<Fragment>
{stepNav.length > 0 ? (
<nav className={[baseClass, className].filter(Boolean).join(' ')}>
<Link className={`${baseClass}__home`} tabIndex={0} to={admin}>
<LinkElement
className={`${baseClass}__home`}
tabIndex={0}
// to={admin} // for `react-router-dom`
href={admin} // for `next/link`
>
<IconGraphic />
</Link>
</LinkElement>
<span>/</span>
{stepNav.map((item, i) => {
const StepLabel = getTranslation(item.label, i18n)
Expand All @@ -62,9 +70,12 @@ const StepNav: React.FC<{
) : (
<Fragment key={i}>
{item.url ? (
<Link to={item.url}>
<LinkElement
// to={item.url} // for `react-router-dom`
href={item.url} // for `next/link`
>
<span key={i}>{StepLabel}</span>
</Link>
</LinkElement>
) : (
<span key={i}>{StepLabel}</span>
)}
Expand Down
9 changes: 5 additions & 4 deletions packages/ui/src/graphics/Account/index.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
'use client'
import React from 'react'
import { useLocation } from 'react-router-dom'

import { useAuth } from '../../providers/Auth'
import { useConfig } from '../../providers/Config'
import { DefaultAccountIcon } from './Default'
import Gravatar from './Gravatar'
import { usePathname } from 'next/navigation'

const Account = () => {
const {
admin: { avatar: Avatar },
routes: { admin: adminRoute },
} = useConfig()
const { user } = useAuth()
const location = useLocation()
const pathname = usePathname()

const isOnAccountPage = location.pathname === `${adminRoute}/account`
const isOnAccountPage = pathname === `${adminRoute}/account`

if (!user.email || Avatar === 'default') return <DefaultAccountIcon active={isOnAccountPage} />
if (!user?.email || Avatar === 'default') return <DefaultAccountIcon active={isOnAccountPage} />
if (Avatar === 'gravatar') return <Gravatar />
if (Avatar) return <Avatar active={isOnAccountPage} />
return <DefaultAccountIcon active={isOnAccountPage} />
Expand Down
8 changes: 2 additions & 6 deletions packages/ui/src/templates/Default/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
'use client'
import React, { Fragment } from 'react'
import { useTranslation } from 'react-i18next'

import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from '../../../../exports/types'
import type { Props } from './types'

import { Hamburger } from '../../elements/Hamburger'
Expand All @@ -16,7 +14,7 @@ import './index.scss'

const baseClass = 'template-default'

export const Default: React.FC<Props> = ({ children, className }) => {
export const Default: React.FC<Props> = ({ children, className, Link }) => {
const {
admin: {
components: { Nav: CustomNav } = {
Expand All @@ -25,8 +23,6 @@ export const Default: React.FC<Props> = ({ children, className }) => {
} = {},
} = useConfig()

const { t } = useTranslation('general')

const { navOpen } = useNav()

return (
Expand All @@ -43,7 +39,7 @@ export const Default: React.FC<Props> = ({ children, className }) => {
>
<RenderCustomComponent CustomComponent={CustomNav} DefaultComponent={DefaultNav} />
<div className={`${baseClass}__wrap`}>
<AppHeader />
<AppHeader Link={Link} />
{children}
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/templates/Default/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import type React from 'react'
export type Props = {
children?: React.ReactNode
className?: string
Link?: React.ComponentType<any>
}
Loading
Loading