01 SOFTWARE
SDK

Components

React 컴포넌트

Components

SDK가 제공하는 React 컴포넌트들입니다.

RichTextContent

Payload CMS의 Lexical 리치 텍스트를 렌더링하는 컴포넌트입니다.

기본 사용법

import { RichTextContent } from '@01.software/sdk'

function Article({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <RichTextContent data={post.content} />
    </article>
  )
}

Props

interface RichTextContentProps {
  // 필수: Lexical 에디터 데이터
  data: any

  // 선택: CSS 클래스
  className?: string

  // 선택: 내부 문서 링크 변환 함수
  internalDocToHref?: (args: {
    relationTo: string
    doc: any
  }) => string

  // 선택: 커스텀 블록 컴포넌트
  blocks?: {
    [blockType: string]: React.ComponentType<any>
  }
}

스타일링

Tailwind Typography를 사용하면 쉽게 스타일링할 수 있습니다.

import { RichTextContent } from '@01.software/sdk'

function Article({ post }) {
  return (
    <article className="prose prose-lg max-w-none">
      <RichTextContent
        data={post.content}
        className="prose-headings:font-bold prose-a:text-blue-600"
      />
    </article>
  )
}

prose 클래스는 Tailwind Typography 플러그인에서 제공됩니다.

내부 링크 처리

문서 간 링크를 Next.js 라우팅으로 변환할 수 있습니다.

import { RichTextContent } from '@01.software/sdk'
import Link from 'next/link'

function Article({ post }) {
  return (
    <RichTextContent
      data={post.content}
      internalDocToHref={(args) => {
        // relationTo에 따라 다른 URL 패턴 적용
        switch (args.relationTo) {
          case 'posts':
            return `/blog/${args.doc.slug}`
          case 'products':
            return `/products/${args.doc.slug}`
          case 'pages':
            return `/${args.doc.slug}`
          default:
            return '/'
        }
      }}
    />
  )
}

커스텀 블록

Lexical 에디터의 커스텀 블록을 렌더링할 수 있습니다.

import { RichTextContent } from '@01.software/sdk'

// 커스텀 블록 컴포넌트
function IframeBlock({ url, height }: { url: string; height?: number }) {
  return (
    <div className="my-8">
      <iframe
        src={url}
        height={height || 400}
        className="w-full border-0"
        allowFullScreen
      />
    </div>
  )
}

function VideoPlayerBlock({ videoId }: { videoId: string }) {
  return (
    <div className="my-8 aspect-video">
      <ReactPlayer
        url={`https://youtube.com/watch?v=${videoId}`}
        width="100%"
        height="100%"
        controls
      />
    </div>
  )
}

function CodeBlock({ code, language }: { code: string; language: string }) {
  return (
    <pre className="my-8 overflow-x-auto rounded-lg bg-gray-900 p-4">
      <code className={`language-${language}`}>{code}</code>
    </pre>
  )
}

// 사용
function Article({ post }) {
  return (
    <RichTextContent
      data={post.content}
      blocks={{
        Iframe: IframeBlock,
        Player: VideoPlayerBlock,
        Code: CodeBlock,
      }}
    />
  )
}

지원하는 노드 타입

RichTextContent는 다음 Lexical 노드들을 지원합니다:

텍스트 노드

  • Paragraph: 일반 단락
  • Heading: H1-H6 제목
  • Quote: 인용문
  • List: 순서/비순서 목록

인라인 노드

  • Link: 링크
  • Bold: 굵게
  • Italic: 기울임
  • Underline: 밑줄
  • Strikethrough: 취소선
  • Code: 인라인 코드

블록 노드

  • Upload: 이미지 업로드
  • Relationship: 문서 관계
  • Custom Blocks: 커스텀 블록

실전 예제

블로그 포스트

'use client'

import { client } from '@/lib/client'
import { RichTextContent } from '@01.software/sdk'

export default function BlogPostPage({ params }: { params: { slug: string } }) {
  const { data: post, isLoading } = client.query.useCollectionSingle('posts', {
    where: { slug: { equals: params.slug } }
  })

  if (isLoading) return <div>Loading...</div>
  if (!post) return <div>Post not found</div>

  return (
    <article className="max-w-4xl mx-auto px-4 py-8">
      <header className="mb-8">
        <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
        <div className="text-gray-600">
          {new Date(post.createdAt).toLocaleDateString()}
        </div>
      </header>

      <div className="prose prose-lg max-w-none">
        <RichTextContent
          data={post.content}
          internalDocToHref={(args) => {
            if (args.relationTo === 'posts') {
              return `/blog/${args.doc.slug}`
            }
            return '/'
          }}
        />
      </div>
    </article>
  )
}

상품 설명

import { RichTextContent } from '@01.software/sdk'

function ProductDescription({ product }) {
  return (
    <div className="bg-white rounded-lg p-6">
      <h2 className="text-2xl font-bold mb-4">상품 설명</h2>

      <div className="prose max-w-none">
        <RichTextContent
          data={product.description}
          className="text-gray-700"
        />
      </div>
    </div>
  )
}

문서 뷰어

import { RichTextContent } from '@01.software/sdk'
import ReactPlayer from 'react-player'

// 비디오 블록
function VideoBlock({ url }: { url: string }) {
  return (
    <div className="my-8">
      <ReactPlayer
        url={url}
        width="100%"
        height="100%"
        controls
        className="aspect-video"
      />
    </div>
  )
}

// 캘아웃 블록
function CalloutBlock({
  type,
  content
}: {
  type: 'info' | 'warning' | 'error'
  content: string
}) {
  const styles = {
    info: 'bg-blue-50 border-blue-500 text-blue-900',
    warning: 'bg-yellow-50 border-yellow-500 text-yellow-900',
    error: 'bg-red-50 border-red-500 text-red-900',
  }

  return (
    <div className={`my-4 border-l-4 p-4 ${styles[type]}`}>
      {content}
    </div>
  )
}

function DocumentViewer({ document }) {
  return (
    <div className="max-w-4xl mx-auto">
      <h1 className="text-3xl font-bold mb-6">{document.title}</h1>

      <RichTextContent
        data={document.content}
        className="prose prose-lg max-w-none"
        blocks={{
          Video: VideoBlock,
          Callout: CalloutBlock,
        }}
        internalDocToHref={(args) => {
          if (args.relationTo === 'documents') {
            return `/docs/${args.doc.slug}`
          }
          return '/'
        }}
      />
    </div>
  )
}

이미지 최적화

Next.js Image 컴포넌트와 함께 사용하여 이미지를 최적화할 수 있습니다.

import { RichTextContent } from '@01.software/sdk'
import Image from 'next/image'

// 커스텀 이미지 블록
function OptimizedImageBlock({
  src,
  alt,
  width,
  height
}: {
  src: string
  alt: string
  width?: number
  height?: number
}) {
  return (
    <div className="my-8">
      <Image
        src={src}
        alt={alt}
        width={width || 1200}
        height={height || 800}
        className="rounded-lg"
        priority={false}
      />
    </div>
  )
}

function Article({ post }) {
  return (
    <RichTextContent
      data={post.content}
      blocks={{
        Upload: OptimizedImageBlock,
      }}
    />
  )
}

다크 모드 지원

import { RichTextContent } from '@01.software/sdk'

function Article({ post }) {
  return (
    <div className="prose dark:prose-invert">
      <RichTextContent
        data={post.content}
        className="
          prose-headings:text-gray-900 dark:prose-headings:text-gray-100
          prose-p:text-gray-700 dark:prose-p:text-gray-300
          prose-a:text-blue-600 dark:prose-a:text-blue-400
        "
      />
    </div>
  )
}

반응형 디자인

import { RichTextContent } from '@01.software/sdk'

function Article({ post }) {
  return (
    <div className="
      prose
      prose-sm md:prose-base lg:prose-lg
      max-w-none
      px-4 md:px-8 lg:px-12
    ">
      <RichTextContent data={post.content} />
    </div>
  )
}

성능 최적화

코드 스플리팅

무거운 블록 컴포넌트는 동적 import로 최적화할 수 있습니다.

import { RichTextContent } from '@01.software/sdk'
import dynamic from 'next/dynamic'

// 동적 import로 코드 스플리팅
const VideoPlayer = dynamic(() => import('./VideoPlayer'), {
  loading: () => <div>Loading player...</div>,
  ssr: false,
})

const CodeEditor = dynamic(() => import('./CodeEditor'), {
  loading: () => <div>Loading editor...</div>,
})

function Article({ post }) {
  return (
    <RichTextContent
      data={post.content}
      blocks={{
        Video: VideoPlayer,
        Code: CodeEditor,
      }}
    />
  )
}

메모이제이션

import { RichTextContent } from '@01.software/sdk'
import { memo } from 'react'

const MemoizedRichTextContent = memo(RichTextContent)

function Article({ post }) {
  return <MemoizedRichTextContent data={post.content} />
}

다음 단계

On this page