feature: button in richtext
This commit is contained in:
parent
36956d7daf
commit
005f91b898
7 changed files with 85 additions and 6 deletions
|
|
@ -14,7 +14,7 @@ import { getPhoto } from '@/utils/dto/gallery'
|
||||||
import { isAuthenticated } from '@/utils/auth'
|
import { isAuthenticated } from '@/utils/auth'
|
||||||
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
|
import { AdminMenu } from '@/components/AdminMenu/AdminMenu'
|
||||||
import { GroupEvents } from '@/compositions/GroupEvents/GroupEvents'
|
import { GroupEvents } from '@/compositions/GroupEvents/GroupEvents'
|
||||||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
import { RichText } from '@/components/Text/RichText'
|
||||||
import { RefreshRouteOnSave } from '@/components/RefreshRouteOnSave/RefreshRouteOnSave'
|
import { RefreshRouteOnSave } from '@/components/RefreshRouteOnSave/RefreshRouteOnSave'
|
||||||
import { draftMode } from 'next/headers'
|
import { draftMode } from 'next/headers'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { PageHeader } from '@/compositions/PageHeader/PageHeader'
|
import { PageHeader } from '@/compositions/PageHeader/PageHeader'
|
||||||
import { fetchDonationForm } from '@/fetch/donationform'
|
import { fetchDonationForm } from '@/fetch/donationform'
|
||||||
import { notFound } from 'next/navigation'
|
import { notFound } from 'next/navigation'
|
||||||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
import { RichText } from '@/components/Text/RichText'
|
||||||
import { Container } from '@/components/Container/Container'
|
import { Container } from '@/components/Container/Container'
|
||||||
import { Section } from '@/components/Section/Section'
|
import { Section } from '@/components/Section/Section'
|
||||||
import styles from '@/components/DonationForm/styles.module.scss'
|
import styles from '@/components/DonationForm/styles.module.scss'
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import classNames from 'classnames'
|
|
||||||
import styles from '@/components/Classifieds/styles.module.scss'
|
import styles from '@/components/Classifieds/styles.module.scss'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { SerializedEditorState } from 'lexical'
|
import { SerializedEditorState } from 'lexical'
|
||||||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
import { RichText } from '@/components/Text/RichText'
|
||||||
|
|
||||||
type AdProps = {
|
type AdProps = {
|
||||||
text: SerializedEditorState,
|
text: SerializedEditorState,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import styles from "./html.module.scss"
|
import styles from "./html.module.scss"
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import { RichText } from '@payloadcms/richtext-lexical/react'
|
|
||||||
import { SerializedEditorState, SerializedLexicalNode } from 'lexical'
|
import { SerializedEditorState, SerializedLexicalNode } from 'lexical'
|
||||||
|
import { RichText } from './RichText'
|
||||||
|
|
||||||
type HTMLTextProps = {
|
type HTMLTextProps = {
|
||||||
width: "1/2" | "3/4",
|
width: "1/2" | "3/4",
|
||||||
|
|
|
||||||
16
src/components/Text/RichText.tsx
Normal file
16
src/components/Text/RichText.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { RichText as PayloadRichText } from '@payloadcms/richtext-lexical/react'
|
||||||
|
import { SerializedEditorState } from 'lexical'
|
||||||
|
import { jsxConverters } from './converters'
|
||||||
|
|
||||||
|
|
||||||
|
type RichTextProps = {
|
||||||
|
data: SerializedEditorState
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thin wrapper around Payload's RichText that always wires up our custom JSX
|
||||||
|
// converters (e.g. rendering links marked as "Button" via the Button component).
|
||||||
|
// Use this everywhere instead of importing RichText directly from Payload, so
|
||||||
|
// the converters can never be forgotten.
|
||||||
|
export const RichText = ({ data }: RichTextProps) => (
|
||||||
|
<PayloadRichText data={data} converters={jsxConverters} />
|
||||||
|
)
|
||||||
50
src/components/Text/converters.tsx
Normal file
50
src/components/Text/converters.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import type { JSXConvertersFunction } from '@payloadcms/richtext-lexical/react'
|
||||||
|
import type { SerializedLinkNode } from '@payloadcms/richtext-lexical'
|
||||||
|
import { Button } from '@/components/Button/Button'
|
||||||
|
|
||||||
|
// Lexical link nodes carry their editable metadata (url, newTab, linkType, ...)
|
||||||
|
// in `fields`. We extend that shape with the custom `appearance` select that is
|
||||||
|
// added to LinkFeature in `payload.config.ts`, so editors can mark a link as a
|
||||||
|
// call-to-action button.
|
||||||
|
type LinkFields = SerializedLinkNode['fields'] & {
|
||||||
|
appearance?: 'link' | 'button'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom JSX converters passed to <RichText /> wherever rich text is rendered.
|
||||||
|
// Only the `link` converter is overridden — every other node keeps Payload's
|
||||||
|
// default rendering via the spread of `defaultConverters`.
|
||||||
|
export const jsxConverters: JSXConvertersFunction = ({ defaultConverters }) => ({
|
||||||
|
...defaultConverters,
|
||||||
|
link: (args) => {
|
||||||
|
const { node, nodesToJSX } = args
|
||||||
|
const fields = node.fields as LinkFields
|
||||||
|
|
||||||
|
// Normal link → delegate to Payload's built-in link converter.
|
||||||
|
// The default converter from the package is a function, but the type
|
||||||
|
// also allows a static ReactNode, so we narrow before calling.
|
||||||
|
if (fields?.appearance !== 'button') {
|
||||||
|
const defaultLink = defaultConverters.link
|
||||||
|
return typeof defaultLink === 'function' ? defaultLink(args) : defaultLink
|
||||||
|
}
|
||||||
|
|
||||||
|
// Button link → resolve href the same way Payload's default converter does:
|
||||||
|
// internal links point at the related doc's slug, custom links use `url`.
|
||||||
|
const href =
|
||||||
|
fields.linkType === 'internal' && typeof fields.doc?.value === 'object'
|
||||||
|
? `/${(fields.doc.value as { slug?: string }).slug ?? ''}`
|
||||||
|
: (fields.url ?? '#')
|
||||||
|
|
||||||
|
// Render the existing Button component. Schema and size are intentionally
|
||||||
|
// hardcoded — editors only choose link vs. button, not the styling.
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
href={href}
|
||||||
|
size="md"
|
||||||
|
schema="contrast"
|
||||||
|
target={fields.newTab ? '_blank' : '_self'}
|
||||||
|
>
|
||||||
|
{nodesToJSX({ nodes: node.children })}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
@ -138,7 +138,21 @@ export default buildConfig({
|
||||||
HeadingFeature({ enabledHeadingSizes: ["h3","h4","h5"]}),
|
HeadingFeature({ enabledHeadingSizes: ["h3","h4","h5"]}),
|
||||||
AlignFeature(),
|
AlignFeature(),
|
||||||
UnorderedListFeature(),
|
UnorderedListFeature(),
|
||||||
LinkFeature(),
|
LinkFeature({
|
||||||
|
fields: ({ defaultFields }) => [
|
||||||
|
...defaultFields,
|
||||||
|
{
|
||||||
|
name: 'appearance',
|
||||||
|
type: 'select',
|
||||||
|
defaultValue: 'link',
|
||||||
|
label: 'Darstellung',
|
||||||
|
options: [
|
||||||
|
{ label: 'Link', value: 'link' },
|
||||||
|
{ label: 'Button', value: 'button' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
ParagraphFeature(),
|
ParagraphFeature(),
|
||||||
InlineToolbarFeature(),
|
InlineToolbarFeature(),
|
||||||
FixedToolbarFeature()
|
FixedToolbarFeature()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue