refactor(ui): deprecates Link props (#11155)
Deprecates all cases where `Link` could be sent as a prop. This was a relic from the past, where we attempted to make our UI library router-agnostic. This was a pipe dream and created more problems than it solved, for example the logout button was missing this prop, causing it to render an anchor tag and perform a hard navigation (caught in #9275). Does so in a non-breaking way, where these props are now optional and simply unused, as opposed to removing them outright.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { EntityToGroup } from '@payloadcms/ui/shared'
|
||||
import type { ServerProps } from 'payload'
|
||||
|
||||
import { Link, Logout } from '@payloadcms/ui'
|
||||
import { Logout } from '@payloadcms/ui'
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
import { EntityType, groupNavItems } from '@payloadcms/ui/shared'
|
||||
import React from 'react'
|
||||
@@ -73,7 +73,6 @@ export const DefaultNav: React.FC<NavProps> = async (props) => {
|
||||
const LogoutComponent = RenderServerComponent({
|
||||
clientProps: {
|
||||
documentSubViewType,
|
||||
Link,
|
||||
viewType,
|
||||
},
|
||||
Component: logout?.Button,
|
||||
|
||||
@@ -17,7 +17,12 @@ export type DashboardProps = {
|
||||
lockDuration?: number
|
||||
slug: string
|
||||
}>
|
||||
Link: React.ComponentType<any>
|
||||
/**
|
||||
* @deprecated
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
* Components now import their own `Link` directly from `next/link`.
|
||||
*/
|
||||
Link?: React.ComponentType
|
||||
navGroups?: ReturnType<typeof groupNavItems>
|
||||
permissions: SanitizedPermissions
|
||||
visibleEntities: VisibleEntities
|
||||
@@ -28,7 +33,6 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
|
||||
globalData,
|
||||
i18n,
|
||||
i18n: { t },
|
||||
Link,
|
||||
locale,
|
||||
navGroups,
|
||||
params,
|
||||
@@ -146,7 +150,6 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
|
||||
el="link"
|
||||
icon="plus"
|
||||
iconStyle="with-border"
|
||||
Link={Link}
|
||||
round
|
||||
to={createHREF}
|
||||
/>
|
||||
@@ -155,7 +158,6 @@ export const DefaultDashboard: React.FC<DashboardProps> = (props) => {
|
||||
buttonAriaLabel={buttonAriaLabel}
|
||||
href={href}
|
||||
id={`card-${slug}`}
|
||||
Link={Link}
|
||||
title={getTranslation(label, i18n)}
|
||||
titleAs="h3"
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { EntityToGroup } from '@payloadcms/ui/shared'
|
||||
import type { AdminViewProps } from 'payload'
|
||||
|
||||
import { HydrateAuthProvider, Link, SetStepNav } from '@payloadcms/ui'
|
||||
import { HydrateAuthProvider, SetStepNav } from '@payloadcms/ui'
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
import { EntityType, groupNavItems } from '@payloadcms/ui/shared'
|
||||
import React, { Fragment } from 'react'
|
||||
@@ -107,7 +107,6 @@ export const Dashboard: React.FC<AdminViewProps> = async ({
|
||||
<SetStepNav nav={[]} />
|
||||
{RenderServerComponent({
|
||||
clientProps: {
|
||||
Link,
|
||||
locale,
|
||||
},
|
||||
Component: config.admin?.components?.views?.dashboard?.Component,
|
||||
|
||||
@@ -52,7 +52,7 @@ export const ForgotPasswordView: React.FC<AdminViewProps> = ({ initPageResult })
|
||||
}
|
||||
heading={i18n.t('authentication:alreadyLoggedIn')}
|
||||
/>
|
||||
<Button buttonStyle="secondary" el="link" Link={Link} size="large" to={adminRoute}>
|
||||
<Button buttonStyle="secondary" el="link" size="large" to={adminRoute}>
|
||||
{i18n.t('general:backToDashboard')}
|
||||
</Button>
|
||||
</Fragment>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
import {
|
||||
Button,
|
||||
Link,
|
||||
LoadingOverlay,
|
||||
toast,
|
||||
useAuth,
|
||||
@@ -69,7 +68,7 @@ export const LogoutClient: React.FC<{
|
||||
return (
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<h2>{t('authentication:loggedOutInactivity')}</h2>
|
||||
<Button buttonStyle="secondary" el="link" Link={Link} size="large" url={loginRoute}>
|
||||
<Button buttonStyle="secondary" el="link" size="large" url={loginRoute}>
|
||||
{t('authentication:logBackIn')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import { Button, Gutter, Link, useConfig, useStepNav, useTranslation } from '@payloadcms/ui'
|
||||
import { Button, Gutter, useConfig, useStepNav, useTranslation } from '@payloadcms/ui'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
import './index.scss'
|
||||
@@ -39,13 +39,7 @@ export const NotFoundClient: React.FC<{
|
||||
<h1>{t('general:nothingFound')}</h1>
|
||||
<p>{t('general:sorryNotFound')}</p>
|
||||
</div>
|
||||
<Button
|
||||
className={`${baseClass}__button`}
|
||||
el="link"
|
||||
Link={Link}
|
||||
size="large"
|
||||
to={adminRoute}
|
||||
>
|
||||
<Button className={`${baseClass}__button`} el="link" size="large" to={adminRoute}>
|
||||
{t('general:backToDashboard')}
|
||||
</Button>
|
||||
</Gutter>
|
||||
|
||||
@@ -5,8 +5,8 @@ import { formatAdminURL, Translation } from '@payloadcms/ui/shared'
|
||||
import React from 'react'
|
||||
|
||||
import { FormHeader } from '../../elements/FormHeader/index.js'
|
||||
import './index.scss'
|
||||
import { ResetPasswordForm } from './ResetPasswordForm/index.js'
|
||||
import './index.scss'
|
||||
|
||||
export const resetPasswordBaseClass = 'reset-password'
|
||||
|
||||
@@ -57,7 +57,7 @@ export const ResetPassword: React.FC<AdminViewProps> = ({ initPageResult, params
|
||||
}
|
||||
heading={i18n.t('authentication:alreadyLoggedIn')}
|
||||
/>
|
||||
<Button buttonStyle="secondary" el="link" Link={Link} size="large" to={adminRoute}>
|
||||
<Button buttonStyle="secondary" el="link" size="large" to={adminRoute}>
|
||||
{i18n.t('general:backToDashboard')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { AdminViewComponent, PayloadServerReactComponent } from 'payload'
|
||||
|
||||
import { Button, Link } from '@payloadcms/ui'
|
||||
import { Button } from '@payloadcms/ui'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import React from 'react'
|
||||
|
||||
@@ -41,7 +41,6 @@ export const UnauthorizedView: PayloadServerReactComponent<AdminViewComponent> =
|
||||
<Button
|
||||
className={`${baseClass}__button`}
|
||||
el="link"
|
||||
Link={Link}
|
||||
size="large"
|
||||
to={formatAdminURL({
|
||||
adminRoute,
|
||||
|
||||
@@ -13,8 +13,8 @@ import { LocalizerLabel } from '../Localizer/LocalizerLabel/index.js'
|
||||
import { useNav } from '../Nav/context.js'
|
||||
import { NavToggler } from '../Nav/NavToggler/index.js'
|
||||
import { RenderCustomComponent } from '../RenderCustomComponent/index.js'
|
||||
import './index.scss'
|
||||
import { StepNav } from '../StepNav/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'app-header'
|
||||
|
||||
@@ -73,7 +73,7 @@ export function AppHeader({ CustomAvatar, CustomIcon }: Props) {
|
||||
</NavToggler>
|
||||
<div className={`${baseClass}__controls-wrapper`}>
|
||||
<div className={`${baseClass}__step-nav-wrapper`}>
|
||||
<StepNav className={`${baseClass}__step-nav`} CustomIcon={CustomIcon} Link={Link} />
|
||||
<StepNav className={`${baseClass}__step-nav`} CustomIcon={CustomIcon} />
|
||||
</div>
|
||||
<div className={`${baseClass}__actions-wrapper`}>
|
||||
<div className={`${baseClass}__actions`} ref={customControlsRef}>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { LinkIcon } from '../../icons/Link/index.js'
|
||||
import { PlusIcon } from '../../icons/Plus/index.js'
|
||||
import { SwapIcon } from '../../icons/Swap/index.js'
|
||||
import { XIcon } from '../../icons/X/index.js'
|
||||
import { Link } from '../Link/index.js'
|
||||
import { Popup } from '../Popup/index.js'
|
||||
import { Tooltip } from '../Tooltip/index.js'
|
||||
import './index.scss'
|
||||
@@ -61,7 +62,6 @@ export const Button: React.FC<Props> = (props) => {
|
||||
icon,
|
||||
iconPosition = 'right',
|
||||
iconStyle = 'without-border',
|
||||
Link,
|
||||
newTab,
|
||||
onClick,
|
||||
onMouseDown,
|
||||
@@ -143,26 +143,24 @@ export const Button: React.FC<Props> = (props) => {
|
||||
break
|
||||
|
||||
case 'link':
|
||||
if (!Link) {
|
||||
console.error('Link is required when using el="link"', children)
|
||||
return null
|
||||
}
|
||||
|
||||
let LinkTag = Link // eslint-disable-line no-case-declarations
|
||||
|
||||
if (disabled) {
|
||||
LinkTag = 'div'
|
||||
} else {
|
||||
prefetch = false
|
||||
}
|
||||
|
||||
buttonElement = (
|
||||
<LinkTag {...buttonProps} href={to || url} prefetch={prefetch} to={to || url}>
|
||||
<div {...buttonProps}>
|
||||
<ButtonContents icon={icon} showTooltip={showTooltip} tooltip={tooltip}>
|
||||
{children}
|
||||
</ButtonContents>
|
||||
</LinkTag>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
buttonElement = (
|
||||
<Link {...buttonProps} href={to || url} prefetch={prefetch}>
|
||||
<ButtonContents icon={icon} showTooltip={showTooltip} tooltip={tooltip}>
|
||||
{children}
|
||||
</ButtonContents>
|
||||
</Link>
|
||||
)
|
||||
|
||||
break
|
||||
|
||||
default:
|
||||
|
||||
@@ -22,6 +22,11 @@ export type Props = {
|
||||
iconPosition?: 'left' | 'right'
|
||||
iconStyle?: 'none' | 'with-border' | 'without-border'
|
||||
id?: string
|
||||
/**
|
||||
* @deprecated
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
* Components now import their own `Link` directly from `next/link`.
|
||||
*/
|
||||
Link?: React.ElementType
|
||||
newTab?: boolean
|
||||
onClick?: (event: MouseEvent) => void
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import type { ElementType } from 'react'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
@@ -11,16 +10,21 @@ export type Props = {
|
||||
buttonAriaLabel?: string
|
||||
href?: string
|
||||
id?: string
|
||||
Link?: ElementType
|
||||
/**
|
||||
* @deprecated
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
* Components now import their own `Link` directly from `next/link`.
|
||||
*/
|
||||
Link?: React.ElementType
|
||||
onClick?: () => void
|
||||
title: string
|
||||
titleAs?: ElementType
|
||||
titleAs?: React.ElementType
|
||||
}
|
||||
|
||||
const baseClass = 'card'
|
||||
|
||||
export const Card: React.FC<Props> = (props) => {
|
||||
const { id, actions, buttonAriaLabel, href, Link, onClick, title, titleAs } = props
|
||||
const { id, actions, buttonAriaLabel, href, onClick, title, titleAs } = props
|
||||
|
||||
const classes = [baseClass, id, (onClick || href) && `${baseClass}--has-onclick`]
|
||||
.filter(Boolean)
|
||||
@@ -38,7 +42,6 @@ export const Card: React.FC<Props> = (props) => {
|
||||
buttonStyle="none"
|
||||
className={`${baseClass}__click`}
|
||||
el="link"
|
||||
Link={Link}
|
||||
onClick={onClick}
|
||||
to={href}
|
||||
/>
|
||||
|
||||
@@ -5,13 +5,19 @@ import { LogOutIcon } from '../../icons/LogOut/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { formatAdminURL } from '../../utilities/formatAdminURL.js'
|
||||
import { Link } from '../Link/index.js'
|
||||
|
||||
const baseClass = 'nav'
|
||||
|
||||
export const Logout: React.FC<{
|
||||
/**
|
||||
* @deprecated
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
* Components now import their own `Link` directly from `next/link`.
|
||||
*/
|
||||
Link?: React.ComponentType
|
||||
tabIndex?: number
|
||||
}> = ({ Link, tabIndex = 0 }) => {
|
||||
}> = ({ tabIndex = 0 }) => {
|
||||
const { t } = useTranslation()
|
||||
const { config } = useConfig()
|
||||
|
||||
@@ -23,7 +29,6 @@ export const Logout: React.FC<{
|
||||
} = config
|
||||
|
||||
const basePath = process.env.NEXT_BASE_PATH ?? ''
|
||||
const LinkElement = Link || 'a'
|
||||
|
||||
const props = {
|
||||
'aria-label': t('authentication:logOut'),
|
||||
@@ -34,7 +39,7 @@ export const Logout: React.FC<{
|
||||
}
|
||||
|
||||
return (
|
||||
<LinkElement
|
||||
<Link
|
||||
{...props}
|
||||
href={formatAdminURL({
|
||||
adminRoute,
|
||||
@@ -43,6 +48,6 @@ export const Logout: React.FC<{
|
||||
})}
|
||||
>
|
||||
<LogOutIcon />
|
||||
</LinkElement>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { StepNavItem } from './types.js'
|
||||
import { PayloadIcon } from '../../graphics/Icon/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { Link } from '../Link/index.js'
|
||||
import { RenderCustomComponent } from '../RenderCustomComponent/index.js'
|
||||
import { StepNavProvider, useStepNav } from './context.js'
|
||||
import './index.scss'
|
||||
@@ -19,8 +20,13 @@ const baseClass = 'step-nav'
|
||||
const StepNav: React.FC<{
|
||||
readonly className?: string
|
||||
readonly CustomIcon?: React.ReactNode
|
||||
/**
|
||||
* @deprecated
|
||||
* This prop is deprecated and will be removed in the next major version.
|
||||
* Components now import their own `Link` directly from `next/link`.
|
||||
*/
|
||||
readonly Link?: React.ComponentType
|
||||
}> = ({ className, CustomIcon, Link }) => {
|
||||
}> = ({ className, CustomIcon }) => {
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const { stepNav } = useStepNav()
|
||||
@@ -33,26 +39,15 @@ const StepNav: React.FC<{
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const LinkElement = Link || 'a'
|
||||
|
||||
const baseLinkProps = {
|
||||
prefetch: Link ? false : undefined,
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{stepNav.length > 0 ? (
|
||||
<nav className={[baseClass, className].filter(Boolean).join(' ')}>
|
||||
<LinkElement
|
||||
className={`${baseClass}__home`}
|
||||
href={admin}
|
||||
tabIndex={0}
|
||||
{...baseLinkProps}
|
||||
>
|
||||
<Link className={`${baseClass}__home`} href={admin} prefetch={false} tabIndex={0}>
|
||||
<span title={t('general:dashboard')}>
|
||||
<RenderCustomComponent CustomComponent={CustomIcon} Fallback={<PayloadIcon />} />
|
||||
</span>
|
||||
</LinkElement>
|
||||
</Link>
|
||||
<span>/</span>
|
||||
{stepNav.map((item, i) => {
|
||||
const StepLabel = getTranslation(item.label, i18n)
|
||||
@@ -65,9 +60,9 @@ const StepNav: React.FC<{
|
||||
) : (
|
||||
<Fragment key={i}>
|
||||
{item.url ? (
|
||||
<LinkElement href={item.url} {...baseLinkProps}>
|
||||
<Link href={item.url} prefetch={false}>
|
||||
<span key={i}>{StepLabel}</span>
|
||||
</LinkElement>
|
||||
</Link>
|
||||
) : (
|
||||
<span key={i}>{StepLabel}</span>
|
||||
)}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { getTranslation } from '@payloadcms/translations'
|
||||
import React from 'react'
|
||||
|
||||
import { Button } from '../../../elements/Button/index.js'
|
||||
import { Link } from '../../../elements/Link/index.js'
|
||||
import { useListDrawerContext } from '../../../elements/ListDrawer/Provider.js'
|
||||
import { ListSelection } from '../../../elements/ListSelection/index.js'
|
||||
import { Pill } from '../../../elements/Pill/index.js'
|
||||
@@ -55,7 +54,6 @@ const DefaultListHeader: React.FC<ListHeaderProps> = ({
|
||||
})}
|
||||
buttonStyle="pill"
|
||||
el={'link'}
|
||||
Link={Link}
|
||||
size="small"
|
||||
to={newDocumentURL}
|
||||
>
|
||||
|
||||
@@ -14,7 +14,6 @@ import { Button } from '../../elements/Button/index.js'
|
||||
import { DeleteMany } from '../../elements/DeleteMany/index.js'
|
||||
import { EditMany } from '../../elements/EditMany/index.js'
|
||||
import { Gutter } from '../../elements/Gutter/index.js'
|
||||
import { Link } from '../../elements/Link/index.js'
|
||||
import { ListControls } from '../../elements/ListControls/index.js'
|
||||
import { useListDrawerContext } from '../../elements/ListDrawer/Provider.js'
|
||||
import { ListSelection } from '../../elements/ListSelection/index.js'
|
||||
@@ -238,7 +237,7 @@ export const DefaultListView: React.FC<ListViewClientProps> = (props) => {
|
||||
})}
|
||||
</Button>
|
||||
) : (
|
||||
<Button el="link" Link={Link} to={newDocumentURL}>
|
||||
<Button el="link" to={newDocumentURL}>
|
||||
{i18n.t('general:createNewLabel', {
|
||||
label: getTranslation(labels?.singular, i18n),
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user